skip to content

What does an `annotation class` compile to on the JVM, and why does its 'implicitly public, no-body' shape matter for interop and tooling?

level: seniorimportance: should knowfreq 22%

answer

  1. compiles to Java @interface / java.lang.annotation.Annotation
  2. public default = visible to tools and other modules
  3. no body = matches JVM annotation model (no behavior allowed)
  4. Kotlin↔Java annotation interop both directions
  5. KClass/kotlin.reflect on Kotlin side, Class.getAnnotation on Java side

basics

~20 s

On the JVM a Kotlin annotation class becomes a Java annotation type (an interface-like @interface). Its public, bodiless shape means Java and tools can see and use it the same way they use Java annotations.

solid answer

~40 s

A Kotlin `annotation class` compiles on the JVM to a **Java annotation type** — effectively a `@interface` (a special interface extending `java.lang.annotation.Annotation`). Because Kotlin makes it **implicitly public** and **bodiless**, the result is a clean, public annotation type that Java code and JVM tooling (reflection, annotation processors, frameworks) consume identically to a native Java annotation. This interop is why the shape is constrained: a metadata-only public type maps cleanly onto the JVM's annotation model. You can apply a Kotlin-declared annotation in Java and vice versa. Reflectively the Kotlin side exposes it via `KClass` / `kotlin.reflect`, while the Java side sees it through `java.lang.annotation`. The no-body rule keeps it from carrying behavior the JVM annotation model can't represent; the public default ensures consumers across module boundaries can reference it.

go deeper

for a junior

May only know it 'works with Java somehow'; not expected to detail the compiled form.

for a middle

Knows it interops with Java and is public/bodiless, with a rough sense of why.

for a senior

Explains it compiles to a JVM annotation type (@interface extending java.lang.annotation.Annotation) and ties public/no-body to that mapping and tooling.

for a principal

Reasons about cross-language metadata design, module visibility implications, and how this shape guarantees stable JVM/tooling interop.

## What it becomes on the JVM When you write: ```kotlin annotation class Loggable ``` the Kotlin compiler emits, for the JVM target, a **Java annotation type**: an `@interface Loggable` that implicitly extends `java.lang.annotation.Annotation`. This is the same construct Java's `public @interface Loggable {}` produces. That is why a Kotlin-declared annotation can be applied to **Java** code and a Java-declared annotation can be applied to **Kotlin** code seamlessly. ## Why 'implicitly public' matters Kotlin makes annotation classes **public by default**. On the JVM, annotation types you intend tools and other modules to read must be visible to them. A public type: - can be referenced from any module/package that depends on it, - is discoverable by reflection and annotation processors, - matches Java's expectation that annotation types are broadly accessible. Making it anything narrower would defeat the purpose of a marker meant to be consumed elsewhere. ## Why 'no body' matters The JVM annotation model only supports **annotation members** (which map to constructor params in Kotlin) — not arbitrary methods, fields, or init logic. By forbidding a behavioral body, Kotlin guarantees the declaration maps cleanly onto that model. There is no place for runtime behavior in a JVM annotation, so the language doesn't let you write one. ## Reflection on both sides ```kotlin import kotlin.reflect.full.findAnnotation annotation class Loggable @Loggable class Service fun main() { val present = Service::class.findAnnotation<Loggable>() != null println(present) // true } ``` - **Kotlin reflection** (`kotlin-reflect`) exposes annotations via `KClass`, `KFunction`, etc. - **Java reflection** sees the same type through `Class.getAnnotation(...)` because it is a real Java annotation type. (Actually *reading* annotations reflectively is a sibling topic; the point here is that the compiled shape makes both reflection worlds work.) ## Practical interop notes - A Kotlin annotation used from Java: `@Loggable` works in `.java` files when the class is on the classpath and public. - Whether the annotation survives to runtime for reflection depends on retention (sibling topic), but the *type* itself is always a proper JVM annotation type. ## Takeaway The 'implicitly public, no-body' design is not arbitrary: it is exactly the shape required to compile to a standard JVM annotation type, giving Kotlin annotations first-class interop with Java and the entire JVM tooling ecosystem.

  • Can you apply a Kotlin-declared annotation in a Java source file?
    Yes. It compiles to a standard JVM annotation type, so Java sees it as a normal @interface and can apply it, given it is public and on the classpath.
  • Why can't an annotation class have member functions or init blocks?
    The JVM annotation model has no representation for behavior — only annotation members. Kotlin forbids a behavioral body so the type maps cleanly to that model.

saying these in an interview costs you the question

  • Saying a Kotlin annotation compiles to an ordinary class, not a JVM annotation type
  • Claiming you can give an annotation runtime methods or init logic
  • Not connecting 'public + no body' to JVM annotation interop
  • Asserting Java cannot use Kotlin-declared annotations
  • Confusing the compiled annotation type with how retention controls runtime visibility

context