Skip to content

Kotlin Codes

  • Home
  • Flutter
  • Kotlin
  • SwiftUI
  • About Me
  • Home
    • Blog
    • Privacy Policy
  • Flutter
    • Widgets In Flutter
      • Cupertino Widgets
      • iOS Styling Flutter
    • Database & Storage
    • State Management in Flutter
    • Performance Optimization
    • Networking & APIs
    • Testing & Debugging
  • Kotlin
    • Kotlin XML Development(Traditional View-based UI)
      • Introduction to XML UI Development
      • State Management and Architecture
      • Advanced Topics
      • Firebase and Cloud Integration
      • UI Components and Customization
      • Media and Visual Enhancements
      • Navigation and Data Handling
      • UI Performance Optimization
      • Networking and Data Management
    • Jetpack Compose
      • UI Elements
      • Kotlin Multiplatform
      • Accessibility
      • Animation
      • Core Concepts
      • Custom Drawing
      • Interoperability
      • Layouts
      • State Management
      • Modifiers
      • Navigation
      • Testing
      • Theming
      • Performance
    • Kotin-CodeChallenge
  • SwiftUI
  • About Me

Kotlin Custom Annotations: When and How to Use Them

March 6, 2025May 15, 2025 Sayandh

Kotlin, a modern statically typed programming language, provides robust support for annotations. Annotations are a powerful way to add metadata to code, enabling various forms of compile-time and runtime processing. Custom annotations, in particular, allow developers to define their own metadata structures tailored to specific application needs. Understanding when and how to use custom annotations in Kotlin can greatly enhance code clarity, maintainability, and functionality.

What are Annotations in Kotlin?

Annotations in Kotlin are a form of metadata that provide additional information about the code. They don’t directly affect the execution of the program but can be used by compilers, build tools, and runtime environments. Annotations can be applied to classes, functions, properties, and other code elements.

Why Use Custom Annotations?

  • Code Generation: Annotations can trigger code generation during compilation.
  • Compile-time Checks: Annotations can enable additional compile-time checks, ensuring code quality.
  • Runtime Behavior Modification: Annotations can be used to modify the runtime behavior of an application.
  • Documentation and Metadata: They can serve as documentation, providing additional context for developers.
  • Framework Integration: Useful for integrating custom functionality into existing frameworks.

How to Define and Use Custom Annotations in Kotlin

Creating and using custom annotations in Kotlin involves several key steps:

Step 1: Defining a Custom Annotation

To define a custom annotation, you use the annotation class syntax:


@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class MyCustomAnnotation(val message: String = "Default Message")

Explanation:

  • @Target: Specifies where the annotation can be applied. In this example, it can be applied to classes, functions, and properties.
  • @Retention: Determines whether the annotation is stored in the compiled class file and available at runtime. RUNTIME retention means the annotation is accessible at runtime.
  • annotation class MyCustomAnnotation: Defines the annotation itself. You can include parameters (val message: String) to pass values to the annotation.

Annotation Targets

The @Target meta-annotation specifies the kinds of elements to which an annotation can be applied. Common targets include:

  • AnnotationTarget.CLASS: Class, interface, or object
  • AnnotationTarget.FUNCTION: Function
  • AnnotationTarget.PROPERTY: Property (getter and setter)
  • AnnotationTarget.FIELD: Field
  • AnnotationTarget.CONSTRUCTOR: Constructor
  • AnnotationTarget.PROPERTY_GETTER: Getter of a property
  • AnnotationTarget.PROPERTY_SETTER: Setter of a property
  • AnnotationTarget.TYPEALIAS: Type alias
  • AnnotationTarget.EXPRESSION: Expression
  • AnnotationTarget.FILE: File

Annotation Retentions

The @Retention meta-annotation specifies how the annotation is stored and available. The retention policies include:

  • AnnotationRetention.SOURCE: The annotation is only available in the source code and is discarded during compilation.
  • AnnotationRetention.BINARY: The annotation is stored in the compiled class file but is not available at runtime.
  • AnnotationRetention.RUNTIME: The annotation is stored in the compiled class file and is available at runtime (via reflection).

Step 2: Applying the Custom Annotation

You can apply the custom annotation to classes, functions, or properties like so:


@MyCustomAnnotation(message = "This is a class annotation")
class MyClass {

    @MyCustomAnnotation(message = "This is a function annotation")
    fun myFunction() {
        println("Function executed")
    }

    @get:MyCustomAnnotation(message = "This is a property annotation")
    val myProperty: String = "Property value"
}

Step 3: Processing the Annotation (Runtime Example)

To process the annotation at runtime, you can use reflection. Here’s an example:


fun main() {
    val myClass = MyClass()

    // Process class annotation
    val classAnnotation = myClass::class.java.getAnnotation(MyCustomAnnotation::class.java)
    println("Class Annotation Message: ${classAnnotation?.message}")

    // Process function annotation
    val myFunction = myClass::class.java.getMethod("myFunction")
    val functionAnnotation = myFunction.getAnnotation(MyCustomAnnotation::class.java)
    println("Function Annotation Message: ${functionAnnotation?.message}")

    // Process property annotation
    val myProperty = myClass::class.java.getDeclaredField("myProperty")
    val propertyAnnotation = myProperty.getAnnotation(MyCustomAnnotation::class.java)
    println("Property Annotation Message: ${propertyAnnotation?.message}")
}

Output:


Class Annotation Message: This is a class annotation
Function Annotation Message: This is a function annotation
Property Annotation Message: null

Note: For properties, you might need to access the getter method to retrieve the annotation correctly:


val myPropertyGetter = myClass::class.java.getMethod("getMyProperty")
val propertyAnnotation = myPropertyGetter.getAnnotation(MyCustomAnnotation::class.java)
println("Property Annotation Message: ${propertyAnnotation?.message}")

Example: Validation Annotation

Consider a scenario where you want to validate fields in a data class:


@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
annotation class NotEmpty

data class User(
    @NotEmpty val username: String,
    @NotEmpty val email: String
)

fun validate(obj: Any) {
    val clazz = obj::class.java
    clazz.declaredFields.forEach { field ->
        field.annotations.forEach { annotation ->
            if (annotation is NotEmpty && (field.get(obj) as String).isEmpty()) {
                throw IllegalArgumentException("Field ${field.name} cannot be empty")
            }
        }
    }
}

fun main() {
    val user = User("", "test@example.com")
    try {
        validate(user)
    } catch (e: IllegalArgumentException) {
        println(e.message) // Prints: Field username cannot be empty
    }
}

Example: Database Mapping Annotation

Let’s look at a more complex scenario where you might use annotations to map a class to a database table:


@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Entity(val tableName: String)

@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
annotation class Column(val columnName: String)

@Entity(tableName = "users")
data class User(
    @Column(columnName = "id") val id: Int,
    @Column(columnName = "username") val username: String,
    @Column(columnName = "email") val email: String
)

fun main() {
    val user = User(1, "john_doe", "john@example.com")
    val entityAnnotation = user::class.java.getAnnotation(Entity::class.java)
    println("Table Name: ${entityAnnotation.tableName}")

    user::class.java.declaredFields.forEach { field ->
        val columnAnnotation = field.getAnnotation(Column::class.java)
        println("Field ${field.name} is mapped to column ${columnAnnotation?.columnName}")
    }
}

Output:


Table Name: users
Field id is mapped to column id
Field username is mapped to column username
Field email is mapped to column email

When to Use Custom Annotations

  • Frameworks and Libraries: When creating frameworks or libraries that require additional metadata about the code, annotations can be invaluable.
  • Code Generation: When you need to generate code based on certain code characteristics.
  • Compile-time Checks: When you want to enforce certain rules at compile time that cannot be expressed through the type system alone.
  • Configuration: For externalizing configuration metadata within the code itself, making it more readable and maintainable.

Best Practices

  • Use Meaningful Names: Ensure annotation names clearly reflect their purpose.
  • Define Appropriate Targets: Restrict annotation targets to avoid misuse.
  • Choose Correct Retention Policy: Select the appropriate retention policy based on whether you need the annotation at runtime.
  • Keep Annotations Simple: Avoid overly complex annotations that are difficult to understand and maintain.
  • Document Annotations: Provide clear documentation on how to use annotations.

Conclusion

Custom annotations in Kotlin are a powerful tool for adding metadata to your code, enabling various compile-time and runtime behaviors. By understanding when and how to use custom annotations, you can improve code quality, generate code, perform compile-time checks, and modify runtime behavior. Following best practices will help you create annotations that are easy to understand, maintain, and use effectively in your Kotlin projects.

Beyond This Article: Your Next Discovery Awaits

Kotlin Coroutines: Simplifying Asynchronous Programming
Compose Multiplatform: Building Cross-Platform UIs with Jetpack Compose
Using Kotlin DSLs to Build More Readable and Flexible APIs
Kotlin Builder Pattern with DSLs: Writing Elegant Fluent APIs
Kotlin’s lateinit vs lazy: When to Use Each
Mastering Kotlin’s Companion Object: Static-like Behavior in Kotlin
Tagged with Advanced Kotlin Programming, Annotation Processing, Annotation Retention, Annotation Targets, Code Generation Kotlin, Compile-time Checks Kotlin, Custom Annotations Kotlin, Kotlin Annotations Reflection, Kotlin Metadata, Kotlin Runtime Reflection
  • Kotlin

Post navigation

Previous Post

Implementing Custom Tab Bars in Flutter

Next Post

Implementing Infinite Scrolling in SwiftUI Lists

Recents

  • Writing Effective Unit Tests for Individual Widgets and UI Components to Ensure They Function Correctly in Isolation in Flutter
  • Understanding the Architecture and Platform Differences When Developing Flutter Desktop Applications
  • Using Firebase Analytics to Track User Behavior, Screen Views, Custom Events, and User Properties in Flutter
  • Using the web_socket_channel Package to Establish and Manage WebSocket Connections in Flutter
  • Working with WebSockets to Enable Real-Time, Bidirectional Communication Between Your Flutter App and a Backend Server
  • Dart
  • Flutter
    • Advanced Concepts
    • Animations & UI Enhancements
    • Data Handling (JSON, REST APIs, Databases)
    • Database & Storage
    • Input Widgets
    • iOS Styling Flutter
    • Layout Widgets
    • Navigation and Routing
    • Networking & APIs
    • Performance Optimization
    • Platform Integration (Native Features)
    • State Management (Provider, BLoC, Riverpod)
    • State Management in Flutter
    • Testing (Unit, Widget, Integration)
    • Testing & Debugging
    • UI Basics
    • Widgets In Flutter
      • Cupertino Widgets
  • Kotlin
    • Jetpack Compose
      • Accessibility
      • Animation
      • Core Concepts
      • Custom Drawing
      • Interoperability
      • Kotlin Multiplatform
      • Layouts
      • Modifiers
      • Navigation
      • Performance
      • State Management
      • Testing
      • Theming
      • UI Elements
    • Kotin-CodeChallenge
    • Kotlin XML Development(Traditional View-based UI)
      • Accessibility
      • Advanced Topics
      • Advanced Topics & Custom Views
      • Animation
      • Data Binding
      • Drawables
      • Firebase and Cloud Integration
      • Introduction to XML UI Development
      • Kotlin Integration & Patterns
      • Layouts
      • Media and Visual Enhancements
      • Navigation and Data Handling
      • Networking and Data Management
      • RecyclerView
      • State Management and Architecture
      • Styles & Themes
      • UI Components and Customization
      • UI Performance Optimization
      • View Binding
      • Views
      • XML Techniques
  • SwiftUI

© KotlinCodes. Explore the latest Kotlin tutorials, Flutter guides, and Dart programming tips. | Learn Kotlin | Flutter Development | Dart Programming | Flutter Widgets