There are a number of tools and libraries I cannot imagine myself writing Java code without these days. Traditionally, things like Google Guava or Joda Time (at least for the pre Java 8 era) are among the dependencies that I end up throwing into my projects most of the time, regardless of the specific domain at hand.

Lombok surely deserves its place in my POMs or Gradle builds as well, albeit not being a typical library/framework utility. Lombok has been around for quite a while now (first released in 2009) and has matured a lot since. However, I have always felt it deserved more attention—it is an amazing way of dealing with Java’s natural verbosity.

In this post, we will explore what makes Lombok such a handy tool.

Project Lombok

Java has many things going for it beyond just the JVM itself, which is a remarkable piece of software. Java is mature and performant, and the community and ecosystem around it are huge and lively.

However, as a programming language, Java has some idiosyncrasies of its own as well as design choices that can make it rather verbose. Add some constructs and class patterns we Java developers often need to use and we frequently end up with many lines of code that bring little or no real value other than complying with some set of constraints or framework conventions.

Here is where Lombok comes into play. It enables us to drastically reduce the amount of “boilerplate” code we need to write. Lombok’s creators are a couple of very smart guys, and certainly have a taste for humor—you cannot miss this intro they made at a past conference!

Let’s see how Lombok does its magic and some usage examples.

How Lombok Works

Lombok acts as an annotation processor that “adds” code to your classes at compile time. Annotation processing is a feature added to the Java compiler at version 5. The idea is that users can put annotation processors (written by oneself, or via third-party dependencies, like Lombok) into the build classpath. Then, as the compile process is going on, whenever the compiler finds an annotation it sort of asks: “Hey, anybody in the classpath interested in this @Annotation?.” For those processors raising their hands, the compiler then transfers control to them along with compile context for them to, well… process.

Maybe the most common case for annotation processors is to generate new source files or perform some kind of compile-time checks.

Lombok does not really fall into these categories: What it does is modify the compiler data structures used to represent the code; i.e., its abstract syntax tree (AST). By modifying the compiler’s AST, Lombok is indirectly altering the final bytecode generation itself.

This unusual, and rather intrusive, approach has traditionally resulted in Lombok being viewed as somewhat of a hack. While I would myself agree with this characterization to some extent, rather than viewing this in the bad sense of the word, I would view Lombok as a “clever, technically meritorious, and original alternative.”

Still, there are developers who consider it to be a hack and do not use Lombok for this reason. That is understandable, but in my experience, Lombok’s productivity benefits outweigh any of these concerns. I have been happily using it for production projects for many years now.

Before going into details, I’d like to summarize the two reasons I especially value the use of Lombok in my projects:

  1. Lombok helps keep my code clean, concise, and to the point. I find my Lombok annotated classes very expressive and I generally find annotated code to be quite intention-revealing, though not everyone on the internet necessarily agrees.
  2. When I’m starting a project and thinking of a domain model, I tend to begin by writing classes that are very much a work in progress and that I change iteratively as I think further and refine them. In these early stages, Lombok helps me move faster by not needing to move around or transform the boilerplate code it generates for me.

Bean Pattern and Common Object Methods

Many of the Java tools and frameworks we use rely on the Bean Pattern. Java Beans are serializable classes that have a default zero-args constructor (and possibly other versions) and expose their state via getters and setters, typically backed by private fields. We write lots of these, for example when working with JPA or serialization frameworks such as JAXB or Jackson.

Consider this User bean that holds up to five attributes (properties), for which we’d like to have an additional constructor for all attributes, meaningful string representation, and define equality/hashing in terms of its email field:

public class User implements Serializable {

    private String email;

    private String firstName;
    private String lastName;

    private Instant registrationTs;

    private boolean payingCustomer;
    
    // Empty constructor implementation: ~3 lines.
    // Utility constructor for all attributes: ~7 lines.
    // Getters/setters: ~38 lines.
    // equals() and hashCode() as per email: ~23 lines.
    // toString() for all attributes: ~3 lines.

    // Relevant: 5 lines; Boilerplate: 74 lines => 93% meaningless code :(
    
}

For brevity here, rather than including the actual implementation of all the methods, I instead just provided comments listing the methods and the number of lines of code that the actual implementations took. That boilerplate code would have totalled more than 90% of the code for this class!

Moreover, if I later wanted to, say, change email to emailAddress or have registrationTs be a Date instead of an Instant then I’d need to devote time (with the help of my IDE for some cases, admittedly) to change things like get/set method names and types, modify my utility constructor, and so on. Again, priceless time for something that brings no practical business value to my code.

Let’s see how Lombok can help here:

import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter @Setter
@NoArgsConstructor @AllArgsConstructor
@ToString
@EqualsAndHashCode(of = {"email"})
public class User {

    private String email;

    private String firstName;
    private String lastName;

    private Instant registrationTs;

    private boolean payingCustomer;

}

Voilà! I just added a bunch of lombok.* annotations and achieved just what I wanted. The listing above is exactly all the code I need to write for this. Lombok is hooking into my compiler process and generated everything for me (see the screenshot below of my IDE).

IDE Screenshot

As you notice, the NetBeans inspector (and this will happen regardless of IDE) does detect the compiled class bytecode, including the additions Lombok brought into the process. What happened here is quite straightforward:

  • Using @Getter and @Setter I instructed Lombok to generate getters and setters for all attributes. This is because I used the annotations at a class level. If I wanted to selectively specify what to generate for which attributes, I could have annotated the fields themselves.
  • Thanks to @NoArgsConstructor and @AllArgsConstructor, I got a default empty constructor for my class as well as an additional one for all the attributes.
  • The @ToString annotation auto-generates a handy toString() method, showing by default all class attributes prefixed by their name.
  • Finally, to have the pair of equals() and hashCode() methods defined in terms of the email field I used @EqualsAndHashCode and parameterized it with the list of relevant fields (just the email in this case).

Customizing Lombok Annotations

Let’s now use some Lombok customizations following this same example:

  • I’d like to lower the visibility of the default constructor. Because I only need it for bean compliancy reasons, I expect consumers of the class to only call the constructor that takes all fields. To enforce this, I am customizing the generated constructor with AccessLevel.PACKAGE.
  • I want to ensure that my fields never get assigned null values, neither via the constructor nor via the setter methods. Annotating class attributes with @NonNull is enough; Lombok will generate null checks throwing NullPointerException when appropriate in the constructor and setter methods.
  • I’ll add a password attribute, but do not want it shown when calling toString() for security reasons. This is accomplished via the excludes argument of @ToString.
  • I’m OK exposing state publicly via getters, but would prefer to restrict outside mutability. So I am leaving @Getter as is, but again using AccessLevel.PROTECTED for @Setter.
  • Perhaps I would like to force some constraint on the email field so that, if it gets modified, some kind of check is run. For this, I just implement the setEmail() method myself. Lombok will just omit generation for a method that already exists.

This is how the User class will then look:

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter(AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PACKAGE)
@AllArgsConstructor
@ToString(exclude = {"password"})
@EqualsAndHashCode(of = {"email"})
public class User {

    private @NonNull String email;

    private @NonNull byte[] password;

    private @NonNull String firstName;
    private @NonNull String lastName;

    private @NonNull Instant registrationTs;

    private boolean payingCustomer;

    protected void setEmail(String email) {
        // Check for null (=> NullPointerException) 
        // and valid email code (=> IllegalArgumentException)
        this.email = email;
    } 
    
}

Note that, for some annotations, we are specifying class attributes as plain strings. Not a problem, because Lombok will throw a compile error if we, for example, mistype or refer to a non-existing field. With Lombok, we’re safe.

Also, just like for the setEmail() method, Lombok will just be OK and not generate anything for a method the programmer has already implemented. This applies to all methods and constructors.

Immutable Data Structures

Another use case where Lombok excels is when creating immutable data structures. These are usually referred to as “value types.” Some languages have built-in support for these, and there’s even a proposal for incorporating this into future Java versions.

Suppose we want to model a response to a user login action. This is the kind of object we’d want to just instantiate and return to other layers of the application (for example, to be JSON serialized as the body of an HTTP response). Such a LoginResponse would not need to be mutable at all and Lombok can help describe this succinctly. Sure, there are many other use cases for immutable data structures (they’re multithreading- and cache-friendly, among other qualities), but let’s stick to this simple example:

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.experimental.Wither;

@Getter
@RequiredArgsConstructor
@ToString
@EqualsAndHashCode
public final class LoginResponse {

    private final long userId;
    
    private final @NonNull String authToken;
    
    private final @NonNull Instant loginTs;

    @Wither
    private final @NonNull Instant tokenExpiryTs;
    
}

Worth noting here:

  • A @RequiredArgsConstructor annotation has been introduced. Aptly named, what it does is generate a constructor for all final fields that have not already been initialized.
  • In cases where we want to reuse a previously issued LoginResonse (imagine, for example, a “refresh token” operation), we certainly don’t want to modify our existing instance, but rather, we want to generate a new one based on it. See how the @Wither annotation helps us here: It tells Lombok to generate a withTokenExpiryTs(Instant tokenExpiryTs) method that creates a new instance of LoginResponse having all the with’ed instance values, except the new one we’re specifying. Would you like this behavior for all fields? Just add @Wither to the class declaration instead.

@Data and @Value

Both use cases discussed so far are so common that Lombok ships a couple of annotations to make them even shorter: Annotating a class with @Data will trigger Lombok to behave just as if it had been annotated with @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor. Likewise, using @Value will turn your class into an immutable (and final) one, also again as if it had been annotated with the list above.

Builder Pattern

Going back to our User example, if we want to create a new instance, we’ll need to use a constructor with up to six arguments. This is already a rather large number, which will get even worse if we further add attributes to the class. Also suppose we’d want to set some default values for the lastName and payingCustomer fields.

Lombok implements a very powerful @Builder feature, allowing us to use a Builder Pattern to create new instances. Let’s add it to our User class:

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter(AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PACKAGE)
@AllArgsConstructor
@ToString(exclude = {"password"})
@EqualsAndHashCode(of = {"email"})
@Builder
public class User {

    private @NonNull String email;

    private @NonNull byte[] password;

    private @NonNull String firstName;
    private @NonNull String lastName = "";

    private @NonNull Instant registrationTs;

    private boolean payingCustomer = false;

}

Now we are able to fluently create new users like this:

User user = User
        .builder()
            .email("miguel.garcia@toptal.com")
            .password("secret".getBytes(StandardCharsets.UTF_8))
            .firstName("Miguel")
            .registrationTs(Instant.now())
        .build();

It’s easy to imagine how convenient this construct becomes as our classes grow.

Delegation/Composition

If you want to follow the very sane rule of “favor composition over inheritance” that’s something Java does not really help with, verbosity wise. If you want to compose objects, you’d typically need to write delegating method calls all over the place.

Lombok proposes a solution for this via @Delegate. Let’s take a look at an example.

Imagine that we want to introduce a new concept of ContactInformation. This is some information our User has and we may want other classes to have too. We can then model this via an interface like this:

public interface HasContactInformation {

    String getEmail();
    String getFirstName();
    String getLastName();

}

We would then introduce a new ContactInformation class using Lombok:

import lombok.Data;

@Data
public class ContactInformation implements HasContactInformation {

    private String email;

    private String firstName;
    private String lastName;

}

And finally, we could refactor User to compose with ContactInformation and use Lombok to generate all the required delegating calls to match the interface contract:

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Delegate;

@Getter
@Setter(AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PACKAGE)
@AllArgsConstructor
@ToString(exclude = {"password"})
@EqualsAndHashCode(of = {"contactInformation"})
public class User implements HasContactInformation {

    @Getter(AccessLevel.NONE)
    @Delegate(types = {HasContactInformation.class})
    private final ContactInformation contactInformation = new ContactInformation();

    private @NonNull byte[] password;

    private @NonNull Instant registrationTs;

    private boolean payingCustomer = false;

}

Note how I did not need to write implementations for the methods of HasContactInformation: this is something we’re telling Lombok to do, delegating calls to our ContactInformation instance.

Also, because I do not want the delegated instance to be accessible from outside, I am customizing it with a @Getter(AccessLevel.NONE), effectively preventing getter generation for it.

Checked Exceptions

As we all know, Java differentiates between checked and unchecked exceptions. This is a traditional source for controversy and criticism to the language as exception handling sometimes gets too much in our way as a result, especially when dealing with APIs designed to throw checked exceptions and therefore forcing us developers to either catch them or declare our methods to throw them.

Consider this example:

public class UserService {

    public URL buildUsersApiUrl() {
        try {
            return new URL("https://apiserver.com/users");
        } catch (MalformedURLException ex) {
            // Malformed? Really?
            throw new RuntimeException(ex);
        }
    }

}

This is such a common pattern: We certainly know our URL is well formed, yet—because the URL constructor throws a checked exception—we are either forced to catch it or declare our method to throw it and put out callers in the same situation. Wrapping these checked exceptions inside a RuntimeException is a very extended practice. And this gets even worse if the number of checked exceptions we need to deal with grows as we code.

So this is exactly what Lombok’s @SneakyThrows is for, it’ll wrap any checked exceptions subject to be thrown in our method into a unchecked one and free us from the hassle:

import lombok.SneakyThrows;

public class UserService {

    @SneakyThrows
    public URL buildUsersApiUrl() {
        return new URL("https://apiserver.com/users");
    }

}

Logging

How often do you add logger instances to your classes like this? (SLF4J sample)

private static final Logger LOG = LoggerFactory.getLogger(UserService.class);

I’m going to guess quite a bit. Knowing this, the creators of Lombok implemented an annotation that creates a logger instance with a customizable name (defaults to log), supporting the most common logging frameworks on the Java platform. Just like this (again, SLF4J based):

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class UserService {

    @SneakyThrows
    public URL buildUsersApiUrl() {
        log.debug("Building users API URL");
        return new URL("https://apiserver.com/users");
    }

}

Annotating Generated Code

If we use Lombok to generate code, it may seem we’d lose the ability to annotate those methods since we’re not actually writing them. But this is not really true. Rather, Lombok allows us to tell it how we’d want generated code to be annotated, using a somewhat peculiar notation though, truth be told.

Consider this example, targeting the use of a dependency injection framework: We have a UserService class that uses constructor injection to get the references to a UserRepository and UserApiClient.

package com.mgl.toptal.lombok;

import javax.inject.Inject;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor(onConstructor = @__(@Inject))
public class UserService {

    private final UserRepository userRepository;
    private final UserApiClient userApiClient;

    // Instead of:
    // 
    // @Inject
    // public UserService(UserRepository userRepository,
    //                    UserApiClient userApiClient) {
    //     this.userRepository = userRepository;
    //     this.userApiClient = userApiClient;
    // }

}

The sample above shows how to annotate a generated constructor. Lombok allows us to do the same thing for generated methods and parameters as well.

Learning More

The Lombok usage explained in this post focuses on those features that I have personally found to be most useful over the years. However, there are many other features and customizations, available.

Lombok’s documentation is very informative and thorough. They have dedicated pages for every single feature (annotation) with very detailed explanations and examples. If you find this post interesting, I encourage you to dive deeper into lombok and its documentation to find out more.

The project site documents how to use Lombok in several different programming environments. In short, most popular IDEs (Eclipse, NetBeans and IntelliJ) are supported. I myself regularly switch from one to another on a per-project basis and use Lombok on all of them flawlessly.

Delombok!

Delombok is part of the “Lombok toolchain” and can come in very handy. What it does is basically generate the Java source code for your Lombok annotated code, performing the same operations the Lombok generated bytecode does.

This is a great option for people considering adopting Lombok but not quite sure yet. You may freely start using it and there’ll be no “vendor lock-in.” In case you or your team later regret the choice, you can always use delombok to generate the corresponding source code which you can then use without any remaining dependency on Lombok.

Delombok is also a great tool to learn exactly what Lombok will be doing. There are very easy ways to plug it into your build process.

Alternatives

There are many tools within the Java world that make a similar usage of annotation processors to enrich or modify your code at compile time, such as Immutables or Google Auto Value. These (and others, for sure!) overlap with Lombok feature-wise. I particularly like the Immutables approach a lot and have also used it in some projects.

It’s also worth noting that there are other great tools providing similar features for “bytecode enhancing”, such as Byte Buddy or Javassist. These typically work at runtime though, and comprise a world of their own beyond the scope of this post.

Concise Java

There are a number of modern JVM targeted languages that provide more idiomatic—or even language level—design approaches helping address some of the same issues. Surely Groovy, Scala, and Kotlin are nice examples. But if you are working on a Java-only project, then Lombok is a nice tool to help your programs be more concise, expressive, and maintainable.

About the author

Miguel García López, Spain
member since February 26, 2014
Miguel is a highly motivated, passionate software engineer who helps digital products and services come to life. He has gained broad experience over the years, working on embedded systems, back-end services, and modern web and mobile applications. He is a friendly and effective communicator who thrives in a distributed team, dedicated to improving people's lives and businesses through technology. [click to continue...]
Hiring? Meet the Top 10 Freelance Java Developers for Hire in November 2017

Comments

Ezequiel Matalon
Thanks for making Project Lombok popular and sperading existence, though it's a hack, it's something that makes code prettier and speeds up a lot the development
Meher Babu
Thanks for the nice Article on Lombok.
chakradhar reddy
In case of Android, it has some issues with generating constructor. Just thought this might help someone One needs to use it as @AllArgsConstructor(suppressConstructorProperties=true) Taken from http://stackoverflow.com/questions/27969416/allargsconstructor-from-lombok-is-not-found-by-android-studio
andries
I used Lombok now as a rule when it comes to doing java. But i found Kotlin, which is the ultimate bloat/verbosity free experience.
Dmitry Kravchenko
Wow! Excelent! Will use it from now on! :)
Miguel García López
So true! But if you need to stick to Java, as you said Lombok is a welcome tool.
Miguel García López
Indeed. Maybe, just as the accepted answer there suggests, It'd be better to achieve this (and avoid repetition) via Lombok's configuration system: https://projectlombok.org/features/configuration.html
Innovimax SARL
Nice! Although a typo slipped into the text : LoginResonse
Lê Anh Quân
Guys, somehow I feel that this whole Lombok idea is like putting another nail onto Java's coffin. 1. This meta-coding idea is like a confirmation that Java code will never be good enough and will stay verbose. And nothing guarrantee that there wont be no meta-meta-coding level to be added to the language 2. Playing around with the compiler like this will make the maintaining task a lot more difficult. What if one day you find out that your Java code need to be upgraded to version 11, and Lombok does not want to play well with the new compiler? 3. Yes, Java is very verbose and being less appealling each day. But efforts should be put into where Java code is weakest, like FP support, Generics, Async... Projects like Lombok are pulling the good effort away. Anyway, if I am to go with this idea, I would make it more "runtime" rather than "compile time" like this. Let the classes unchanged, but load them with the decorated version is a more healthy approach. Just my 2 cents
ADIO Kingsley O
Great post! However, at this rate, I think one might as well give Kotlin a chance. (https://kotlinlang.org/)
River Satya
Wow, great write-up and very interesting project. I'll definitely look into this next time I'm compelled to write Java :D.
Kambiz
We use Commons to do some of the above but it is not with annotations. I like the post...and would use Lombok. Having said that, altering bytecode might not pass some security architects requirements within the O/S Governance context. Other than that, Lombok looks awesome ESPECIALLY of the last part of the above, regarding DeLombok. Finally, given the blow, I think JSR's could be a good way of working in the Lombok philosophy to better Java as a platform.
Longstation
If we use lombok to avoid writing getters and setters (just use this as an example), would the IDE recognize this if I say A.set(xxx)? Would it give me error since I haven't compile the code and there is no setter?
Miguel García López
This is a late reply (sorry)... however: IDE's recognise Lombok "changes" without issues. I have used it with NetBeans, Eclipse and IntelliJ IDEA. For the last two some plugin needs to be installed though.
Miguel García López
These are very valid points indeed (and mine here a late reply!). I believe Lombok's features are something not really even eligible for an approvable JSR, although I'd love so. This would ease out dealing with (2) which can indeed become a future issue. At least, again, for (2) there's always the future-proof, safety net of de-Lombok. Very insightful comments anyway, thanks!
Longstation
Thanks!
comments powered by Disqus
Subscribe
The #1 Blog for Engineers
Get the latest content first.
No spam. Just great engineering posts.
The #1 Blog for Engineers
Get the latest content first.
Thank you for subscribing!
Check your inbox to confirm subscription. You'll start receiving posts after you confirm.
Trending articles
Relevant Technologies
About the author
Miguel García López
Java Developer
Miguel is a highly motivated, passionate software engineer who helps digital products and services come to life. He has gained broad experience over the years, working on embedded systems, back-end services, and modern web and mobile applications. He is a friendly and effective communicator who thrives in a distributed team, dedicated to improving people's lives and businesses through technology.