Skip to content

CONCEPT Cited by 1 source

Interlanguage null safety

Definition

Interlanguage null safety is the set of mechanisms — annotations, static analysers, and runtime checks — that keep null-related invariants intact when control crosses a language boundary (e.g. Java ↔ Kotlin). The load-bearing observation is that static-analysis-only null safety is 100% effective only at 100% coverage, which is infeasible on any real codebase; every boundary crossing to unannotated or third-party code is a potential NPE entry point.

Why it matters

Two languages in the same binary don't share a null-safety story unless the boundary enforces one. Three failure modes appear at interlanguage edges:

  1. Annotation absence. A Java method that forgot @Nullable passes null to a Kotlin non-nullable parameter — translation-inferred nullability was wrong because the source annotation was wrong.
  2. Coverage gap. Even 99%-@Nullsafe Java is not 100%: the remaining 1% dependents can silently inject null into a "safe" method. NPE risk is unbounded in the non-coverage.
  3. Static analyser divergence. Kotlin's compiler is stricter than Java's Nullsafe in certain cases (e.g. class-level properties potentially nulled by another thread), so translations legitimately need more !! than the source had.

Kotlin's distinctive contribution: runtime checkNotNull

The biggest difference between null-safe Java and Kotlin is that Kotlin inserts implicit checkNotNull calls at the interlanguage boundary in bytecode, transforming null safety from a static-only property into a runtime-enforced one:

"The biggest difference between null-safe Java and Kotlin is the presence of runtime validation in Kotlin bytecode at the interlanguage boundary. This validation is invisible but powerful because it allows developers to trust the stated nullability annotations in any code they're modifying or calling." (Meta, 2024-12-18.)

After translation, a Kotlin fun doThing(s: String) carries an "invisible checkNotNull(s) at the start of doThing's body, so we can safely add a dereference to s, because if s were nullable, this code would already be crashing." This operationalises nullability: the runtime fails fast at the boundary, so Kotlin code downstream can trust the type. Java's static-analysis-only tools (@Nullsafe, NullAway) don't have this property — a lie in an annotation can silently propagate through a whole Java call chain.

The translation risk

When Meta translates Java doThing(String s) to Kotlin doThing(s: String), someone has to take the initial risk of inserting the implicit nonnull assertion. If the Java s is actually sometimes null (because a dependent forgot @Nullable), translation creates an NPE. Meta's defences:

  • Err toward nullable on inference. When context clues are ambiguous, the Kotlinator picks String? not String.
  • Scrutinise new !!. Reviewers pay extra attention to !! inserted outside pre-existing dereferences — someMethodDefinedInJava(foo!!) is particularly suspicious because it combines a potentially-missing @Nullable with an assertion that turns absence into a crash.
  • Runtime nullability telemetry. The long tail — unannotated parameters + return types at interop boundaries — is closed with a javac plugin that collects actual null flows in production and feeds codemods that backfill @Nullable. See [[concepts/runtime-nullability- telemetry]].

Canonical wiki reference

sources/2024-12-18-meta-translating-10m-lines-of-java-to-kotlin — the 2024-12-18 Meta post is the wiki's canonical statement of interlanguage null safety as the load-bearing concept under any Java-to-Kotlin migration of scale.

Last updated · 517 distilled / 1,221 read