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?
answer
- KClass<*> parameter, pass Foo::class
- ::class in Kotlin vs Foo.class in Java
- Bytecode: KClass -> java.lang.Class
- Bound with KClass<out Base>
- Read back: KClass from Kotlin, Class from Java; .java to convert
basics
~10 sYou 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.
solid answer
~40 sTo accept a class literal, declare the parameter as `KClass<*>` (or a bounded `KClass<out Base>`), and callers pass `SomeClass::class`. Kotlin's `::class` on a type yields a `KClass`, whereas Java uses `SomeClass.class` to get a `Class`. At the bytecode level Kotlin maps the annotation's `KClass` parameter to `java.lang.Class`, so the same annotation is usable from Java and readable by JVM annotation processors and reflection. You can bound the accepted classes: `val validator: KClass<out Validator>` restricts arguments to subtypes of `Validator`. When you read the annotation reflectively from Kotlin you get back a `KClass`; from Java you get a `Class`. Note `Foo::class` gives the `KClass` for `Foo` itself, while `Foo::class.java` converts to the Java `Class` — but inside an annotation you pass the `::class` form directly.
code
kotlin · 11 linesimport kotlin.reflect.KClass
interface Validator
class EmailValidator : Validator
annotation class Validated(val by: KClass<out Validator>)
@Validated(by = EmailValidator::class)
class SignupForm
// EmailValidator::class -> KClass; in bytecode the param is java.lang.Classgo deeper
Knows to declare KClass and pass Foo::class for a class-valued annotation parameter.
Explains the ::class vs .java distinction inside annotations and that you get a class reference, not an instance.
Knows the KClass -> java.lang.Class bytecode mapping and uses bounded KClass<out Base> for compile-time safety and Java interop.
Designs annotation contracts considering KAPT/Java-reflection consumers reading the Class value, and how bounds shape the public API.
## The problem: referencing a type as data Sometimes an annotation must point at a *type*: `@Convert(converter = JsonConverter::class)`, `@Component(scope = RequestScope::class)`. You cannot store a live object, so you store a **class literal** — a compile-time reference to a class. ## Declaring the parameter Use `kotlin.reflect.KClass` as the parameter type: ```kotlin import kotlin.reflect.KClass annotation class Convert(val converter: KClass<*>) annotation class UseValidator(val validator: KClass<out Validator>) // bounded ``` - `KClass<*>` accepts any class. - `KClass<out Base>` restricts arguments to `Base` or its subtypes — a useful design constraint that the compiler enforces. ## Passing the argument: `::class` Callers use the **class reference operator** `::class`: ```kotlin @Convert(converter = MoneyConverter::class) class Account ``` - `MoneyConverter::class` evaluates to a `KClass<MoneyConverter>`. - Contrast with Java, where you would write `MoneyConverter.class` to get a `java.lang.Class`. - `MoneyConverter::class.java` converts a `KClass` to a Java `Class` in regular code — but **inside an annotation argument you pass the `::class` form**, not `.java`. ## Bytecode mapping Kotlin compiles a `KClass` annotation parameter to **`java.lang.Class`** in the `.class` file. Consequences: - The annotation is fully usable from Java: `@Convert(converter = MoneyConverter.class)`. - JVM annotation processors (KAPT/APT) and Java reflection see a normal `Class` value. - When you read it from Kotlin reflection, Kotlin gives you back a `KClass`; from Java reflection you get a `Class`. ## Reading it back ```kotlin val ann = Account::class.annotations.filterIsInstance<Convert>().first() val k: KClass<*> = ann.converter // KClass when read from Kotlin val j: Class<*> = ann.converter.java // convert to java.lang.Class if needed ``` ## Pitfalls - Writing `MoneyConverter::class.java` *as the annotation argument* is wrong — pass `MoneyConverter::class`. - Forgetting the bound: `KClass<*>` lets any class through; use `KClass<out Base>` when you want compile-time safety. - Expecting an *instance* — you only get the class reference, you must instantiate it yourself (or let a framework do so).
- Why declare the parameter as KClass<out Validator> instead of KClass<*>?It constrains accepted arguments to subtypes of Validator, so the compiler rejects unrelated classes at the call site.
- If a Java caller uses this annotation, what syntax do they use?They pass SomeClass.class because the parameter compiles to java.lang.Class.
Like writing a recipe that names the brand of pan to use, not handing over the actual pan.
saying these in an interview costs you the question
- Passing Foo::class.java as the annotation argument instead of Foo::class
- Declaring the parameter as Class<*> in Kotlin instead of KClass<*>
- Thinking you receive an instance rather than a class reference
- Not knowing KClass maps to java.lang.Class in bytecode
- Believing a string class name is required instead of a class literal