Kotlin Annotations: Declaring and Applying Metadata

Annotations provide a mechanism for attaching metadata to code. To declare an annotation, use the annotation modifier before a class.

annotation class CustomAnnotation

Additional characteristics of an annotation can be specified by annotating the annotation class itself with meta-annotations.

  • @Target defines the types of elements the annotation can be applied to (e.g., class, function, property).
  • @Retention determines if the annotation is stored in the compiled class file and accessible at runtime via reflection (default is true for both).
  • @Repeatable permits multiple uses of the same annotation on a single element.
  • @MustBeDocumented indicates the annotation is part of the public API and should be included in generated documentation.
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
        AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class CustomAnnotation

Apply annotations using the @ symbol.

@CustomAnnotation class Sample {
    @CustomAnnotation fun process(@CustomAnnotation param: Int): Int {
        return (@CustomAnnotation 1)
    }
}

To annotate a primary constructor, add the constructor keyword and place the annotation before it.

class Sample @Inject constructor(dep: MyDependency) { /*...*/ }

Property accessors can also be annotated.

class Sample {
    var data: MyDependency? = null
        @Inject set
}

Annotations can have constructors that accept parameters.

annotation class Marked(val reason: String)

@Marked("sample usage") class Demo {}

Permitted parameter types include:

  • Primitive types (e.g., Int, Long).
  • String.
  • Classses (e.g., Foo::class).
  • Enums.
  • Other annotations.
  • Arrays of the above types.

Annotation parameters cannot be nullable, as the JVM does not support storing null as an annotation attribute value.

When an annotation is used as a parameter for another annotation, its name is used without the @ prefix.

annotation class Substitute(val expr: String)

annotation class Outdated(
    val msg: String,
    val substitute: Substitute = Substitute("")
)

@Outdated("Use '===' instead", Substitute("this === other"))

To specify a class as an annotation parameter, use the Kotlin class reference (KClass). The Kotlin compiler converts this to a Java Class.

import kotlin.reflect.KClass

annotation class AnnotationExample(val arg1: KClass<*>, val arg2: KClass<out Any>)

@AnnotationExample(String::class, Int::class) class ExampleClass

Annotations can be applied to lambda expressions. They target the invoke() method of the lambda's body, which is useful for frameworks like Quasar.

annotation class Pausable

val lambdaFunc = @Pausable { Fiber.delay(10) }

When annotating a property or primary constructor parameter, multiple Java elements are generated. Use use-site target syntax to specify the exact placement.

class DemoClass(
    @field:AnnotationExample val first,      // Annotates Java field
    @get:AnnotationExample val second,       // Annotates Java getter
    @param:AnnotationExample val third       // Annotates constructor parameter
)

To annotate an entire file, place the annotation with the file target at the top level, before the package directive or imports.

@file:JvmName("DemoFile")
package com.example.demo

For multiple annotations on the same target, group them within brackets.

class TestClass {
    @set:[Inject VisibleForTesting]
    var helper: Helper
}

The complete list of supported use-site targets is:

  • file
  • property (invisible to Java)
  • field
  • get (property getter)
  • set (property setter)
  • receiver (extension function/property receiver)
  • param (constructor parameter)
  • setparam (property setter parameter)
  • delegate (field storing the delegate instance for a delegated property)

To annotate the receiver of an extension function, use this syntax:

fun @receiver:CustomAnnotation String.customExtension() { ... }

If no use-site target is specified, the target is chosen based on the @Target meta-annotation of the annotation being used. If multiple targets are applicable, the first from this list is selected: param, property, field.

Kotlin fully supports Java annotations.

import org.junit.Test
import org.junit.Assert.*
import org.junit.Rule
import org.junit.rules.*

class TestSuite {
    @get:Rule val testFolder = TemporaryFolder()

    @Test fun basicTest() {
        val file = testFolder.newFile()
        assertEquals(42, computeAnswer())
    }
}

Since Java annotations do not define parameter order, you must use named argument syntax in Kotlin.

// Java annotation
declaration
public @interface SampleAnn {
    int intValue();
    String textValue();
}
// Kotlin usage
@SampleAnn(intValue = 5, textValue = "test") class Example

For the special value parameter, the name can be omitted.

// Java
public @interface AnnWithSingleValue {
    String value();
}
// Kotlin
@AnnWithSingleValue("data") class Item

If the Java value parameter is an array, it becomes a vararg parameter in Kotlin.

// Java
public @interface AnnWithArrayParam {
    String[] value();
}
// Kotlin
@AnnWithArrayParam("a", "b", "c") class Container

For other array-type parameters, use array literal syntax (Kotlin 1.2+) or arrayOf().

// Java
public @interface AnnWithArrayMethod {
    String[] names();
}
// Kotlin 1.2+
@AnnWithArrayMethod(names = ["x", "y", "z"])
class Box

// Older Kotlin
@AnnWithArrayMethod(names = arrayOf("x", "y", "z"))
class OldBox

Annotation instance values are exposed as properties in Kotlin.

// Java
public @interface SimpleAnn {
    int value();
}
// Kotlin
fun examine(ann: SimpleAnn) {
    val num = ann.value
}

Tags: kotlin annotations metadata Java Interoperability programming

Posted on Wed, 07 Oct 2026 16:34:53 +0000 by visonardo