Annotations and Reflection

This section documents Kotlin 2.4.x on the JVM, as published at kotlinlang.org, which is the reference these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against kotlinlang.org before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Annotations and reflection in Kotlin build directly on the JVM’s own annotation and reflection facilities, with a Kotlin-specific reflection API (kotlin.reflect) layered on top for Kotlin-only concepts (properties, nullability, data classes).

Declaring an Annotation

An annotation class carries no body of its own — only constructor parameters, which become the annotation’s attributes:

annotation class Author(val name: String)

@Author(name = "Ada Lovelace")
class Calculator

Meta-Annotations: @Target and @Retention

Two built-in meta-annotations control where a custom annotation may be applied and how long it survives:

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class Experimental(val reason: String)

class Api {
    @Experimental("subject to change before 1.0")
    fun betaFeature() { /* ... */ }
}

@Target restricts which declarations (classes, functions, properties, parameters, …​) the annotation may decorate; @Retention mirrors Java’s RetentionPolicy — SOURCE (compiler-only), BINARY (kept in the .class file but invisible to reflection), or RUNTIME (readable via reflection, the default).

Using Built-In Annotations

Several annotations recur throughout ordinary Kotlin code, most already seen elsewhere in this section:

@Deprecated("use newMethod() instead", ReplaceWith("newMethod()"))
fun oldMethod() { }

@JvmStatic                              // exposes a companion-object function as a real "static" for Java callers
companion object {
    @JvmStatic
    fun create(): Widget = Widget()
}

@Suppress("UNCHECKED_CAST")
fun <T> unsafeCast(value: Any): T = value as T

@JvmStatic, @JvmOverloads, and @JvmName are a family of Java-interop annotations specific to Kotlin: because Kotlin has no static keyword and supports default arguments (see Functions), Java callers would otherwise not see a plain static method or a set of overloads for each default-argument combination — these annotations ask the compiler to generate that extra, Java-friendly surface.

Basic kotlin.reflect

::class produces a KClass<T> — Kotlin’s own reflection handle, richer than Java’s Class<T> for Kotlin-specific constructs (nullable types, properties as first-class members, primary-constructor parameters):

class User(val name: String, val age: Int)

val kClass = User::class                 // KClass<User>
println(kClass.simpleName)               // "User"

val javaClass = User::class.java          // the underlying java.lang.Class<User>, for Java-facing APIs

val instance = User("Ada", 30)
for (prop in User::class.members) {
    println("${prop.name}")               // reflects over Kotlin members, including properties
}

::class.java is the bridge back to java.lang.Class whenever a Java API (a JSON library, a dependency injection framework) expects one — KClass itself is not a drop-in replacement for Class at every call site.

See Also

  • Functions — default arguments, the feature @JvmOverloads exists to bridge to Java.

  • Testing — how @Test/@ExtendWith are themselves discovered via runtime-retained annotations and reflection.

  • Kotlin and the JVM — the Java-interop annotations in the wider context of calling Kotlin from Java.