ADR-009: Kotlin as Primary Language

Field

Value

Status

Accepted

Date

2026-05-12

Deciders

Full Team

Supersedes

Superseded by


Context

We need to choose a primary JVM language for our Spring Modulith backend (ADR-002). The choice affects day-to-day developer productivity, code expressiveness, safety guarantees, and the long-term maintainability of the codebase. The team has existing experience on the JVM and the system is a backend service without UI concerns.

Decision Drivers

  • Null-safety as a language-level guarantee reduces an entire class of runtime errors

  • Concise, expressive syntax reduces boilerplate in domain and data classes

  • Full interoperability with the Java/Spring ecosystem

  • Coroutine support for non-blocking I/O without callback complexity

Considered Options

  • Option A – Kotlin

  • Option B – Java

Decision

Chosen option: Option A – Kotlin is used as the primary language across all modules. Java interoperability is maintained, but all new code is written in Kotlin.

Rationale

The team already has Java background, so the step up to Kotlin is a natural one. Kotlin’s null-safety system catches potential null pointer exceptions at compile time rather than at runtime, which is particularly valuable in a backend service where data flows through multiple layers. Data classes significantly reduce boilerplate for model and event classes — a daily concern given our module structure (ADR-004). Coroutines provide structured, readable non-blocking I/O without the complexity of reactive streams, and integrate well with Spring WebFlux and Spring Data if needed in the future. Spring has first-class Kotlin support, including Kotlin-specific DSLs and extension functions.

Pros and Cons of the Options

Option A – Kotlin

  • ✅ Null-safety at compile time — eliminates a class of NullPointerException bugs

  • ✅ Data classes reduce boilerplate for model and event classes

  • ✅ Coroutines enable non-blocking I/O with sequential, readable code

  • ✅ Full Java interoperability — all Spring and Java libraries work without wrappers

  • ✅ First-class Spring support including Kotlin DSLs and extension functions

  • ❌ Slightly longer compile times than Java in some configurations

  • ❌ Slight adoption barrier at first

Option B – Java

  • ✅ Team experience in Java

  • ✅ Slightly faster compile times in some toolchain setups

  • ❌ Verbose boilerplate for data classes, builders, and null checks (partially mitigated by Records and newer Java features, but still less concise than Kotlin)

  • ❌ Null safety requires external tooling (e.g. @NonNull annotations) rather than being built into the type system

  • ❌ No native coroutine support — reactive programming requires a steeper learning curve

Consequences

Positive:

  • Concise data and event classes across all modules (aligns with ADR-0003 module structure)

  • Compile-time null-safety reduces defensive null checks throughout the codebase

  • Coroutines available if non-blocking I/O becomes a requirement without introducing a reactive framework

Negative / Trade-offs:

  • All contributors must be comfortable with Kotlin; onboarding Java-only developers requires a short ramp-up period

  • Kotlin compiler plugins (e.g. for Spring, JPA) must be configured correctly — misconfiguration can cause subtle runtime issues (e.g. open classes for JPA entities)

Follow-up actions:

  • Ensure Kotlin compiler plugins for Spring (kotlin-spring) and JPA (kotlin-jpa) are configured in the build file

  • Add a Kotlin style guide reference to the team wiki (e.g. official Kotlin coding conventions)

  • Agree on coroutine usage boundaries — whether coroutines are used broadly or only where explicitly needed