skip to content

Boundary Pitfalls

The places where the boundary leaks: nullability guarantees that do not hold, synthetic members Java stumbles over, mangled names, annotation targets, and erasure clashes. Interviewers use these to check you have actually shipped mixed-language code.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

explore

questions

25

What is a 'platform declaration clash' in Kotlin, and what commonly causes it?

level: juniorimportance: must knowfreq 55%

answer

  1. Distinct in Kotlin, identical on JVM
  2. Type erasure collapses List<Int>/List<String> to List
  3. Descriptor = name + erased params
  4. @JvmName renames the bytecode method
  5. Kotlin call site unchanged

basics

~10 s

It is a compile error that happens when two Kotlin functions look different in Kotlin but turn into the exact same method on the JVM, so the JVM cannot tell them apart.

solid answer

~40 s

A 'platform declaration clash' is a Kotlin compile-time error raised when two declarations are distinct in Kotlin source but compile to the same JVM method signature (same name + same erased parameter types + return type for some cases). The classic cause is generic type erasure: the JVM erases type arguments, so `fun f(x: List<Int>)` and `fun f(x: List<String>)` both become `f(List)`. Other causes include a property's generated getter colliding with a function, or two functions differing only by a return type the JVM does not consider part of the descriptor. The fix is to make one JVM signature distinct, usually by renaming the bytecode method via the `@JvmName("...")` annotation, which keeps the Kotlin-side names while giving each a unique JVM name.

code

kotlin · 9 lines
kotlin
// Compile error: platform declaration clash
fun sum(xs: List<Int>): Int = xs.sum()
fun sum(xs: List<Long>): Long = xs.sum()

// Fixed
@JvmName("sumInts")
fun sumI(xs: List<Int>): Int = xs.sum()
@JvmName("sumLongs")
fun sumL(xs: List<Long>): Long = xs.sum()

go deeper

for a junior

Can recognize the error message and name type erasure as the cause.

for a middle

Applies @JvmName correctly and explains it only affects the JVM symbol, not Kotlin calls.

for a senior

Explains JVM descriptors precisely and enumerates non-generic triggers (getter/function collisions).

for a principal

Frames API design to avoid the clash entirely and weighs @JvmName vs distinct names for Java consumers.

## What the error means Kotlin compiles to JVM bytecode. On the JVM every method is identified by its **descriptor**: its name plus the (erased) types of its parameters. If two Kotlin declarations end up with the **same JVM descriptor**, the compiler reports a *platform declaration clash* — because the resulting `.class` file would contain two methods the JVM treats as identical, which is illegal. ## Why generics cause it: type erasure The JVM has no generics at runtime. Type arguments like `<Int>` or `<String>` are **erased** — `List<Int>` and `List<String>` both become the raw type `List` (technically `java.util.List`). So these two Kotlin functions: ```kotlin fun process(items: List<Int>) { } fun process(items: List<String>) { } ``` are perfectly valid and distinct in Kotlin, but both compile to the JVM method `process(java.util.List)`. Same descriptor -> clash. ## Other common triggers - A property generating a getter that collides with a same-named function (e.g. `val x` produces `getX()` colliding with `fun getX()`). - Functions differing only in return type after erasure. - An `Int`/`Long` etc. that boxes the same way is *not* a cause — primitives keep distinct descriptors. ## The fix: @JvmName The `@JvmName("...")` annotation renames the **generated bytecode method** without changing how you call it from Kotlin: ```kotlin @JvmName("processInts") fun process(items: List<Int>) { } @JvmName("processStrings") fun process(items: List<String>) { } ``` Now the JVM sees `processInts(List)` and `processStrings(List)` — distinct descriptors, no clash. From Kotlin you still call `process(...)`; from Java you call `processInts(...)` / `processStrings(...)`. ## Key terms - **Descriptor**: the JVM's identity for a method (name + param types). - **Erasure**: dropping generic type arguments at compile time. - **@JvmName**: renames the emitted JVM symbol for a function/getter/setter/file class.

  • Does adding @JvmName change how Kotlin code calls the function?
    No. @JvmName only renames the emitted JVM method; Kotlin call sites resolve by the Kotlin name and are unaffected. Only Java callers see the new name.

Two contacts with different nicknames but the same phone number — your address book (Kotlin) shows two people, but the phone (JVM) can only dial one.

saying these in an interview costs you the question

  • Claiming the JVM keeps generic type arguments at runtime
  • Thinking the two functions are ambiguous to Kotlin's overload resolution (they are not)
  • Suggesting you must rename the Kotlin-visible function name to fix it
  • Confusing this with an unresolved-reference or overload-resolution error

context

open as a page

What is a Kotlin 'platform type' and why can a value coming from Java code throw a NullPointerException even though your Kotlin code looks fully null-safe?

level: juniorimportance: must knowfreq 75%

basics

~20 s

When Java returns a value, Kotlin doesn't know if it can be null, so it trusts you. If you treat it as non-null and it actually is null, you only crash later when you use it.

open as a page

When a Java developer calls into a Kotlin class that has a companion object, what is the 'Companion' field they see in autocomplete, and how should they reach the companion's members from Java?

level: juniorimportance: must knowfreq 58%

basics

~10 s

Kotlin turns a companion object into a static field named Companion plus a nested class. From Java you call MyClass.Companion.method(), unless the member is annotated @JvmStatic, which lets you call MyClass.method() directly.

open as a page

In Kotlin, what is a use-site annotation target, and why might you write `@field:NotNull` instead of just `@NotNull` on a property?

level: juniorimportance: must knowfreq 55%

basics

~10 s

A single Kotlin property turns into several Java elements (field, getter, setter, constructor parameter). A use-site target like @field: tells the compiler which of those elements the annotation should land on.

open as a page

Show how to resolve a generic-erasure platform clash for two overloads using @JvmName, and explain what each annotation changes.

level: middleimportance: must knowfreq 50%

basics

~10 s

Put @JvmName with a different name on at least one of the clashing functions. That gives each a unique method name in the compiled code, so the JVM no longer sees a duplicate.

open as a page

At a Java boundary, what is the difference between guarding a platform value with requireNotNull/checkNotNull versus using the !! operator, and when should each be used?

level: middleimportance: must knowfreq 55%

basics

~10 s

All of them crash if the value is null, but requireNotNull/checkNotNull let you attach a clear message and throw a meaningful exception, while !! throws a bare NullPointerException with no explanation.

open as a page

A teammate annotates a JPA/Jackson entity field with a bare constraint and it is silently ignored at runtime. Explain the cause and how `@field:` fixes it.

level: middleimportance: must knowfreq 50%

basics

~10 s

The bare annotation landed on the constructor parameter, but the framework reads the backing field by reflection. Since the field has no annotation, nothing happens. Prefixing with @field: puts it where the framework looks.

open as a page

A Java class in the same module-graph tries to call a Kotlin function declared `internal fun computeScore()`, but autocomplete shows a weird name like `computeScore$app_main`. What is happening and why?

level: juniorimportance: should knowfreq 45%

basics

~20 s

Kotlin's internal means 'visible only inside this module'. To keep Java from calling it casually, Kotlin renames it in the compiled bytecode by adding a suffix, so the Java-visible name no longer matches the Kotlin name.

open as a page

A Kotlin property's generated getter clashes with a function. Explain the mechanism and how to resolve it.

level: middleimportance: should knowfreq 35%

basics

~20 s

A Kotlin property quietly generates a getter method like getX(). If you also write a function named getX(), both produce the same JVM method, so they clash. Rename one with @JvmName, or rename the function.

open as a page

You want a `internal` Kotlin helper and a value-class-parameter function both callable from a Java module with clean names. How do you remove the mangling, and what risk does that introduce?

level: middleimportance: should knowfreq 30%

basics

~20 s

Add @JvmName("cleanName") to the function. That overrides the compiler's mangled name with the one you pick, so Java sees a normal method. The risk is you might now collide with another method that has the same JVM signature.

open as a page

You expose `fun pay(amount: Money)` where `@JvmInline value class Money(val cents: Long)`. A teammate says Java can't find `pay`. Explain what the compiler did to the signature and why.

level: middleimportance: should knowfreq 40%

basics

~20 s

An inline value class disappears at runtime — it's replaced by its underlying type (here Long). To keep functions that take value classes apart from ones that take the raw type, Kotlin adds a hash suffix to the method name, so Java sees a mangled name instead of pay.

open as a page

How can you make Kotlin treat the return of a Java method as a proper non-null or nullable type instead of a platform type, and which annotations does Kotlin recognize?

level: middleimportance: should knowfreq 60%

basics

~10 s

Add nullability annotations like @Nullable or @NotNull on the Java side. Kotlin reads them and turns the value into a real nullable or non-null type, bringing back compile-time checks.

open as a page

A Kotlin function with default parameter values shows up in Java as both a normal method and a `name$default(...)` method with extra int and Object arguments. Explain what the `$default` synthetic method is and how a Java caller should use it.

level: middleimportance: should knowfreq 41%

basics

~20 s

Java has no default arguments, so Kotlin compiles a function with defaults into the real method plus a synthetic $default helper. The helper takes an extra bitmask telling it which arguments were omitted, then fills in the defaults. Java should normally use @JvmOverloads instead.

open as a page

What is the synthetic WhenMappings class that Kotlin sometimes generates, and which kind of `when` expression triggers it?

level: middleimportance: should knowfreq 34%

basics

~20 s

WhenMappings is a hidden helper class Kotlin creates when you switch over an enum (or sealed) in a when. It holds an int[] mapping each enum constant's ordinal to a small table index so the compiled switch is a fast tableswitch.

open as a page

How and why would you use `@get:JvmName` (and `@set:JvmName`) on a Kotlin property, and what problem does it solve at the Java boundary?

level: middleimportance: should knowfreq 35%

basics

~10 s

@get:JvmName("...") renames the getter method that Java sees, without changing the Kotlin property name. You use it to fix or customize the Java-facing accessor name, e.g. to avoid awkward or clashing generated names.

open as a page

Can default arguments or @JvmOverloads cause platform declaration clashes? How do you reason about the generated signatures?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Yes. @JvmOverloads expands a function with default values into several Java overloads. If those generated overloads, after erasure, match an existing function's signature, you get a clash. You fix it by renaming with @JvmName or removing the overlap.

open as a page

Your build sets a custom Gradle module name, and later a Java module that referenced a mangled `internal` method by its `$module` name fails to link. Explain the binary-compatibility hazard and how mangling factors in.

level: seniorimportance: should knowfreq 18%

basics

~20 s

The suffix on an internal method contains the module's name. If the module name changes, the mangled method name changes too, so anything that linked against the old name breaks. Internal members are not stable API — don't rely on their JVM names.

open as a page

Beyond simple return values, where else do platform-type null holes hide — for example in Java collections, generics, and arrays — and how do they bite?

level: seniorimportance: should knowfreq 45%

basics

~20 s

A null can also hide inside a Java list, map, or array element, or in a generic type argument. Kotlin trusts the whole container as non-null, so the crash pops up only when you read the null element.

open as a page

What are the synthetic `access$` methods Kotlin generates, why do they appear, and what changed so that they are needed less often?

level: seniorimportance: should knowfreq 24%

basics

~20 s

When an inner/nested or companion context needs a private member of an outer class, Kotlin used to generate hidden access$ bridge methods because separate JVM classes can't touch each other's privates. Newer bytecode targets use nestmates instead, so fewer of these appear.

open as a page

Explain Kotlin's default use-site target resolution. Given an annotation valid on multiple targets, how does the compiler choose, and how do `@param:`, `@property:`, and `@field:` differ in visibility to Java reflection?

level: seniorimportance: should knowfreq 30%

basics

~20 s

If you omit the target, the compiler picks the first applicable one from param, then property, then field. @param: and @field: are real Java elements reflection can see; @property: is Kotlin-only metadata that plain Java can't read.

open as a page

How does erasure of inline value classes interact with signature clashes, and when does @JvmName help versus the automatic name mangling Kotlin already applies?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

An inline value class is erased to its underlying type on the JVM, so two functions taking different value classes over the same base type would clash. Kotlin auto-mangles their names to avoid that; @JvmName is for the generic-collection case it can't auto-fix.

open as a page

Kotlin enums (like Java enums) carry a synthetic `$VALUES` field and `values()`/`valueOf()` methods. Explain what `$VALUES` is, how `entries` differs from `values()`, and why this matters at the Java boundary and in reflection.

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

$VALUES is a hidden static array holding every enum constant once. values() returns a defensive copy of it each call; valueOf() looks a name up in it. Kotlin's newer entries property returns a cached immutable list instead, avoiding the per-call copy.

open as a page

What do the `@receiver:` and `@delegate:` use-site targets annotate, and when would you reach for each at the Kotlin/Java or framework boundary?

level: seniorimportance: nice to knowfreq 14%

basics

~10 s

@receiver: puts an annotation on the receiver parameter of an extension function or property (the hidden 'this' parameter). @delegate: puts it on the synthetic field that stores a delegated property's delegate instance.

open as a page

You're designing a Kotlin library consumed by both Kotlin and Java teams, using `@JvmInline value class` domain types and `internal` helpers. How do you architect the public surface so mangling never bites Java consumers?

level: principalimportance: nice to knowfreq 12%

basics

~20 s

Keep the value classes for Kotlin callers, but give Java a separate, plain-typed public API. Don't let Java touch internal members or value-class-typed methods directly; expose facade functions that take primitive/boxed types and have stable names.

open as a page

When a Kotlin class overrides a Java method whose parameter or return is a platform type, how do you choose the signature, and what team-level strategy hardens an entire Java boundary against null holes?

level: principalimportance: nice to knowfreq 25%

basics

~20 s

When you override a Java method, Kotlin lets you pick nullable or non-null for the platform-typed parts. Choose nullable if callers might pass null. As a team, annotate Java, wrap third-party APIs, and turn unknown values nullable at the edge.

open as a page