Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
167: Annotation Processing Basics
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@JsonEntityannotation, it hands your processor aRoundEnvironmentcontaining 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.processingAPI, 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
@JsonEntityis 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.
There are no comments for now.