skip to content

Annotations

Declaring your own annotations, controlling where they may appear and how long they survive, aiming them at the right emitted element, and reading them back. Framework-adjacent work depends on all four.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

explore

questions

30

What types are allowed as parameters of a Kotlin annotation, and which types are NOT allowed?

level: juniorimportance: must knowfreq 60%

answer

  1. Compile-time constants only
  2. Primitives, String, enum, KClass, annotation, arrays
  3. No List/Map, no nullable, no lambdas
  4. Class literal = String::class -> Class in bytecode
  5. Arrays one-dimensional, [..] literal allowed inside annotations

basics

~10 s

Annotation parameters can only be simple compile-time values: numbers, Boolean, Char, String, enums, a class reference, another annotation, and arrays of those. You cannot use arbitrary objects like a List or a date.

solid answer

~40 s

Kotlin restricts annotation parameter types to values known at compile time: the Java primitives and their Kotlin equivalents (Int, Long, Short, Byte, Double, Float, Boolean, Char), String, enum entries, a KClass literal (e.g. String::class, mapped to java.lang.Class in bytecode), other annotation types, and one-dimensional arrays of any of the above. You CANNOT use nullable types, arbitrary class instances, collections like List or Map, functions/lambdas, or generic type parameters. Every argument must be a compile-time constant or expression built only from those constants — so values must come from literals, const val, or enum entries, never a regular val or a function call. This restriction exists because annotations are baked into the .class file metadata and read by reflection or annotation processors before any runtime objects exist.

code

kotlin · 8 lines
kotlin
annotation class Route(
    val path: String,
    val methods: Array<String> = ["GET"],
    val handler: kotlin.reflect.KClass<*>
)

@Route(path = "/users", methods = ["GET", "POST"], handler = String::class)
class UsersController

go deeper

for a junior

Lists the allowed types (primitives, String, enum, KClass, annotation, arrays) and knows collections/nullable are not allowed.

for a middle

Explains the compile-time-constant rule that unifies all the restrictions and uses Array over List.

for a senior

Connects the restriction to class-file metadata storage and how KClass maps to java.lang.Class in bytecode.

for a principal

Reasons about cross-tooling implications (annotation processors / reflection reading constants pre-runtime) when designing annotation-driven APIs.

## What an annotation parameter is An **annotation** is metadata attached to code (`@Deprecated`, `@JvmStatic`, your own `@Audited`). Its **parameters** are the values you pass: `@Deprecated("use foo", level = DeprecationLevel.ERROR)`. Because annotations are stored in the compiled `.class` file and read *statically* (by reflection or by annotation processors at build time), their arguments must be **compile-time constants** — there is no live object graph when they are read. ## Allowed parameter types Kotlin permits exactly these: - **Primitive-backed types**: `Int`, `Long`, `Short`, `Byte`, `Double`, `Float`, `Boolean`, `Char`. - **`String`**. - **Enum classes** — pass an entry, e.g. `DeprecationLevel.ERROR`. - **Class references via `KClass`** — declared as `val type: KClass<*>`, passed as `String::class`. In JVM bytecode this becomes `java.lang.Class`. - **Other annotations** — an annotation type can have a parameter that is itself an annotation, e.g. `@Validated(rule = Rule(min = 1))`. - **Arrays** of any of the above — declared `val tags: Array<String>`, passed `["a", "b"]` (the `[...]` literal is allowed *inside annotations only*) or `arrayOf("a", "b")`. ## NOT allowed - **Nullable types** (`String?`) — annotation params cannot be nullable. - **Arbitrary objects / collections** — `List`, `Map`, `Set`, a data class instance, `LocalDate`, etc. - **Function / lambda types**. - **Generic type parameters** as the parameter type. - **Non-constant expressions** — a regular `val x = computeName()` cannot be passed; only literals, `const val`, and enum entries qualify. ```kotlin enum class Level { LOW, HIGH } annotation class Inner(val n: Int) annotation class Audited( val name: String, // String OK val level: Level, // enum OK val target: kotlin.reflect.KClass<*>, // class literal OK val nested: Inner, // another annotation OK val tags: Array<String>, // array OK val flag: Boolean = false // primitive with default OK ) const val DEFAULT_NAME = "svc" // const val -> usable in annotations @Audited( name = DEFAULT_NAME, level = Level.HIGH, target = String::class, nested = Inner(7), tags = ["x", "y"] ) class Service ``` ## Why the restriction The values are written into class-file metadata. A compiler/processor reading them has no JVM runtime objects to hand, so each value must be reducible to a constant at compile time. That is the single rule behind every item in the allow/deny lists above.

  • Can an annotation parameter be of type List<String>?
    No. Collections are not allowed; use Array<String> instead, which is the supported container type.
  • Can a parameter be nullable, e.g. val name: String??
    No. Annotation parameters cannot be nullable types.

Like writing on a luggage tag: only short fixed text and stamps fit — you can't tie a whole suitcase to the tag.

saying these in an interview costs you the question

  • Claiming you can pass a List or Map as an annotation parameter
  • Saying any object can be used because annotations are 'just classes'
  • Thinking a regular val (not const) can be passed
  • Confusing Array<String> with List<String> for annotation params
  • Believing nullable types are allowed

context

open as a page

How do you declare your own annotation in Kotlin, and how do you apply it to a function?

level: juniorimportance: must knowfreq 60%

basics

~10 s

Write a class with the annotation class keywords, for example annotation class Loggable. Then put @Loggable in front of a function, property, or class to mark it.

open as a page

What are meta-annotations in Kotlin, and which built-in ones can you place on an annotation declaration?

level: juniorimportance: must knowfreq 55%

basics

~10 s

Meta-annotations are annotations you put on another annotation's declaration to describe how it behaves. The built-in ones are @Retention, @Target, @MustBeDocumented, and @Repeatable.

open as a page

In Kotlin, how do you read an annotation off a class at runtime, and what must be true of that annotation for it to be visible?

level: juniorimportance: must knowfreq 55%

basics

~10 s

Use Kotlin reflection: get the class with ::class, look at its annotations list, or call findAnnotation. The annotation only shows up at runtime if it was declared with RUNTIME retention.

open as a page

What do the @Retention and @Target meta-annotations control when you declare your own Kotlin annotation?

level: juniorimportance: must knowfreq 55%

basics

~10 s

@Retention says how long the annotation is kept: source only, in the class file, or readable at runtime. @Target says where you can put it: on classes, functions, properties, parameters, and so on.

open as a page

What is a use-site target on a Kotlin annotation (e.g. @get:, @field:, @param:), and why is it needed when annotating a property?

level: juniorimportance: must knowfreq 70%

basics

~20 s

One Kotlin property can become several Java pieces: a field, a getter, a setter, a constructor parameter. A use-site target like @get: or @field: tells the compiler which of those pieces the annotation should land on.

open as a page

What expressions can supply an annotation argument or its default value? Explain the role of const val.

level: middleimportance: must knowfreq 50%

basics

~10 s

Only compile-time constants: literals, enum entries, and values declared with const val. A normal val or a function result won't compile because the value must be known when the code is compiled.

open as a page

How does an annotation class differ from a normal class in Kotlin in terms of body, instantiation, and visibility?

level: middleimportance: must knowfreq 45%

basics

~10 s

An annotation class has no body, you don't create it with new/constructor calls in your code, and it's public by default. A normal class has a body, is instantiated, and you choose its visibility.

open as a page

Explain the difference between SOURCE, BINARY, and RUNTIME retention, and which one you must use to read an annotation via reflection.

level: middleimportance: must knowfreq 50%

basics

~10 s

SOURCE is dropped after compiling, BINARY stays in the class file but reflection can't see it, RUNTIME stays and reflection can read it. You need RUNTIME for reflection.

open as a page

When you annotate a Kotlin property without a use-site target, what is the default resolution order the compiler uses to choose the target?

level: middleimportance: must knowfreq 55%

basics

~10 s

Kotlin tries targets in a fixed order and uses the first one the annotation is allowed on: the constructor parameter, then the property, then the field. Getters and setters are never chosen automatically.

open as a page

How do AnnotationRetention values (SOURCE, BINARY, RUNTIME) interact with reading meta-configured annotations, and what is Kotlin's default?

level: seniorimportance: must knowfreq 40%

basics

~10 s

@Retention sets how long an annotation survives: SOURCE (compile-time only), BINARY (in the class file), or RUNTIME (readable by reflection). Kotlin defaults to RUNTIME.

open as a page

How do you pass a class as an annotation argument in Kotlin, and what type does the parameter have? How does it appear in JVM bytecode?

level: middleimportance: should knowfreq 45%

basics

~10 s

You declare the parameter as KClass and pass a class with the ::class syntax, like MyClass::class. In Java bytecode it becomes a java.lang.Class reference, so Java frameworks can read it too.

open as a page

What kinds of declarations can you apply a self-declared annotation like @Loggable to, and what does applying it actually do at runtime by default?

level: middleimportance: should knowfreq 35%

basics

~10 s

You can put @Loggable on classes, functions, properties, parameters, and more. By itself it does nothing at runtime — it just marks the code so something else can read and react to the mark.

open as a page

What does @Repeatable do, and how do you declare and apply a repeatable annotation in Kotlin?

level: middleimportance: should knowfreq 45%

basics

~10 s

@Repeatable lets you put the same annotation on one declaration more than once. You mark the annotation class with @Repeatable, then apply it multiple times.

open as a page

How do you read RUNTIME annotations from functions, properties, and parameters using KClass members and KCallable in Kotlin reflection?

level: middleimportance: should knowfreq 45%

basics

~10 s

Walk the class members: KClass exposes declaredFunctions and declaredMemberProperties, each a KCallable with its own .annotations. Function parameters are KParameter objects that also carry .annotations.

open as a page

Once you have an annotation instance from reflection, how do you read its parameter values, and how do you handle finding annotations that are themselves meta-annotated?

level: middleimportance: should knowfreq 35%

basics

~10 s

A reflected annotation is a real object: just read its properties like anno.path. To find an annotation that carries another annotation, look at each annotation's own annotationClass.annotations.

open as a page

How does @Target restrict where an annotation can be applied, and what happens if you apply it to a disallowed element? Name several AnnotationTarget values.

level: middleimportance: should knowfreq 42%

basics

~10 s

@Target lists the kinds of code elements (class, function, property, parameter, etc.) the annotation is allowed on. If you put it somewhere not in that list, the code won't compile.

open as a page

You annotate a Kotlin data class property with a Jackson/JPA annotation and it seems ignored at runtime. How do use-site targets explain and fix this?

level: middleimportance: should knowfreq 60%

basics

~20 s

The annotation probably landed on the wrong JVM element. The library reads, say, the getter, but Kotlin put your annotation on the field or the constructor parameter. Adding the right prefix like @get: or @field: makes it visible.

open as a page

How do array-typed annotation parameters work in Kotlin, including vararg and the array literal syntax? How do they differ from Java's interop expectations?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Annotations can take arrays. You declare Array<String> and pass values with [..] or arrayOf(...). A vararg parameter lets callers pass items without wrapping them, and it shows up as an array to Java.

open as a page

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%

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.

open as a page

How does @Target govern annotation placement, what happens if you omit it, and how does it interact with @Repeatable for a multi-target annotation?

level: seniorimportance: should knowfreq 35%

basics

~10 s

@Target lists the kinds of code an annotation may be put on. Omit it and the annotation is allowed on most targets. Using it on a disallowed target is a compile error.

open as a page

What are the practical differences and pitfalls when reading annotations via Kotlin reflection (KClass) versus Java reflection (Class), and when would you choose each?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Kotlin reflection (KClass) understands Kotlin concepts like properties and use-site targets; Java reflection (Class) sees the compiled view of fields, getters, and methods. Kotlin reflection needs kotlin-reflect; Java reflection is built in and faster to start.

open as a page

You are designing a validation annotation that a runtime framework will read on data-class properties via reflection. What @Retention and @Target would you choose, and why does the property/field/getter distinction matter?

level: seniorimportance: should knowfreq 33%

basics

~20 s

Use RUNTIME retention so reflection can read it, and target the property (and maybe field/parameter). Because a property, its field, and its getter are separate places, you must make sure the annotation lands where your framework actually looks.

open as a page

Explain the @delegate: and @receiver: use-site targets. When would you reach for each, and how do they differ from @field:?

level: seniorimportance: should knowfreq 30%

basics

~10 s

@delegate: annotates the hidden field that stores a delegate created with by. @receiver: annotates the receiver of an extension function or property. Both target elements that @field: cannot reach.

open as a page

What is @MustBeDocumented for, and how does it differ from @Retention and @Target in effect?

level: middleimportance: nice to knowfreq 25%

basics

~10 s

@MustBeDocumented marks an annotation as part of an element's public API so documentation tools include it. It changes docs only — not where the annotation can go or how long it's kept.

open as a page

You're designing a small set of marker annotations for your codebase. What principles guide declaring them, and what mistakes turn an annotation into a poor abstraction?

level: seniorimportance: nice to knowfreq 14%

basics

~20 s

Keep each annotation a small, clearly named public marker that means one thing, and make sure something actually reads it. Avoid using an annotation when a normal type, interface, or function would express the idea better.

open as a page

Design a robust runtime resolver that, given any Kotlin property, finds a @Serialized(name) annotation regardless of how it was placed, and discuss correctness and performance concerns.

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

Look for the annotation in several places for each property: the property itself, its getter, its backing field, and the matching constructor parameter. Take the first one found, and cache results so reflection runs once per class.

open as a page

How do Kotlin's retention defaults and AnnotationRetention map to Java's @Retention/RetentionPolicy, and what interop gotcha follows from the differing defaults?

level: seniorimportance: nice to knowfreq 20%

basics

~10 s

Kotlin's SOURCE/BINARY/RUNTIME line up with Java's SOURCE/CLASS/RUNTIME. The catch: Kotlin defaults to RUNTIME while Java defaults to CLASS (BINARY), so a Kotlin annotation with no @Retention is reflection-visible but a Java one isn't.

open as a page

Contrast @property: with @field:. What does each produce at the bytecode/metadata level, and what are the consequences for Java reflection and for annotations declared with only @Target(PROPERTY)?

level: seniorimportance: nice to knowfreq 22%

basics

~10 s

@field: puts the annotation on the real Java field, so Java reflection can read it. @property: stores it in Kotlin-only metadata, so plain Java reflection can't see it — only Kotlin reflection can.

open as a page

When designing an annotation API, how do the parameter-type restrictions shape your choices (e.g., referencing a strategy class, bounded class literals, avoiding non-constant config)? What patterns work around the limits?

level: principalimportance: nice to knowfreq 20%

basics

~20 s

Because annotations only hold fixed compile-time values, you reference behavior by class (KClass) and let the framework create it, keep config in const values, and push anything dynamic out of the annotation into runtime code.

open as a page