Riadh Mnasri
← Back to blog
3 min read

Why Kotlin remains my JVM language of choice

Kortex, kotlin-counterparty-risk, kotlin-chess-tournament: three seemingly unrelated projects, united by the same language choice. This isn't blind loyalty to a tool, but the result of a few specific traits that matter particularly in the domains I work in.

Null-safety as a guardrail, not a constraint

In market finance, a null value flowing through a calculation without being explicitly handled can be expensive: a NullPointerException in the middle of an overnight batch calculation across an entire portfolio isn't an abstraction. Kotlin's type system, with its strict distinction between nullable and non-nullable types, moves that entire class of bugs from runtime to compile time. It isn't a developer comfort, it's a measurable reduction in operational risk.

// collateral.amount doesn't compile: collateral is nullable (Collateral?),
// and .amount is only directly accessible on a non-nullable type.

fun calculateExposure(collateral: Collateral?): BigDecimal {
    return collateral?.amount ?: BigDecimal.ZERO
}

That second call isn't just safer: it makes visible, in the function's own signature, that a missing collateral is an anticipated, handled case, not an oversight.

Data classes and domain modeling

Modeling a rich business domain (a chess tournament's rules, a risk calculation's invariants) requires types that carry meaning, not just data structures. Kotlin's data classes, with structural equality and immutable copying (copy()) built in, make natural what other languages require you to implement by hand. On kotlin-chess-tournament, every tournament state is an immutable object: impossible to accidentally mutate from elsewhere in the code, which eliminates an entire category of state synchronization bugs.

data class TournamentState(
    val round: Int,
    val standings: List<PlayerScore>,
)

val nextRound = currentState.copy(round = currentState.round + 1)
// currentState stays unchanged: copy() produces a new object,
// currentState and nextRound share no mutable reference.

Without this guarantee, you'd have to hand-write equality, hashcode, and immutable copying logic, with the risk of forgetting a field every time the class evolves.

AspectJavaKotlin
Null valuesOpt-in Optional, NPE always possibleNullable/non-nullable baked into the type system
Data classesManual boilerplate or Lombokdata class: equals/hashCode/copy() generated
Finite cases (sum types)switch on enum, not exhaustive by defaultsealed class + when, exhaustiveness checked at compile time
Interop with existing JavaDirect, no wrapper or FFI

Java interoperability, a bridge rather than a wall

Working in a banking environment, where the Java ecosystem remains dominant, Kotlin's ability to interface with existing Java code without friction isn't a technical detail: it's what makes the language adoptable incrementally, module by module, rather than through a risky rewrite. That compatibility literally changes the decision to introduce Kotlin on an engagement: the entry cost is close to zero.

What I'm not claiming

Kotlin isn't magic and doesn't replace architectural discipline: on counterparty-risk-lab, I apply the same hexagonal discipline in TypeScript. Language choice never excuses design discipline. But at equal discipline, Kotlin eliminates by construction categories of bugs that other languages leave to developer vigilance, and that structural elimination, more than the syntax itself, is what explains why I keep coming back to it for my JVM projects.