Skip to Content
Course content

167: Annotation Processing Basics

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Look, we've all been there. You're building a system and you realize you're writing the same boilerplate code for the tenth time—maybe it's mapping a POJO to a JSON string, or validating that a set of fields follows a specific naming convention. Your first instinct is usually to create a custom annotation, like @JsonEntity, and then write a utility class that uses Reflection to scan for that annotation at runtime and do something clever. It feels like magic the first time you do it, and it definitely keeps your business logic clean.

The Runtime Reflection Trap

The problem with the Reflection-based approach is that you're essentially pushing all the "discovery" work to the person actually running the application. I've seen projects where the startup time bloated by seconds just because the app was scanning every single class in the classpath for a specific annotation. Beyond the performance hit, you lose the safety net of the compiler. If you misconfigure an annotation or apply it to a field that doesn't support your logic, you won't find out until the code actually hits that path in production. You're trading compile-time certainty for a bit of runtime convenience, and in a large-scale Java app, that's a trade that rarely pays off.

// The "Naive" way: Runtime scanning
public class JsonMapper {
    public String toJson(Object obj) {
        Class> clazz = obj.getClass();
        if (clazz.isAnnotationPresent(JsonEntity.class)) {
            // Use reflection to iterate fields... 
            // This happens every time you call this method 
            // or requires a heavy cache.
        }
        return "{}";
    }
}


Shifting the Burden to the Compiler

The professional way to handle this is through Annotation Processing. Instead of asking "Does this class have this annotation?" while the app is running, we ask the Java compiler (javac) to tell us about it while it's building the bytecode. By extending AbstractProcessor, you can hook into the compilation round. When the compiler sees your @JsonEntity annotation, it hands your processor a RoundEnvironment containing every element marked with that tag.

The real power here isn't just in checking for errors; it's in code generation. Instead of using Reflection to figure out how to map a class at runtime, your processor can literally write a new Java source file—something like UserEntity_Mapper.java—that contains hard-coded, lightning-fast mapping logic. You're essentially writing a program that writes your boilerplate for you.

Build-time Cost vs. Runtime Speed

Now, I'll be honest with you: setting up an Annotation Processor is more work. You have to deal with the javax.annotation.processing API, and you have to be careful about the "round" system (since the code you generate might itself have annotations that need processing). You'll also notice your build times increase slightly because the compiler is now running your custom logic.

But consider the payoff. Your runtime performance is now identical to if you had written the boilerplate by hand. There's no reflection, no expensive classpath scanning, and no "magic" that fails at 3 AM. If you use the processor to validate the annotations—for example, throwing a compiler error if @JsonEntity is placed on an interface—you stop bugs from ever leaving the developer's machine. In my experience, spending an extra hour on the processor setup saves dozens of hours in debugging and performance tuning later on.




📋 Practical Task

Build a Compile-Time Field Validator

Your task is to implement a basic Annotation Processor that enforces a naming convention at compile-time. Create an annotation called @ImmutableField. Write a processor that scans for this annotation; if the annotation is placed on a field that is not marked as final, the processor should trigger a compilation error using the Messager API.

Your goal is to ensure that the project cannot even finish compiling if a developer marks a field as @ImmutableField but forgets the final keyword. Verify this by creating a test class with both a correct final field and an incorrect non-final field, and confirm that the compiler rejects the build.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.