0% found this document useful (0 votes)
18 views36 pages

Java Annotations: Runtime vs Compile-Time

Uploaded by

Trong Nguyen Duc
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
18 views36 pages

Java Annotations: Runtime vs Compile-Time

Uploaded by

Trong Nguyen Duc
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Revealing the ✨magic🪄

behind Java annotations

Álvaro Sánchez-Mariscal
Principal Member of Technical Staff
Oracle
@alvaro_sanchez
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.

🇪🇸 🇩🇪 🇮🇹 🇬🇧 🇺🇸 🇨🇿 🇵🇱 🇩🇰 🇧🇪 🇳🇴 🇷🇴 🇸🇪
🇱🇻 🇫🇷 🇱🇺 🇲🇦 🇷🇸 🇨🇭 🇧🇬 🇵🇹 🇨🇦 🇸🇮 🇬🇷 🇭🇷
2 Copyright © 2025, Oracle and/or its affiliates
@alvaro_sanchez
Slides available on Speaker Deck

3 Copyright © 2025, Oracle and/or its affiliates @alvaro_sanchez


Annotation processing in Java

Discovering annotated Handling the annotation


classes

4 Copyright © 2025, Oracle and/or its affiliates


Runtime annotation processing
🪄 Discovering annotated classes at runtime: Spring

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

6 Copyright © 2025, Oracle and/or its affiliates


🪄 Discovering annotated classes at runtime : ClassGraph

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

7 Copyright © 2025, Oracle and/or its affiliates


🪄 Handling the annotation at runtime: dynamic proxies

• JDK built-in capability to generate


new classes via the
[Link] API.

• Bytecode is generated at runtime ⚠


and then loaded into a classloader.

• Can only generate implementations


for interfaces ⚠.

8 Copyright © 2025, Oracle and/or its affiliates @alvaro_sanchez


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

9 Copyright © 2025, Oracle and/or its affiliates @alvaro_sanchez


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

10 Copyright © 2025, Oracle and/or its affiliates @alvaro_sanchez


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

11 Copyright © 2025, Oracle and/or its affiliates @alvaro_sanchez


Demo
Implementing @Timed with Byte Buddy
⚠ 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.

13 Copyright © 2025, Oracle and/or its affiliates @alvaro_sanchez


Compile-time annotation processing
🪄 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.

15 Copyright © 2025, Oracle and/or its affiliates @alvaro_sanchez


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

16 Copyright © 2025, Oracle and/or its affiliates @alvaro_sanchez


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

17 Copyright © 2025, Oracle and/or its affiliates @alvaro_sanchez


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.

18 Copyright © 2025, Oracle and/or its affiliates @alvaro_sanchez


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

19 Copyright © 2025, Oracle and/or its affiliates @alvaro_sanchez


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.

20 Copyright © 2025, Oracle and/or its affiliates @alvaro_sanchez


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

21 Copyright © 2025, Oracle and/or its affiliates @alvaro_sanchez


The Micronaut approach
Introduction to Micronaut

Modern, open-source
Java Framework All application types Highly optimised

Micronaut has been designed Micronaut is a complete Micronaut leverages Java


from scratch in 2017, focused solution for any type of annotation processors and
on modern architectures like application: microservices, other optimisations to
microservices and serverless, message-driven producers or compute the framework
and with the cloud in mind. consumers, command-line infrastructure at compile-
apps, serverless functions, time, drastically reducing
etc. startup time and memory
consumption.

23 Copyright © 2025, Oracle and/or its affiliates


AOT: Ahead Of Time

Micronaut computes at build time:


• All dependency and configuration injection.
• Annotation metadata (meta annotations)
• AOP proxies.
• Bean introspections.
• All other framework infrastructure. Source code Bytecode
compilation
class $MySvc$Definition {}
@Singleton
At runtime: class MySvc {} class $MySvc$Definition$Reference {}
• No reflection.
• No proxy generation.
• No dynamic classloading. Source code Source code
compilation
• No classpath scanning. @Builder @Generated
record Person() {} class PersonBuilder() {}

24 Copyright © 2025 Oracle and/or its affiliates


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.

25 Copyright © 2025, Oracle and/or its affiliates @alvaro_sanchez


Demo
Implementing @Timed with Micronaut AOP
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.

27 Copyright © 2025, Oracle and/or its affiliates @alvaro_sanchez


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.

28 Copyright © 2025, Oracle and/or its affiliates @alvaro_sanchez


Demo
The Micronaut Developer Experience
Micronaut is the fastest to startup

Source: [Link]
30 Copyright © 2025, Oracle and/or its affiliates @alvaro_sanchez
Micronaut is the fastest to startup

Source: [Link]
31 Copyright © 2025, Oracle and/or its affiliates @alvaro_sanchez
Micronaut consumes the less memory

Source: [Link]
32 Copyright © 2025, Oracle and/or its affiliates @alvaro_sanchez
Micronaut and GraalVM powering Disney+

[Link]
33 Copyright © 2024, Oracle and/or its affiliates @alvaro_sanchez
Q&A

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


Álvaro Sánchez-Mariscal
@alvaro_sanchez

You might also like