Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Revealing the magic behind Java annotations

Revealing the magic behind Java annotations

Annotations, introduced in Java 5 two decades ago, have revolutionised how we develop Java applications. Traditional frameworks like Spring and Hibernate rely on runtime annotation processing using reflection to perform tasks such as dependency injection or implement persistence operations.

Alternatively, the Java Annotation Processing API, introduced in Java 6 in 2006, allows developers to hook into the compilation phase to process sources and react to annotations present on them. This was leveraged by libraries like Lombok and Dagger, and frameworks such as Micronaut, although their way of processing annotations varies. Other frameworks like Quarkus perform the annotation processing during build time, instead of the compilation time.

In this session, Micronaut Framework committer Álvaro Sánchez-Mariscal will explain with examples the different techniques that can be used for both runtime and compile-time processing of annotations, revealing the magic behind popular open-source projects.

Álvaro Sánchez-Mariscal

January 22, 2025
Tweet

More Decks by Álvaro Sánchez-Mariscal

Other Decks in Technology

Transcript

  1. Copyright © 2025, Oracle and/or its affiliates 2 About me

    • Coming from Madrid 🇪🇸 • Developer since 2001 (Java ☕ stack) • Micronaut core developer since its inception (2017). • Author: Maven Plugin, Object Storage, Kubernetes, Cache, Control Panel. • Others: Core, Gradle, AWS, GCP, Azure, Security, Test, etc. • Currently at Oracle Labs: • Micronaut and Graal Development Kit for Micronaut (GDK). • GraalVM Native Build Tools. 🇪🇸 🇩🇪 🇮🇹 🇬🇧 🇺🇸 🇨🇿 🇵🇱 🇩🇰 🇧🇪 🇳🇴 🇷🇴 🇸🇪 🇱🇻 🇫🇷 🇱🇺 🇲🇦 🇷🇸 🇨🇭 🇧🇬 🇵🇹 🇨🇦 🇸🇮 🇬🇷 🇭🇷 @alvaro_sanchez
  2. Copyright © 2025, Oracle and/or its affiliates 4 Annotation processing

    in Java Discovering annotated classes Handling the annotation
  3. 🪄 Discovering annotated classes at runtime: Spring Copyright © 2025,

    Oracle and/or its affiliates 6 • Iterates over the classes (.class) of a given package, using a custom classloader⚠, and use the reflection⚠ API to query their annotations. • Spring will also use reflection to query fields and methods metada to process annotations such @Autowired or @Transactional. • The new Spring AOT module is an attempt to reduce the amount of these runtime operations that increase startup time and memory consumption.
  4. 🪄 Discovering annotated classes at runtime : ClassGraph Copyright ©

    2025, Oracle and/or its affiliates 7 • Parses the classfile binary format directly. • Implements its own bytecode parser without dependencies. • It’s capable of handling JARs nested within a fat JAR without extracting them. • Highly optimised: uses multiple threads, lock-free datastructures to avoid thread contention, and caches to avoid duplicating work. • Burningwave Core is another alternative.
  5. @alvaro_sanchez Copyright © 2025, Oracle and/or its affiliates 8 🪄

    Handling the annotation at runtime: dynamic proxies • JDK built-in capability to generate new classes via the java.lang.reflect.Proxy API. • Bytecode is generated at runtime ⚠ and then loaded into a classloader. • Can only generate implementations for interfaces ⚠.
  6. @alvaro_sanchez Copyright © 2025, Oracle and/or its affiliates 9 🪄

    Handling the annotation at runtime: CGLib • Mature library used by Spring and others to dynamically generate proxies for any class or interface. • Can also be used to dynamically enhance (read: mutate) existing classes. • No longer maintained and “does not work well (or possibly at all?) in newer JDKs, particularly JDK17+” (Spring uses its own fork). • As its alternatives, uses ASM for low-level bytecode manipulation.
  7. @alvaro_sanchez Copyright © 2025, Oracle and/or its affiliates 10 🪄

    Handling the annotation at runtime: Byte Buddy • Modern library used by Hibernate, Mockito, Jackson, Selenium, Spock, AssertJ… • Allows generating or changing classes either manually, using a Java agent or during a build. • It is fast (but at runtime), with a nice API, very well documented and there are lots of examples.
  8. @alvaro_sanchez Copyright © 2025, Oracle and/or its affiliates 11 🪄

    Handling the annotation at runtime: JEP-484 • Preview in JDK 22/23, released in JDK 24 (March 2025). • Internal replacement for ASM, "an old codebase with plenty of legacy baggage”. • Allows parsing, generating and transforming classes with a modern API.
  9. @alvaro_sanchez Copyright © 2025, Oracle and/or its affiliates 13 ⚠

    The problems of runtime processing • Classpath scanning is slow. • Reflection API is slow. • Hence, frameworks and libraries using reflection cache the metadata in memory. • More classes and dependencies => more startup time and memory footprint. • Bytecode generation at runtime increases startup time. • Runtime processing requires additional metadata for GraalVM Native Image.
  10. @alvaro_sanchez Copyright © 2025, Oracle and/or its affiliates 15 🪄

    Annotation discovery at build-time: Jandex • Java class file indexer, used by Quarkus. • Designed to be used during build time (!= compilation time). • Parses the bytecode and collects metadata into a persistence index. • The index can then be loaded at runtime, avoiding the use of reflection.
  11. @alvaro_sanchez Copyright © 2025, Oracle and/or its affiliates 16 🪄

    Annotation handling at build-time: Gizmo • Library to generate new bytecode, written on top of ASM, used by Quarkus. • Can also be used to mutate existing classes. • Quarkus also allows extension developers to use “bytecode recording”: • Recorded invocations are transformed to bytecode using Gizmo during an augmentation build stage.
  12. @alvaro_sanchez Copyright © 2025, Oracle and/or its affiliates 17 🪄

    Annotation discovery at compile-time: JSR-269 • “Pluggable Annotation Processing API”, December 2006. • Compiler hooks for javac. • Used by frameworks like Micronaut, and libraries like Lombok or MapStruct.
  13. @alvaro_sanchez Copyright © 2025, Oracle and/or its affiliates 18 Understanding

    annotation processors • Annotation processing is done in several rounds. • Since a processor may generate source that needs to be processed by another processor. • Only sources available (javac inputs), not compiled classes nor dependencies. • Annotation processor classpath != compilation classpath. • Can have its own dependencies, but should be kept minimal.
  14. @alvaro_sanchez Copyright © 2025, Oracle and/or its affiliates 19 🪄

    Handling the annotation at compile time • Examples of what an annotation processor can do during compilation: • Generate source code (MapStruct, Micronaut). • Generate new bytecode (Micronaut). • Modify existing classes (Lombok ⚠). • Generate JSON/YAML/etc (Dekorate, Micronaut).
  15. @alvaro_sanchez Copyright © 2025, Oracle and/or its affiliates 20 The

    case of Project Lombok • Java compilation is done in 3 stages: 1. In the first stage, source files are parsed into an Abstract Syntax Tree (AST). 2. In the second stage, annotation processors are invoked. • If as a result, new sources are generated, the compilation loops back to the parsing stage, and then runs another annotation processing round. 3. In the final stage, the bytecode is generated and class files are written. • The annotation processing API doesn’t allow to modify classes. • Lombok then manipulates the AST injecting new methods, statements to existing methods, etc. • To do so, it uses internal APIs in javac and in the Eclipse Compiler, OpenJ9, etc.
  16. @alvaro_sanchez Copyright © 2025, Oracle and/or its affiliates 21 The

    problems of using Lombok • There are potential functional problems beyond the scope of this talk (e.g. using @Data with JPA). • Compilation-wise, Lombok presents the following technical issues: • It uses loopholes that will be closed gradually in future releases (JEP 403: Strongly Encapsulate JDK Internals), so Lombok maintainers will have to find other ways going forward. • This limits your ability to upgrade to newer Java versions. • Mutating the AST breaks other annotation processors – Lombok needs to be run first. • Their AST manipulation breaks incremental compilation. • Java language today has evolved significantly since Java 6/7/8 (e.g. records).
  17. Introduction to Micronaut Copyright © 2025, Oracle and/or its affiliates

    23 Micronaut is a complete solution for any type of application: microservices, message-driven producers or consumers, command-line apps, serverless functions, etc. All application types Micronaut leverages Java annotation processors and other optimisations to compute the framework infrastructure at compile- time, drastically reducing startup time and memory consumption. Highly optimised Modern, open-source Java Framework Micronaut has been designed from scratch in 2017, focused on modern architectures like microservices and serverless, and with the cloud in mind.
  18. Micronaut computes at build time: • All dependency and configuration

    injection. • Annotation metadata (meta annotations) • AOP proxies. • Bean introspections. • All other framework infrastructure. At runtime: • No reflection. • No proxy generation. • No dynamic classloading. • No classpath scanning. AOT: Ahead Of Time Copyright © 2025 Oracle and/or its affiliates 24 Source code @Singleton class MySvc {} Bytecode class $MySvc$Definition {} class $MySvc$Definition$Reference {} Source code @Builder record Person() {} Source code @Generated class PersonBuilder() {} compilation compilation
  19. @alvaro_sanchez Copyright © 2025, Oracle and/or its affiliates 25 Micronaut’s

    compile-time approach • Micronaut is really a smart compiler, and the only framework that supports cross-language compiler plugins (Java, Kotlin, Groovy*), including: • Bean introspections: replacement for JDK’s Introspector. • Bean validation: replacement for Hibernate Validator with faster startup and smaller memory consumption. • Bean mappers: built-in alternative to MapStruct. • Micronaut Serialization: drop-in replacement for Jackson Databind, faster and more secure. • Micronaut Data JDBC with compile-time checks and queries as an alternative to Hibernate. • … and much more, all fully-compatible with GraalVM Native Image since day 1. • Micronaut strives to help you reduce the number of dependencies you need.
  20. @alvaro_sanchez Copyright © 2025, Oracle and/or its affiliates 27 Micronaut

    SourceGen • Language-neutral, high-level API for performing source code generation during compilation for Java and Kotlin. • Ships with a few annotations to reduce the need of using Lombok: @Builder @Wither @SuperBuilder @Singular @ToString @EqualsAndHashCode @Delegate • Micronaut will never mutate your classes.
  21. @alvaro_sanchez Copyright © 2025, Oracle and/or its affiliates 28 Why

    startup time and memory consumption matters? • Q: if your application is a long-running service, why this matters? • A1: Developer Productivity. • You can run end-to-end tests within milliseconds. • In development mode, your application restarts so fast you don’t even notice. • A2: Cloud-Native deployments. • Scale to zero: in Kubernetes, you can have pods that come up instantly. • In Serverless, you literally save money with fast-running functions. • If you want to squeeze performance even more, use GraalVM • With the best framework support in the industry.
  22. @alvaro_sanchez Copyright © 2025, Oracle and/or its affiliates 30 Micronaut

    is the fastest to startup Source: https://speakerdeck.com/mraible/comparing-native-java-rest-api-frameworks-jchampions-conference-2024
  23. @alvaro_sanchez Copyright © 2025, Oracle and/or its affiliates 31 Micronaut

    is the fastest to startup Source: https://speakerdeck.com/mraible/comparing-native-java-rest-api-frameworks-jchampions-conference-2024
  24. @alvaro_sanchez Copyright © 2025, Oracle and/or its affiliates 32 Micronaut

    consumes the less memory Source: https://speakerdeck.com/mraible/comparing-native-java-rest-api-frameworks-jchampions-conference-2024
  25. @alvaro_sanchez Copyright © 2024, Oracle and/or its affiliates 33 Micronaut

    and GraalVM powering Disney+ https://aws.amazon.com/blogs/opensource/improving-developer-productivity-at-disney-with-serverless-and-open-source/