Skip to content
CMO & CTO
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

  • Digital Experience
    • Experience Strategy
    • Experience-Driven Commerce
    • Multi-Channel Experience
    • Personalization & Targeting
    • SEO & Performance
    • User Journey & Behavior
  • Marketing Technologies
    • Analytics & Measurement
    • Content Management Systems
    • Customer Data Platforms
    • Digital Asset Management
    • Marketing Automation
    • MarTech Stack & Strategy
    • Technology Buying & ROI
  • Software Engineering
    • Software Engineering
    • Software Architecture
    • General Software
    • Development Practices
    • Productivity & Workflow
    • Code
    • Engineering Management
    • Business of Software
    • Code
    • Digital Transformation
    • Systems Thinking
    • Technical Implementation
  • About
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

Interoperability on the JVM: Practical Pitfalls

Posted on May 17, 2015 By Luis Fernandez

“Interoperability is where bright ideas meet old decisions.”

Working on the JVM feels like living in a crowded apartment with great roommates who forget to wash the dishes. You get Java 8 lambdas, a bit of Scala sugar, some Groovy tests, maybe a Clojure script to poke a service. Everything runs on the same machine, yet small things keep bumping into each other. This week Groovy moved under the Apache umbrella, Kotlin keeps teasing a stable release, and teams are mixing it all with Gradle. The vibe is good. The papercuts are real.

Two projects in my week show the contrast. On a backend service we stitched Scala into a Java codebase. It shipped. It is fast. It also taught us that public Java facing APIs need a different shape from what feels natural in Scala. On a mobile client, we tried a tiny slice of Kotlin next to Java on Android. The code read well and tests were clean, yet the build told a different story. Tooling and bytecode rules did not care about our taste. They cared about details.

Technical deep dive one: types, nulls, and the trap of convenience

The most common interop bugs start with types that look close but are not. Java treats null as a routine thing. Scala and Kotlin try to make it explicit. Groovy shrugs and keeps moving. Then you pass data between them and wonder why an NPE shows up three calls later.

Pick a stance for nullability at the edges. In Java code expose @Nullable and @NotNull annotations that your other languages understand. Kotlin reads those and gives you safer types. Scala tooling can pick them up too. If you are writing Scala or Kotlin APIs that Java will call, prefer methods that return a plain value with a clear contract, not a nested type that Java folks will misread. Option is great inside Scala, but Java callers often ignore it or misuse it. Consider a simple return plus a clear javadoc about when it can be empty.

Watch for default parameters and method overloading. Kotlin and Scala make defaults feel natural. Java does not know about them, so compilers create multiple overloads. Mixed with varargs and generics this gets messy. Overload resolution can flip when called from Groovy or when erased types line up in a different way. Keep the surface small. Fewer overloads. Fewer surprises.

Then there are checked exceptions. Java advertises them. Scala and Groovy ignore them. When Java calls Scala code that throws a checked exception at runtime, you get a catch block that never compiled for it. Decide up front whether you throw checked exceptions across language lines. If you do not, wrap them. If you do, declare them in Java and make your other languages call through a small Java shim.

Technical deep dive two: collections, generics, and conversion costs

Collections feel universal, yet the defaults are different across the stack. Scala collections have their own hierarchy with views and builders. Kotlin has read only views and mutable views that are both List at runtime. Clojure gives you persistent structures. Groovy adds truthiness and GDK methods on top of Java types. The bytecode sees type erasure. Your runtime sees surprises.

When crossing a boundary, convert on entry and on exit. If a Java method says it returns a List, return a concrete Java List. Do not leak Scala Seq or a Clojure vector and hope callers are polite. In Scala use JavaConverters with care and keep it local to the boundary. In Kotlin pick the right view. A read only List is not the same as an immutable List, and mutation will still happen under the hood if someone holds a reference to the backing collection.

Generics add another twist. Variance in Scala types rarely lines up with Java wildcards. Mixed with overloaded methods this can turn a single call red in one language and green in another. Avoid raw types. Avoid wildcard heavy signatures at the edge. If you need flexibility, publish a tiny Java interface with simple types and keep the rich types inside the module.

Technical deep dive three: bytecode quirks, build order, and method count pain

The JVM is friendly to many languages because the bytecode leaves room for tricks. Those tricks show up in your interop. Companion objects in Scala or Kotlin surface as synthetic holders of static like members. Call sites from Java may need Foo.Companion.bar or a generated class with a dollar in the name. Reflection adds more surprises. Parameter names are not always stored, annotation retention varies, and proxies do not always look like real classes to other runtimes. If you use frameworks that inspect classes, test them with your mixed code early.

Build order matters. Java compiles fast and simple. Scala needs the whole picture. Groovy can compile against stubs that are not perfect. If your build mixes them, define a stable compile plan. Compile Scala with the scala plugin first or last as needed, then Java, then Groovy with stubs if you must, or keep them in separate modules. Maven and Gradle support this, but the defaults are not always right. Watch for binary compatibility across Scala versions too. Upgrading from one minor version to the next can break callers. Lock versions per module and upgrade in steps.

On Android you meet a different limit. The 65k method count ceiling is real. Each language brings helpers and standard libs. Add Groovy and your count jumps. Add Scala and it jumps again. Proguard helps, but do the math early. Keep the language count low on the client side or split features into modules that ship as needed. Nice syntax is not worth a late night with multidex if you can avoid it.

Small note on performance. Dynamic dispatch from Groovy, reflective builders, or boxing in high traffic paths will tax the CPU. Clojure interop with hot loops needs care. Put hot code in plain Java or well tuned Scala and keep the fancy bits at the edges.

Reflective close

Mixing languages on the JVM can make teams faster and code clearer. The trap is not the idea, it is the seam. Design Java first boundaries. Keep collections and types simple at the edge. Convert data on entry and exit. Be strict about nulls. Test with the same tools your runtime uses. Keep builds honest and predictable. Write the fancy parts inside, and keep the door to other callers plain. The JVM will meet you half way, as long as you do not forget who is washing the dishes.

Software Architecture Software Engineering

Post navigation

Previous post
Next post
  • Digital Experience (94)
    • Experience Strategy (19)
    • Experience-Driven Commerce (5)
    • Multi-Channel Experience (9)
    • Personalization & Targeting (21)
    • SEO & Performance (10)
  • Marketing Technologies (92)
    • Analytics & Measurement (14)
    • Content Management Systems (45)
    • Customer Data Platforms (4)
    • Digital Asset Management (8)
    • Marketing Automation (6)
    • MarTech Stack & Strategy (10)
    • Technology Buying & ROI (3)
  • Software Engineering (310)
    • Business of Software (20)
    • Code (30)
    • Development Practices (52)
    • Digital Transformation (21)
    • Engineering Management (25)
    • General Software (82)
    • Productivity & Workflow (30)
    • Software Architecture (85)
    • Technical Implementation (23)
  • 2025 (12)
  • 2024 (8)
  • 2023 (18)
  • 2022 (13)
  • 2021 (3)
  • 2020 (8)
  • 2019 (8)
  • 2018 (23)
  • 2017 (17)
  • 2016 (40)
  • 2015 (37)
  • 2014 (25)
  • 2013 (28)
  • 2012 (24)
  • 2011 (30)
  • 2010 (42)
  • 2009 (25)
  • 2008 (13)
  • 2007 (33)
  • 2006 (26)

Ab Testing Adobe Adobe Analytics Adobe Target AEM agile-methodologies Analytics architecture-patterns CDP CMS coding-practices content-marketing Content Supply Chain Conversion Optimization Core Web Vitals customer-education Customer Data Platform Customer Experience Customer Journey DAM Data Layer Data Unification documentation DXP Individualization java Martech metrics mobile-development Mobile First Multichannel Omnichannel Personalization product-strategy project-management Responsive Design Search Engine Optimization Segmentation seo spring Targeting Tracking user-experience User Journey web-development

©2025 CMO & CTO | WordPress Theme by SuperbThemes