Introduction to Project Lombok – Lombok项目简介

最后修改: 2016年 6月 12日


1. Avoid Repetitive Code


Java is a great language, but it can sometimes get too verbose for common tasks we have to do in our code or compliance with some framework practices. This often doesn’t bring any real value to the business side of our programs, and that’s where Lombok comes in to make us more productive.


The way it works is by plugging into our build process and auto-generating Java bytecode into our .class files as per a number of project annotations we introduce in our code.


Including it in our builds, in whichever system we’re using, is very straight forward. Project Lombok’s project page has detailed instructions on the specifics. Most of my projects are maven based, so I just typically drop their dependency in the provided scope and I’m good to go:

无论我们使用哪种系统,将其纳入我们的构建中,都是非常直接的。Project Lombok的项目页面有详细的具体说明。我的大多数项目都是基于maven的,所以我通常只需把它们的依赖关系放在provided范围内,就可以了。


We can check for the most recent available version here.


Note that depending on Lombok won’t make users of our .jars depend on it as well, as it is a pure build dependency, not runtime.


2. Getters/Setters, Constructors – So Repetitive

2.获取器/设置器,构造器 – 如此重复

Encapsulating object properties via public getter and setter methods is such a common practice in the Java world, and lots of frameworks rely on this “Java Bean” pattern extensively (a class with an empty constructor and get/set methods for “properties”).

通过公共的getter和setter方法来封装对象属性是Java世界中的一种常见做法,很多框架都广泛依赖这种 “Java Bean “模式(一个具有空的构造函数和get/set方法的 “属性 “类)。

This is so common that most IDE’s support auto-generating code for these patterns (and more). However, this code needs to live in our sources and be maintained when a new property is added or a field renamed.


Let’s consider this class we want to use as a JPA entity:


public class User implements Serializable {

    private @Id Long id; // will be set when persisting

    private String firstName;
    private String lastName;
    private int age;

    public User() {

    public User(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;

    // getters and setters: ~30 extra lines of code

This is a rather simple class, but imagine if we had added the extra code for getters and setters. We would have ended up with a definition where there would be more boilerplate zero-value code than the relevant business information: “a User has first and last names, and age.”


Let’s now Lombok-ize this class:


@Getter @Setter @NoArgsConstructor // <--- THIS is it
public class User implements Serializable {

    private @Id Long id; // will be set when persisting

    private String firstName;
    private String lastName;
    private int age;

    public User(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;

By adding the @Getter and @Setter annotations, we told Lombok to generate these for all the fields of the class. @NoArgsConstructor will lead to an empty constructor generation.


Note that this is the whole class code, we’re not omitting anything unlike the version above with the // getters and setters comment. For a three relevant attributes class, this is a significant saving in code!

请注意,这是整个类的代码,我们没有省略任何东西,与上面带有// getters和setters注释的版本不同。对于一个有三个相关属性的类来说,这是一个相当大的代码节省!

If we further add attributes (properties) to our User class, the same will happen; we apply the annotations to the type itself so they will mind all fields by default.


What if we want to refine the visibility of some properties? For example, if we want to keep our entities’ id field modifiers package or protected visible because they are expected to be read, but not explicitly set by application code, we can just use a finer grained @Setter for this particular field:


private @Id @Setter(AccessLevel.PROTECTED) Long id;

3. Lazy Getter


Applications often need to perform expensive operations and save the results for subsequent use.


For instance, let’s say we need to read static data from a file or a database. It’s generally good practice to retrieve this data once, and then cache it to allow in-memory reads within the application. This saves the application from repeating the expensive operation.


Another common pattern is to retrieve this data only when it’s first needed. In other words, we only get the data when we call the corresponding getter the first time. We call this lazy-loading.


Let’s suppose that this data is cached as a field inside a class. The class must now make sure that any access to this field returns the cached data. One possible way to implement such a class is to make the getter method retrieve the data only if the field is null. We call this a lazy getter.

让我们假设这个数据被缓存在一个类里面的一个字段。这个类现在必须确保对这个字段的任何访问都能返回缓存的数据。实现这样一个类的一个可能的方法是让getter方法只在字段为null时才检索到数据。我们把这称为lazy getter

Lombok makes this possible with the lazy parameter in the @Getter annotation we saw above.


For example, consider this simple class:


public class GetterLazy {

    @Getter(lazy = true)
    private final Map<String, Long> transactions = getTransactions();

    private Map<String, Long> getTransactions() {

        final Map<String, Long> cache = new HashMap<>();
        List<String> txnRows = readTxnListFromFile();

        txnRows.forEach(s -> {
            String[] txnIdValueTuple = s.split(DELIMETER);
            cache.put(txnIdValueTuple[0], Long.parseLong(txnIdValueTuple[1]));

        return cache;

This reads some transactions from a file into a Map. Since the data in the file doesn’t change, we’ll cache it once and allow access via a getter.


If we now look at the compiled code of this class, we’ll see a getter method which updates the cache if it was null and then returns the cached data:


public class GetterLazy {

    private final AtomicReference<Object> transactions = new AtomicReference();

    public GetterLazy() {

    //other methods

    public Map<String, Long> getTransactions() {
        Object value = this.transactions.get();
        if (value == null) {
            synchronized(this.transactions) {
                value = this.transactions.get();
                if (value == null) {
                    Map<String, Long> actualValue = this.readTxnsFromFile();
                    value = actualValue == null ? this.transactions : actualValue;

        return (Map)((Map)(value == this.transactions ? null : value));

It’s interesting to point out that Lombok wrapped the data field in an AtomicReference. This ensures atomic updates to the transactions field. The getTransactions() method also makes sure to read the file if transactions is null.

有趣的是,Lombok将数据字段包裹在一个a href=”/java-atomic-variables”>AtomicReference.这确保了对transactions字段的原子更新。getTransactions()方法还确保在transactionsnull时读取该文件。

We discourage using the AtomicReference transactions field directly from within the class. We recommend using the getTransactions() method for accessing the field.


For this reason, if we use another Lombok annotation like ToString in the same class, it will use getTransactions() instead of directly accessing the field.


4. Value Classes/DTO’s


There are many situations in which we want to define a data type with the sole purpose of representing complex “values” as “Data Transfer Objects,” most of the time in the form of immutable data structures we build once and never want to change.

在很多情况下,我们想定义一种数据类型,其唯一目的是将复杂的 “值 “表示为 “数据传输对象”,大多数情况下是以不可改变的数据结构的形式,我们建立一次就永远不想再改变。

We design a class to represent a successful login operation. We want all fields to be non-null and objects be immutable so that we can thread-safely access its properties:


public class LoginResult {

    private final Instant loginTs;

    private final String authToken;
    private final Duration tokenValidity;
    private final URL tokenRefreshUrl;

    // constructor taking every field and checking nulls

    // read-only accessor, not necessarily as get*() form

Again, the amount of code we would have to write for the commented sections would be of a much larger volume than the information we want to encapsulate needs to be. We can use Lombok to improve this:


@Accessors(fluent = true) @Getter
public class LoginResult {

    private final @NonNull Instant loginTs;

    private final @NonNull String authToken;
    private final @NonNull Duration tokenValidity;
    private final @NonNull URL tokenRefreshUrl;


Once we add the @RequiredArgsConstructor annotation, we’ll get a constructor for all the final fields int the class, just as we declared them. Adding @NonNull to attributes makes our constructor check for nullability and throw NullPointerExceptions accordingly. This would also happen if the fields were non-final and we added @Setter for them.


Do we want the boring old get*() form for our properties? Since we added @Accessors(fluent=true) in this example, “getters” would have the same method name as the properties; getAuthToken() simply becomes authToken().

我们是否想让我们的属性采用无聊的老式get*()形式?由于我们在这个例子中添加了@Accessors(fluent=true),”getters “将拥有与属性相同的方法名; getAuthToken()简单地变成authToken()

This “fluent” form would apply to non-final fields for attribute setters, as well as allow for chained calls:

这种 “流畅 “的形式将适用于属性设置器的非最终字段,并允许链式调用。

// Imagine fields were no longer final now
return new LoginResult()
  . // and so on

5. Core Java Boilerplate


Another situation where we end up writing code we need to maintain is when generating the toString(), equals() and hashCode() methods. IDEs try to help with templates for auto-generating these in terms of our class attributes.


We can automate this by means of other Lombok class-level annotations:


  • @ToString: will generate a toString() method including all class attributes. No need to write one ourselves and maintain it as we enrich our data model.
  • @EqualsAndHashCode: will generate both equals() and hashCode() methods by default considering all relevant fields, and according to very well though semantics.

These generators ship very handy configuration options. For example, if our annotated classes take part of a hierarchy, we can just use the callSuper=true parameter and parent results will be considered when generating the method’s code.


To demonstrate this, let’s say we had our User JPA entity example include a reference to events associated to this user:


@OneToMany(mappedBy = "user")
private List<UserEvent> events;

We wouldn’t want to have the whole list of events dumped whenever we call the toString() method of our User, just because we used the @ToString annotation. Instead, we can parameterize it like this, @ToString(exclude = {“events”}), and that won’t happen. This is also helpful to avoid circular references if, for example, UserEvents had a reference to a User.

我们不想因为使用了@ToString注解,而在我们调用用户的toString()方法时,整个事件列表被转储。相反,我们可以像这样将其参数化, @ToString(exclude = {“events”}),这样就不会发生。这也有助于避免循环引用,例如,如果UserEvents有一个对User的引用。

For the LoginResult example, we may want to define equality and hash code calculation just in terms of the token itself and not the other final attributes in our class. Then we can simply write something like @EqualsAndHashCode(of = {“authToken”}).

对于LoginResult的例子,我们可能想只用令牌本身来定义平等和哈希代码计算,而不是用我们类中的其他最终属性。那么我们可以简单地写一些东西,如@EqualsAndHashCode(of = {“authToken”})

If the features from the annotations we’ve reviewed so far are of interest, we may want to examine @Data and @Value annotations too, as they behave as if a set of them had been applied to our classes. After all, these discussed usages are very commonly put together in many cases.


5.1. (Not) Using the @EqualsAndHashCode With JPA Entities

5.1. (不)使用@EqualsAndHashCode与JPA实体

Whether we should use the default equals() and hashCode() methods, or create custom ones for the JPA entities, is an often discussed topic among developers. There are multiple approaches we can follow, each having its pros and cons.


By default, @EqualsAndHashCode includes all non-final properties of the entity class. We can try to “fix” this by using the onlyExplicitlyIncluded attribute of the @EqualsAndHashCode to make Lombok use only the entity’s primary key. Still, the generated equals() method can cause some issues. Thorben Janssen explains this scenario in greater detail in one of his blog posts.

默认情况下,@EqualsAndHashCode包括实体类的所有非最终属性。我们可以尝试通过使用@EqualsAndHashCodeonlyExplicitlyIncluded属性来 “解决 “这个问题,使Lombok只使用实体的主键。不过,生成的equals()方法还是会引起一些问题。Thorben Janssen在他的一篇博文中更详细地解释了这种情况。

In general, we should avoid using Lombok to generate the equals() and hashCode() methods for our JPA entities.


6. The Builder Pattern


The following could make for a sample configuration class for a REST API client:

下面的内容可以作为REST API客户端的一个配置类样本。

public class ApiClientConfiguration {

    private String host;
    private int port;
    private boolean useHttps;

    private long connectTimeout;
    private long readTimeout;

    private String username;
    private String password;

    // Whatever other options you may thing.

    // Empty constructor? All combinations?

    // getters... and setters?

We could have an initial approach based on using the class default empty constructor and providing setter methods for every field; however, we ideally want configurations not to be re-set once they’ve been built (instantiated), effectively making them immutable. Therefore, we want to avoid setters, but writing such a potentially long args constructor is an anti-pattern.


Instead, we can tell the tool to generate a builder pattern, which negates us from having to write an extra Builder class and the associated fluent setter-like methods by simply adding the @Builder annotation to our ApiClientConfiguration:


public class ApiClientConfiguration {

    // ... everything else remains the same


Leaving the class definition such as above (no declare constructors or setters + @Builder), we can end up using it as:


ApiClientConfiguration config = 

7. Checked Exceptions Burden


Lots of Java APIs are designed so that they can throw a number of checked exceptions; client code is forced to either catch or declare to throws. How many times have we turned these exceptions we know won’t happen into something like this?:

很多Java API被设计成可以抛出一些被检查的异常;客户端代码被迫要么catch,要么声明到throws。我们有多少次把这些我们知道不会发生的异常变成了这样的东西?

public String resourceAsString() {
    try (InputStream is = this.getClass().getResourceAsStream("sure_in_my_jar.txt")) {
        BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
        return br.lines().collect(Collectors.joining("\n"));
    } catch (IOException | UnsupportedCharsetException ex) {
        // If this ever happens, then its a bug.
        throw new RuntimeException(ex); <--- encapsulate into a Runtime ex.

If we want to avoid this code pattern because the compiler won’t be happy (and we know the checked errors cannot happen), use the aptly named @SneakyThrows:


public String resourceAsString() {
    try (InputStream is = this.getClass().getResourceAsStream("sure_in_my_jar.txt")) {
        BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
        return br.lines().collect(Collectors.joining("\n"));

8. Ensure Our Resources Are Released


Java 7 introduced the try-with-resources block to ensure that our resources being held by instances of anything implementing java.lang.AutoCloseable are released when exiting.

Java 7 引入了 try-with-resources 块,以确保我们被任何实现 java.lang.AutoCloseable 的实例所持有的资源在退出时被释放。

Lombok provides an alternative and more flexible way of achieving this via @Cleanup. We can use it for any local variable whose resources we want to make sure are released. There’s no need for them to implement any particular interface, we’ll just get the close() method called:


@Cleanup InputStream is = this.getClass().getResourceAsStream("res.txt");

Our releasing method has a different name? No problem, we just customize the annotation:


@Cleanup("dispose") JFrame mainFrame = new JFrame("Main Window");

9. Annotate Our Class to Get a Logger


Many of us add logging statements to our code sparingly by creating an instance of a Logger from our framework of choice. Let’s say SLF4J:


public class ApiClientConfiguration {

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

    // LOG.debug(),, ...


This is such a common pattern that Lombok developers have simplified it for us:


@Slf4j // or: @Log @CommonsLog @Log4j @Log4j2 @XSlf4j
public class ApiClientConfiguration {

    // log.debug(),, ...


Many logging frameworks are supported, and of course we can customize the instance name, topic, etc.


10. Write Thread-Safer Methods


In Java, we can use the synchronized keyword to implement critical sections; however, this is not a 100% safe approach. Other client code can eventually also synchronize on our instance, potentially leading to unexpected deadlocks.


This is where @Synchronized comes in. We can annotate our methods (both instance and static) with it, and we’ll get an auto-generated, private, unexposed field that our implementation will use for locking:

这就是@Synchronized 的用处。我们可以用它来注解我们的方法(包括实例和静态方法),我们将得到一个自动生成的、私有的、未暴露的字段,我们的实现将使用它来锁定。

public /* better than: synchronized */ void putValueInCache(String key, Object value) {
    // whatever here will be thread-safe code

11. Automate Objects Composition


Java doesn’t have language level constructs to smooth out a “favor composition inheritance” approach. Other languages have built-in concepts such as Traits or Mixins to achieve this.

Java没有语言层面的构造来平滑 “有利于组成继承 “的方法。其他语言有内置的概念,如TraitsMixins来实现这一点。

Lombok’s @Delegate comes in very handy when we want to use this programming pattern. Let’s consider an example:


  • We want Users and Customers to share some common attributes for naming and phone number.
  • We define both an interface and an adapter class for these fields.
  • We’ll have our models implement the interface and @Delegate to their adapter, effectively composing them with our contact information.

First, let’s define an interface:


public interface HasContactInformation {

    String getFirstName();
    void setFirstName(String firstName);

    String getFullName();

    String getLastName();
    void setLastName(String lastName);

    String getPhoneNr();
    void setPhoneNr(String phoneNr);


Now an adapter as a support class:


public class ContactInformationSupport implements HasContactInformation {

    private String firstName;
    private String lastName;
    private String phoneNr;

    public String getFullName() {
        return getFirstName() + " " + getLastName();

Now for the interesting part; see how easy it is to compose contact information into both model classes:


public class User implements HasContactInformation {

    // Whichever other User-specific attributes

    @Delegate(types = {HasContactInformation.class})
    private final ContactInformationSupport contactInformation =
            new ContactInformationSupport();

    // User itself will implement all contact information by delegation

The case for Customer would be so similar that we can omit the sample for brevity.


12. Rolling Lombok Back?


Short answer: Not at all really.


There may be a worry that if we use Lombok in one of our projects, we may later want to rollback that decision. The potential problem could be that there are a large number of classes annotated for it. In this case, we’re covered thanks to the delombok tool from the same project.


By delombok-ing our code, we get auto-generated Java source code with exactly the same features from the bytecode Lombok built. We can then simply replace our original annotated code with these new delomboked files, and no longer depend on it.


This is something we can integrate in our build.


13. Conclusion


There are still some other features we haven’t presented in this article. We can take a deeper dive into the feature overview for more details and use cases.


Furthermore, most functions we’ve shown have a number of customization options that we may find handy. The available built-in configuration system could also help us with that.


Now that we can give Lombok a chance to get into our Java development tool set, we can boost our productivity.


The example code can be found in the GitHub project.