Skip to main content
Languages

Kotlin

Choose the JVM or mobile product first, then decide what Kotlin should own.

The Android team wants a modern language, or the Java estate needs a safer route for new work. Kotlin can be introduced incrementally on the JVM, but the commercial decision still depends on the product, build and platform boundary around it.

Kotlin offers concise syntax, nullable types, coroutines and close Java interoperability. It is the preferred language for modern Android guidance and a credible JVM server language. Kotlin Multiplatform can share selected logic across targets, although each target retains its platform APIs, releases and user experience.

We choose Kotlin for a defined code boundary. “One language everywhere” is not a useful acceptance test.

Kotlin is strongest when the platform decision is already sound

For a new native Android application, Kotlin follows the platform’s primary documentation, libraries and Jetpack examples. Google’s Android Kotlin guidance makes that ecosystem alignment clear. The language is rarely the controversial part; device support, app architecture and release ownership are.

On the server, Kotlin can use the JVM’s mature libraries and operating model while giving teams a smaller type and syntax surface than equivalent Java code. That fit is strongest where the organisation already knows JVM deployment, profiling and build tooling.

Starting a JVM capability only because Kotlin looks pleasant needs more scrutiny. A maintained Java, PHP, C# or Node stack may serve the product with less organisational change. Language preference should not multiply production platforms without a benefit.

Java interoperability makes migration possible, not costless

Kotlin and Java can call each other in the same JVM project. That lets a team add Kotlin to a module or write new features without rewriting a stable Java estate. The official Java interoperability guide also exposes the edge cases: getters, checked exceptions, static members, keywords and nullability all cross language conventions.

Java types without reliable nullability annotations appear as platform types. Kotlin cannot provide the same compile-time guarantee at that boundary that it gives within well-typed Kotlin. We wrap important legacy APIs, add annotations where owned and test their real null behaviour.

Mixed-language code also affects review and staffing. A clean incremental plan names which modules may use Kotlin, how public APIs appear to both languages and when old Java code is left alone. A partial migration should reduce risk rather than create two competing styles in every class.

Null safety helps only when unsafe edges stay visible

Kotlin distinguishes nullable and non-nullable references and uses flow analysis to narrow values after checks. This removes a common class of accidental null dereferences in code that respects the type model.

The escape hatches still matter. The not-null assertion can turn uncertainty into a runtime failure. Late-initialised properties can fail if lifecycle assumptions are wrong. Deserialised data, databases and Java libraries still need validation.

We treat repeated assertions as a design signal. A boundary parser, explicit state or narrower type often describes the product better than promising the value will be present. Null safety is most useful when it drives domain modelling instead of shortening error handling.

API contracts and persistence schemas remain runtime concerns. A non-null Kotlin property cannot make an old database row or partner response comply.

Coroutines require cancellation and lifecycle decisions

Kotlin coroutines provide suspending functions and structured tools for asynchronous work. They can make network and database flows easier to read than nested callbacks. The coroutines guide also covers context, cancellation, exceptions, shared state and flow, which are the parts production code must own.

A coroutine is not a free thread, and a suspending function does not make blocking work non-blocking. Dispatchers should match the library and workload. Blocking database or file calls need a suitable execution context; CPU-heavy work needs bounded parallelism.

On Android, work must respect screen and application lifecycles. Google’s coroutine practices recommend lifecycle-aware ownership and exposing suspend functions or flows from lower layers. We test cancellation, rotation, backgrounding and duplicate requests rather than proving only the happy path.

Server code needs the same discipline around request cancellation, timeouts and shutdown. Lost coroutine exceptions can be operational failures even when the service process remains healthy.

Gradle and compiler compatibility belong in the estimate

Kotlin projects commonly combine the Kotlin compiler plugin, Gradle, JDK, Android Gradle Plugin or framework plugins, and code-generation tools. Their compatibility can determine whether a language update is a short change or a build migration.

We pin supported versions, retain the wrapper and make clean builds reproducible in CI. Compiler warnings and deprecations are addressed regularly. Waiting through several toolchain generations concentrates unrelated changes into one risky upgrade.

Build performance matters to delivery. Annotation processing, code generation and large multiplatform graphs can slow feedback. We measure clean and incremental builds before prescribing caches or extra plugins.

A dependency compiled for a new Kotlin metadata or compiler generation may block an older project. Package readiness is part of upgrade planning, especially around Compose and multiplatform components with their own release cycles.

Kotlin Multiplatform should share stable rules selectively

Kotlin Multiplatform organises common and target-specific source sets. The project structure guide shows that common code cannot automatically use a platform API such as Java file access. Sharing is constrained by the targets and libraries selected.

Business rules, validation, data models and API clients are often sensible candidates. Camera, notifications, background execution, accessibility and interface layout remain platform-shaped. We choose shared modules by stability and testing value rather than a target percentage.

Current platform and UI stability levels differ and are published in the supported-platform table. The team must check the targets it will ship, not rely on the broad word “multiplatform.”

For iOS, the decision includes Swift-facing APIs, binary size, debugging, build integration and whether iOS engineers can work effectively at the Kotlin boundary. A proof should exercise the hardest native integration and release build.

Questions to answer before approving Kotlin

  • “Is this Android, JVM server or shared product work?” Name the target and its operating model.
  • “Which Java modules and nullability edges will Kotlin call?” Wrap unsafe contracts deliberately.
  • “Who owns coroutine scopes, cancellation and blocking work?” Test lifecycle failures.
  • “Which Gradle, JDK and plugin versions form the supported build?” Keep CI reproducible.
  • “What code is stable enough to share across platforms?” Prefer domain rules over UI quotas.
  • “Can each platform team debug the common boundary?” Do not centralise knowledge in one person.
  • “What is the incremental migration route?” Avoid rewriting proven Java without product value.

When Kotlin is a sound language choice

Choose Kotlin for modern Android applications, JVM services whose team values its type model, or carefully scoped shared modules across platforms. Its Java interoperability supports gradual adoption and preserves access to a mature ecosystem.

The case is complete when toolchain compatibility, coroutine ownership and platform testing are included. Kotlin can improve code clarity without pretending the platform has disappeared.

When Kotlin is expanding the technology estate

Keep Java when a maintained JVM application and team gain little from mixing languages. Introduce Kotlin at a new module boundary only if that boundary provides a credible evaluation.

Choose separate native implementations when shared code would be small, unstable or difficult for one platform team to debug. Swift and Kotlin can still share API contracts and product behaviour without sharing source.

And avoid Kotlin Multiplatform as a mandate to maximise shared code. The right amount is the code whose sameness helps both products.

Alexander De Sousa, Founder of Digital Royalty
Developer’s verdict

Kotlin is an excellent Android and JVM choice when its platform boundary stays explicit.

I use Kotlin for Android and JVM work where null-aware types, coroutines and Java interoperability improve a real codebase. I do not promise one codebase across every platform before testing the native edges. Show me the Gradle build, cancellation model and shared-module boundary, and I can judge the language honestly.

Alexander De Sousa · Founder, Digital Royalty · LinkedIn

Decision evidence

What Kotlin changes inside an Android, JVM or multiplatform product

Kotlin improves language-level expression and can share selected code. Java edges, coroutine lifecycle, build tooling and target-specific behaviour still need owners.

The Kotlin commitment in six decisions

Best fit
Modern Android, JVM services and stable cross-platform domain modules supported by teams that can own Gradle and target-specific integration.
Product effect
Clearer null and asynchronous models in owned code, with remaining risk at Java, data, lifecycle and native-platform boundaries.
Adoption cost
Low for incremental JVM use; moderate for new Android architecture; higher for multiplatform builds, native integration and two-platform release testing.
Ongoing owner
Android or JVM engineers responsible for compiler, Gradle, JDK, dependencies, coroutine behaviour and each supported platform target.
Exit cost
Low within a mixed JVM estate; moderate to high when shared Kotlin modules and Compose UI are embedded across native products.
Proof required
A clean CI build, an unsafe Java or data boundary, cancellation under failure and the hardest native integration on every intended target.

Language routes to compare for JVM and mobile work

  • Keep Java on the JVM

    Right when: The maintained application, libraries and team already use current Java well and a second language would not improve a meaningful boundary.

    Watch for: Distinguish deliberate consistency from avoiding useful modernisation in a new isolated module.

  • Kotlin for Android or JVM only

    Right when: One platform gains from Kotlin's primary ecosystem support without adding a cross-platform build and API commitment.

    Watch for: Java interop, coroutine lifecycle and Gradle versions still require explicit policies.

  • Kotlin shared domain modules

    Right when: Stable rules and API handling should remain aligned while user interfaces and native integrations stay platform-owned.

    Watch for: Design Swift-facing APIs and debugging routes and test each target's library compatibility before expanding the shared boundary.

  • Separate Swift and Kotlin applications

    Right when: Native experience and independent platform delivery matter more than sharing implementation.

    Watch for: Maintain shared product specifications and API contracts so separate source does not create different business behaviour.

Research behind this Kotlin position

  • Platform guidance
    Android development with Kotlin

    Positions Kotlin across modern Android documentation and Jetpack libraries, making it the natural language baseline for a new native Android build.

  • Official interoperability
    Calling Java from Kotlin

    Documents mixed-language calls and platform types, showing where Java contracts weaken Kotlin's normal null guarantees.

  • Official concurrency
    Kotlin coroutines guide

    Covers cancellation, contexts, exception handling, shared state and flows rather than treating suspend syntax as the whole concurrency model.

  • Platform concurrency
    Android coroutine best practices

    Recommends lifecycle-aware scope ownership and main-safe lower layers for Android applications.

  • Official multiplatform architecture
    Kotlin Multiplatform project structure

    Explains common and target-specific source sets and which APIs become unavailable when code must compile for several targets.

  • Official stability matrix
    Kotlin Multiplatform supported platforms

    Publishes current stability levels separately for core targets and Compose UI, allowing the actual target set to drive risk.

  • Developer ecosystem survey
    JetBrains developer ecosystem: Kotlin

    Provides vendor-run evidence about Kotlin use cases and adoption. It is useful context and should not replace product-specific staffing evidence.

  • Practitioner question
    Stack Overflow: understanding coroutine cancellation

    The discussion illustrates recurring uncertainty around failure propagation; current structured-concurrency documentation should guide implementation.

Get started

Tell us what you need

A few quick questions, then a straight answer from a real person — usually within a few hours.

Tell us what you're working on

Whether it's a new site, a platform, or a process that shouldn't be manual any more — we'll tell you honestly if we can help.