"After all, the engineers only needed to refuse to fix anything, and modern industry would grind to a halt." -Michael Lewis

Enable Massive Growth

Exploring Lombok: How to use @Builder, @Accessors, and @Wither for POJO Classes

Feb 2019

Lombok manipulates the output java bytecode .class files, and inserts boilerplate code that java developers are very familiar with repeating themselves on.

This post will address three wonderful features of Lombok: @Buidler, @Accessors, and @Wither.

@Builder

The Builder annotation is based off of the popular design pattern from Effective Java. Combine @Builder with @Getter and you can make a read only class that cannot be modified after construction. If all of the types within the class are immutable, then the class itself is immutable. Immutable objects are a lot easier to work with, particularly in concurrent programming.

This:

@Builder
public class Person {
    private String firstName;
    private String lastName;
    private int height;
}

Can be called anywhere in the code like so:

Person person = Person.builder()
                .firstName("Jack")
                .lastName("Bauer")
                .height(100)
                .build();

But in practice this isn't particularly helpful. Typically this is combined with @Getter to access an immutable object after building it:

@Builder
@Getter
public class Person {
    private String firstName;
    private String lastName;
    private int height;
}

Which can then be very practically applied like so:

    @Test
    public void testBuilder() {
        Person person = Person.builder()
                .firstName("Jack")
                .lastName("Bauer")
                .height(100)
                .build();

        assertEquals("Jack", person.getFirstName());
        assertEquals("Bauer", person.getLastName());
        assertEquals(100, person.getHeight());
    }

@Accessors

The accessors annotation is currently an "experimental" feature. The most powerful feature puts it in a similar camp to @Builder, which is it's chain option. This adjusts the behavior of setters to return this after setting the value, making modifications to existing objects more compact:

@Accessors(chain = true)
@Setter @Getter
public class Person {
    private String firstName;
    private String lastName;
    private int height;
}

....

@Test
public void testAccessors() {
    Person person = new Person();

    person.setFirstName("Jack")
        .setLastName("Bauer")
        .setHeight(100);

    assertEquals("Jack", person.getFirstName());
    assertEquals("Bauer", person.getLastName());
    assertEquals(100, person.getHeight());
}

This usage implies that the object is mutable, but still has valid use cases from where I sit.

@Wither

The wither annotation is also experimental, with a less favorable current view than accessors by the community. We use Wither exclusively on a field, at the moment, and when we use it in code it returns a clone of the object, with the only modified field being the field called by .with***(..). If we add a @Wither to our Person:

@Builder
@Getter
public class Person {

    @Wither
    private String firstName;

    private String lastName;

    private int height;
}

We can then see that it properly clones the object like so:

@Test
public void testWither() {
    Person person = Person.builder()
            .firstName("Jack")
            .lastName("Bauer")
            .height(100)
            .build();

    Person clonedPerson = person.withFirstName("Joe");

    assertEquals("Jack", person.getFirstName());
    assertEquals("Joe", clonedPerson.getFirstName());
}

This is another very useful annotation in concurrent programming. If you have a set of data that is far more often read than it is written, then the above immutable and easily clone-able Person is thread safe.

Nick Fisher is a software engineer in the Pacific Northwest. He focuses on building highly scalable and maintainable backend systems.