By Sergey Tselovalnikov on 23 December 2024

ABC: Learning The Alphabet with Java Annotations

Hi, kids, old and young! This Christmas, a couple of friends of mine are visiting me with Jackie, their little one. Jackie is young but is already getting good at mastering the alphabet. Naturally, I decided to prepare this article to help Jackie step the game up during Christmas. After all, nothing says “Christmas spirit” quite like learning the alphabet through Java annotations, right?

So, welcome to this Christmas special blog, and I hope that you can empathise with Jackie or his Grunkle Sergey and enjoy the read!

You never stop growing up. There’s always something new to learn.

Grunkle Stan

Hi, Jackie! 👋

I know you love magic – the worlds of elves, orcs, and dragons. Your Uncle Sergey loves those things too. But here’s the thing – he doesn’t quite like it when the real world and the magical world mix. And as magic is just a sufficiently large number of abstraction layers, it is sometimes part of his job to make this technology sufficiently less advanced so that magic doesn’t feel like magic anymore.

Now, Jackie, I know you’re just starting to master the alphabet, but you already know your Java basics, right? After all, your parents are excellent software engineers. So, let’s take you on a journey through the land of Java annotations, one letter at a time.

A is for @Autowired. This sounds so convenient, Jackie. You just add an annotation, and all fields in your class get magically initialised. But where do the values come from?

B is for @Bean. Aha, so this is where the values are come from. Maybe we just need to find a method annotated with @Bean. Okay, Jackie, you can go and play with @Bean construction, but only after you read and understand everything that's written here.

C is for @Component(Scan). Maybe, but if there is no such method, then the value comes from somewhere else. Yes, you can also change the entire behaviour of the program by adding a class with the annotation somewhere in your codebase. When it's done automagically, it's a step too far, and I'm not the only one who thinks that way.

D is for @Deprecated. Now, here’s an annotation that doesn’t make Uncle Sergey sad! It doesn’t introduce any magic. Instead, it communicates information about a piece of program structure in a consistent way, allowing you to act on it programmatically without necessarily affecting runtime behaviour. Adding forRemoval in Java 9 has made it even more useful.

E is for @Email. These days, Jackie, none of your friends use emails anymore. Yet they are ubiquitous. Sometimes, you just don’t want to know which of those regexes was used for email validation. Until you do. At least, I haven't seen @HTML yet.

F is for @FunctionalInterface. Your Uncle Sergey is on the fence about this one. Yes, it is just metadata, but is this metadata ever useful? I guess an additional compile-time check doesn't hurt if you're building a library.

G is for @Generated. Jackie, sometimes we grown-ups write code that writes code. Ideally, it should follow the same style guide, formatting rules, and checks as everything else. But hey, we can’t rewrite the generator for everyone’s codebase, so at least we know how to disable checks for these classes if we can guarantee their safety by construction.

H is for @HeaderParam. We adults are often pretty lazy, but to be honest, req.getHeader wouldn’t take that much longer to write. And as a bonus, your IDE will ensure nice API discoverability with autocompletion.

I is for @Inject. Okay, maybe we’re going in circles here, but we’re not even halfway through the alphabet.

J is for @JsonProperty. Describing data schema together with the class fields is indeed convenient. But consider that the schema declared in this way will likely become your source of truth, in which case it’s going to be Java-centric. But if the schema is declared somewhere else, then it's likely that the annotated classes are going to be auto-generated, at which point, why not generate more performant code instead?

K is for @KeyFor. Even adults make mistakes, Jackie. So static analysis is here to help, and annotations are a decent tool to help you fix your mistakes early. Yet, at some point, the costs of maintaining those checks can outweigh the pros. If you have to rely on annotating keys for maps, consider if there is a way to rewrite the code with more rigid types.

L is for @Log4j. Believe me, Jackie, you don’t want to hear my opinion on using Lombok in the real world. Others have articulated well that it should be treated as a separate language, and I agree with them.

M is for @Mock. Hey, Jackie, you’re young. You’ve got your whole life ahead of you. You don’t want to spend it updating the tests every time you change the structure of your code without changing its logic. Instead of mocks, prefer fakes, which let you test state, not interactions. @DoNotMock is my favourite annotation these days.

N is for @Nullable. Ah, the million-dollar friend: NullPointerException. Adding this metadata is great as long as it’s consistent, and potentially enforced with a tool like NullAway. Otherwise, you won't be able to trust it.

O is for @Override. Part metadata, part language feature mentioned in the JLS. If you’re using an IDE, you’ll never have to type it by hand.

P is for @Profile. Because declarative Spring wiring wasn’t flexible enough, now we have declarative if conditions. At least the configuration is not stored in XML anymore.

Q is for @Qualifier. What’s scary is that with this annotation, you can create your own custom qualifier annotation. I call it recursive magic.

R is for @RestController. A @Component with a twist. I’m glad to see that more and more frameworks are moving to a functional approach, which is a lot less magical, in a good way.

S is for @SuppressWarnings. Warnings quickly start being meaningless if you’re not failing the build on them, making this annotation a necessary evil.

T is for @Transactional. Many hours have been wasted debugging @Transactional on private methods, Jackie.

U is for @Untainted. Taint checking is a very interesting concept. Some languages are built around it, like this project: Jeeves. But in Java, I honestly prefer the way Google does it by using types.

V is for @VisibleForTesting. On the surface, this annotation sounds useful: machine-readable metadata attached to a part of the program structure. But in reality, Jackie, it can encourage shortcuts by legitimising suboptimal choices. Avoid coupling your tests with the implementation details of your classes from the outside if you can.

W is for @WebAppConfiguration. I think I used this one once. I don’t think I fully understood back then what it did. I wish I had just written plain old code instead.

X is for @XmlElement. We already discussed the issue of declaring a schema together with your code. Except these days, if you're parsing XML, it's more likely that you're reading a specific file format rather than integrating with another system.

Y is for @YamlProperty. As before. Except, make sure to check out this masterpiece noyaml.com before committing to using this annotation.

Z is for @Z.... You’ll have to write your own someday, Jackie. Maybe you’ll encounter a real problem where it’s genuinely useful to attach metadata to a part of your program structure – like marking unstable APIs with @Beta and enforcing rules about their usage programmatically. But don’t create a new annotation just to save yourself a few lines of code. Do it once to understand how magical frameworks work, and then never do it again. The magic shortcut is almost never worth it.

Instead of Conclusion

Soon, Jackie, you’ll be growing up. Maybe your parents will even introduce you to some beautiful concepts like pure functions and immutable data. But then you’ll be searching the internet, and you might stumble upon some powerful concepts like metaprogramming and reflection. I hope that before using this power, you’ll remember the ABC of Java annotations – there is already one for every letter of the alphabet. Make sure to only use them consciously for good – attaching useful machine-readable metadata to your program structure instead of building magical frameworks.

The internet is full of scary things, Jackie, but hopefully, this article prepares you for at least one of them. Now, go back to the Christmas table – we'll be watching Simple Made Easy instead of Die Hard this year.

Subscribe

I'll be sending an email every time I publish a new post.

Or, subscribe with RSS.