skip to content

Show how `T & Any` is used to override a Java generic method annotated `@NotNull`. Why is it needed there?

level: middleimportance: must knowfreq 30%

answer

  1. Java type variable -> Kotlin param with Any? bound -> bare T is nullable
  2. @NotNull return can't be expressed by bare T in override
  3. override fun f(x: T?): T & Any
  4. Removes need for platform type or unchecked cast
  5. Only needed at the interop/override boundary

basics

~20 s

When a Java method returns a generic value marked @NotNull, Kotlin couldn't express that the return is non-null in an override. T & Any lets the Kotlin override say 'this return is definitely not null'.

solid answer

~40 s

Consider a Java interface `interface Box<T> { @NotNull T getOrDefault(@Nullable T value); }`. The type variable `T` in Java carries no nullability of its own, but `@NotNull` annotates the *return*. When you override this in Kotlin, the Kotlin parameter `T` has a nullable upper bound (Java type variables map to `Any?`-bounded parameters), so you cannot write a bare `T` return that is non-null. Before Kotlin 1.7 the override could only use a platform/nullable type, losing the `@NotNull` guarantee. With definitely-non-null types you write `override fun getOrDefault(value: T?): T & Any`, faithfully reproducing the Java contract: argument nullable, return non-null. This keeps the Kotlin side null-safe and avoids spurious `!!` at call sites. The `& Any` only appears at the override boundary; pure-Kotlin code with proper `T : Any` bounds usually doesn't need it.

code

kotlin · 11 lines
kotlin
// Java: interface Transformer<T> { @NotNull T transform(@Nullable T input); }

class Identity<T> : Transformer<T> {
    override fun transform(input: T?): T & Any =
        input ?: error("null not allowed")
}

fun main() {
    val r: String = Identity<String>().transform("hi") // precise non-null, no !!
    println(r)
}

go deeper

for a junior

Recognizes that Java @NotNull generics are awkward to override and that T & Any helps.

for a middle

Writes a correct override with T? argument and T & Any return and explains the mapping.

for a senior

Articulates the platform-type/unchecked-cast alternatives it replaces and why null safety improves.

for a principal

Discusses how this interacts with JVM erasure/bridge methods and broader interop nullability annotation handling (JSpecify, strict mode).

## Why Java interop needs this Java has no built-in nullability in its type system; tools express it with **annotations** like `@NotNull` / `@Nullable` (from JetBrains, JSpecify, etc.). Kotlin reads these annotations to refine Java types when calling Java code. The tricky case is a **generic type variable** used as a return type with a nullability annotation: ```java // Java public interface Transformer<T> { @NotNull T transform(@Nullable T input); } ``` Kotlin maps a Java type parameter `T` to a parameter with a **nullable upper bound** (`Any?`), because Java could pass either `String` or `String?`. So in a Kotlin override a bare `T` is potentially nullable, and you cannot directly declare the **non-null** return that `@NotNull` promises. ## The fix: `T & Any` at the override ```kotlin class UpperCaser : Transformer<String> { override fun transform(input: String?): String & Any = input?.uppercase() ?: "" } // Generic override keeping T abstract: class Identity<T> : Transformer<T> { override fun transform(input: T?): T & Any = input ?: error("null not allowed") } ``` The override declares `input: T?` (matching `@Nullable`) and `T & Any` for the return (matching `@NotNull`). Now Kotlin callers get a precise non-null result type — no platform type, no `!!`. ## What the compiler enforces - The override's return must be assignable to a non-null value; returning a possibly-null expression without an elvis (`?:`) or check is a compile error, because the declared type is non-null. - The signature must remain a valid override of the Java method (the JVM erases generics, so the bridge methods line up). ## Without `T & Any` (the old pain) Pre-1.7 you'd either inherit a **platform type** (`T!`) — silently unsafe — or wrap with `@Suppress("UNCHECKED_CAST")` and an unchecked cast `as T`. Definitely-non-null types remove both hacks. ## Key APIs/keywords here - `@NotNull` / `@Nullable` — Java nullability annotations Kotlin honors. - platform type `T!` — Java type with unknown nullability. - `?:` (elvis) — supplies the non-null fallback so the body satisfies the `T & Any` return. - `override` — must keep a compatible signature with the Java declaration.

  • Why does the Kotlin override parameter become `T?` rather than `T` here?
    The Java parameter is `@Nullable T`, so Kotlin sees it as nullable. Matching it as `T?` reproduces the contract; the non-null guarantee belongs only to the `@NotNull` return.
  • If you ignored `T & Any` and returned a platform type, what's the risk?
    Platform types defer null checks to runtime. A null could flow into non-null Kotlin code and throw a `NullPointerException` later, far from its source — exactly what null safety aims to prevent.

saying these in an interview costs you the question

  • Claiming Kotlin overrides Java generics by writing `!!` everywhere instead of using the type
  • Not realizing Java type variables map to nullable-bounded Kotlin parameters
  • Thinking `@NotNull` on the return can be honored by a bare `T` return
  • Saying `T & Any` performs a runtime null check (it's the elvis/`error` in the body that does)

context