Skip to content

Recommended Reading

Kotlin Annotations Explained with Code Examples

7 Minutes

Kotlin Annotations Explained with Code Examples

Fix Bugs Faster! Log Collection Made Easy

Get started

Metadata allows ordinary code to work. It defines and organizes our code, modifies its behavior and provides vital information for a compiler plugin or annotation process, so our apps are easier to use.

In Kotlin, annotations are a vital way to inject metadata into our code. Simply by adding the character @, we can attach metadata to code elements like classes, functions, properties or parameters. The Kotlin compiler plugin can handle eight built-in annotations: @Deprecated , @JvmOverloads , @JvmStatic , @Suppress , @Throws , @Retention , @Target, and @Repeatable. And it allows us to create customized annotations, too.

You’re already thinking about how to use them, right? Well don’t worry, we’re going to get right into the topic of Kotlin annotations and annotation processing, with basic code generation examples.

The article will be useful for:

  1. Kotlin developers who are new to annotations and want a working knowledge.
  2. Devs who are already familiar with Kotlin annotations but want to take their knowledge deeper.
  3. Those who have already used Java annotations and want to migrate to Kotlin (we’ll include a quick section on Java annotations further down, so you can see how they look when compared with Kotlin code).

First, a bit of a deeper dive into what we can use Kotlin annotations for

Kotlin annotations are used for various purposes, but here are some of the most common:

  • As a code document providing additional information about the code element, like whether the function is deprecated or not.
  • By a compiler to detect warnings or errors at compile time.
  • At runtime, annotation can be read to understand the behavior of generated code.
  • To add missing nullability annotations with the help of IntelliJ IDEA.

As you get used to Kotlin annotations, you’ll find lots more uses for the technology. But that’s enough preamble: this is a how-to guide, to let’s get to it!

Ok, let’s start with the built-in annotations in Kotlin

We’re going to look at five of the useful annotations here: Deprecated, Suppress, Target, Repeatable and Retention.

1. Deprecated

When a function, either built-in or custom, is no longer used in code, we declare that function using the @Deprecated annotation type. During compile time, the compiler understands that the function is deprecated and there is no need to compile.

Here’s an example of how to define the annotation parameter in Kotlin code:

@Deprecated("Use calculateNewValue() for better performance", ReplaceWith("calculateNewValue()"))
fun calculateValue(input: Int): Int {
    return input * 5
}

fun calculateNewValue(input: Int): Int {
    return input * 6 + 1
}

In the above example, the old function is declared deprecated. When we call this function in the project, the Integrated Development Environment (IDE) will suggest using a new function instead of the old one.

2. Suppress

Type casting is an important feature in Kotlin, but sometimes it is forgotten and replaced with Any. The @Suppress annotation plays an important role here, communicating the warning to the Kotlin compiler.

@Suppress("UNCHECKED_CAST")
fun uncheckedCastFunction() {
    val list = listOf<Any>("Anupam") as List<String>
}

Because we have declared the function as @Suppress("UNCHECKED_CAST"), the casting will not be allowed and the user can input any type parameters of value into the list. If we want to cast the value, then we have to replace Any with String and remove the @Suppress annotation.

3. Target

Sometimes we need to create a custom annotation for project-specific purposes. That way, we can define the metadata for the function or properties it would be used for. The @Target type annotation plays a major role here, and here’s the code to show you how this annotation attribute works.

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class TrackPerformanceOnFunction

class PerformanceCheck {
    @TrackPerformanceOnFunction
    fun complexOperation() {
        
    }
}

With this code, we have set a target for an annotation class that will be used only on a function of another class. Only those functions where the annotation processor is used will be targeted.

4. Repeatable

@Repeatable is an incredibly useful annotation process. How do we use it? Well the clue’s kind of in the name, ****but just to clarify:@Repeatable is allowed when we need to apply the same annotation process multiple times in a single element. Here’s a quick example:

@Repeatable
annotation class Car(val name: String)

@Car("Hyundai")
@Car("Kia")
@Car("Honda")
class CarVarient
import kotlin.reflect.full.findAnnotation

fun main() {
    val annotations = CarVarient::class.findAnnotations<Car>()
    annotations.forEach { println(it.name) }
}

Output: 

Hyundai
Kia
Honda

Btw, in Java code, we can define a @Repeatable annotation the same way. Here is the example of the Java annotation code.


import java.lang.annotation.*;

@Repeatable(Hints.class)
@Retention(RetentionPolicy.RUNTIME)
@interface Car {
    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@interface Hints {
    Car[] value();
}

@Car("Suzuki")
@Car("Tata")
public class CarVarient {
}

import java.lang.annotation.Annotation;

public class Main {
    public static void main(String[] args) {
        Annotation[] annotations = CarVarient.class.getAnnotationsByType(Car.class);
        for (Annotation annotation : annotations) {
            Car car = (Car) annotation;
            System.out.println(car.value());
        }
    }
}

Output: 

Suzuki
Tata

5. Retention

Retention is sort of a subset of @Target, as it is used to access data at runtime, but it also has some properties of its own. Here’s a quick look at all three properties and their usage:

  • Source: When the annotation processing is only needed at compile time and not present in the .class file.
  • Binary: This is present in the .class file but not available at runtime via reflection.
  • Runtime: This is present in the .class file and also available at runtime via reflection.
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class MyRuntimeAnnotation

Ok, now let’s look at how to create a custom annotation

A custom annotation is, essentially, an annotation created by itself. The annotation is created by the annotation class keyword and also takes a parameter.

That may sound tricky, but actually it’s really simple. Here’s the code to show you how.

annotation class MyCustomAnnotation

Now we will see how to pass parameters in custom annotation. Remember: Annotation parameters in Kotlin must be compile-time constants**.** Instead of embedding annotations, we can use annotation classes with multiple parameters or multiple annotations:

annotation class MyCustomAnnotation (val message : String)

Lastly, we will see how to apply custom annotation in a class.

@MyCustomAnnotation("Test annotation")
class MyClass

Pretty straightforward, right?

But what about reflection in Kotlin annotation?

Reflection is vital in Kotlin, as it allows us to inspect code when it’s about to run. Specifically, it allows us to access the class, function, properties, and constructor at runtime.

Kotlin annotations can be accessed at runtime using reflection. This technique allows us to inspect and manipulate annotations dynamically. To access annotation, we need to obtain a KClass instance of a class. To access the property, we will use the findAnnotation function.

First, we will create an annotation that will take a single parameter. We will access the value at runtime using reflection.

@Target(AnnotationTarget.CLASS)
annotation class MyReflectionAnnotation ( val value : String)

Okay, now let’s define this annotation in a class.

@MyReflectionAnnotation("Anupam")
class MyReflectionClass

Great. Now we will access the value using reflection, as shown below:

import kotlin.reflect.full.findAnnotation

fun main() {
    val annotation = MyReflectionClass::class.findAnnotation<MyReflectionAnnotation>()
    println(annotation?.value) // Output: Anupam
}

And… we’re done!

That was quick, right!

Hopefully you’ve now got a good understanding of how annotations work in Kotlin, and how you can use them to make your code simpler and more useful.

Let’s quickly summarize what we have learned:

  • Annotation is a powerful tool to add metadata to our code and plays an important role in libraries and frameworks. We can define custom annotations and access them at runtime with the help of reflection. Reflections are used in dependency injection, serialization, and more.
  • In Kotlin, an annotation processor is a class that helps to generate auto-generated code. Annotation processing is a tool provided by the Kotlin compiler that helps to add additional code during compilation.

Any other questions? Just write to us. We’re always happy to chat about Kotlin annotation or any other Android-related topics. So whatever questions you’ve got, we’re here to chat.

Happy Coding!

Expect The Unexpected!

Debug Faster With Bugfender

Start for Free
blog author

Anupam Singh

Anupam Singh is a Native Android Developer as well as Hybrid Mobile App Developer. More than 10 years of experience in Developing Mobile Apps. He is a technology blogger. Follow him on Linkedin to stay connected.

Join thousands of developers
and start fixing bugs faster than ever.