Slide 1

Slide 1 text

Revealing the ✨magic🪄 behind Java annotations Álvaro Sánchez-Mariscal Principal Member of Technical Staff Oracle @alvaro_sanchez

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

@alvaro_sanchez Copyright © 2025, Oracle and/or its affiliates 3 Slides available on Speaker Deck

Slide 4

Slide 4 text

Copyright © 2025, Oracle and/or its affiliates 4 Annotation processing in Java Discovering annotated classes Handling the annotation

Slide 5

Slide 5 text

Runtime annotation processing

Slide 6

Slide 6 text

🪄 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.

Slide 7

Slide 7 text

🪄 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.

Slide 8

Slide 8 text

@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 ⚠.

Slide 9

Slide 9 text

@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.

Slide 10

Slide 10 text

@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.

Slide 11

Slide 11 text

@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.

Slide 12

Slide 12 text

Demo Implementing @Timed with Byte Buddy

Slide 13

Slide 13 text

@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.

Slide 14

Slide 14 text

Compile-time annotation processing

Slide 15

Slide 15 text

@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.

Slide 16

Slide 16 text

@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.

Slide 17

Slide 17 text

@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.

Slide 18

Slide 18 text

@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.

Slide 19

Slide 19 text

@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).

Slide 20

Slide 20 text

@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.

Slide 21

Slide 21 text

@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).

Slide 22

Slide 22 text

The Micronaut approach

Slide 23

Slide 23 text

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.

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

@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.

Slide 26

Slide 26 text

Demo Implementing @Timed with Micronaut AOP

Slide 27

Slide 27 text

@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.

Slide 28

Slide 28 text

@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.

Slide 29

Slide 29 text

Demo The Micronaut Developer Experience

Slide 30

Slide 30 text

@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

Slide 31

Slide 31 text

@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

Slide 32

Slide 32 text

@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

Slide 33

Slide 33 text

@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/

Slide 34

Slide 34 text

@alvaro_sanchez Q&A Copyright © 2025, Oracle and/or its affiliates 34

Slide 35

Slide 35 text

Álvaro Sánchez-Mariscal @alvaro_sanchez

Slide 36

Slide 36 text

No content