Piotr Grosicki
Avra Sp. z o.o.
Piotr GrosickiSenior Java Software Engineer @ Avra Sp. z o.o.

Java Lombok. Why should we care?

Lombok is a Java library that helps us avoid writing (or generating by IDE) boilerplate parts of code.
20.12.20176 min
Java Lombok. Why should we care?

How does it work?


To understand how Lombok works, let's go over the most important steps of Java compilation process. First, a compiler takes source files and generates an AST (abstract source tree) that can be treated as Java's equivalent of DOM. The compiler calls custom registered annotation processors for the generated AST. The processors can additionally validate classes and/or generate new resources (of course new resources must be converted to AST so here we are going back to the first step). Once all processors finish their work, the compiler can finally generate byte code that will be understandable by Java Virtual Machine.

As you've probably, guessed Lombok is an annotation processor - it has full access to the generated source tree. While annotation processors usually generate new source files, Lombok modifies existing ones by adding new fields or methods.


How to install it?


Like all of external libraries, Lombok must be included in our project somehow. In this article we will use Gradle as dependency manager so we can simply add following lines to build.gradle:

dependencies {
    compileOnly 'org.projectlombok:lombok:1.16.16'
}


NOTE: Since Lombok is an annotation processor, we can use 'compileOnly' scope because we don't need to include it in our classpath

At this step, the compiler knows how to deal with Lombok annotations, but when we develop our applications we use (or at least we should use) IDEs. Initially, the IDEs are not aware of methods that will be generated during the compilation. To make your favourite IDE understand that connection between annotation and new code, you have to install one of Lombok's plugins, e. g. for IntelliJ IDEA.



Lombok Island (source)

What does it do?


There are a lot of annotations provided by Lombok (here is a full list). Everything is better with real examples so there are a few of them below:

1. Mutable Entities

Such classes contain a lot of boilerplate code such as Constructors, Getters, Setters, equals, hashCode, toString. Of course, we can generate it using our IDE (as I did), but in our example we have 86 lines of code of class that has only four fields - everything else is code that we have to write to make the object of this class usable.

package pl.avra.lombok;

public class Entity {

    private String field1;
    private String field2;
    private String field3;
    private int field4;

    public Entity() {
    }

    public Entity(String field1, String field2, String field3, int field4) {
        this.field1 = field1;
        this.field2 = field2;
        this.field3 = field3;
        this.field4 = field4;
    }

    public String getField1() {
        return field1;
    }

    public void setField1(String field1) {
        this.field1 = field1;
    }

    public String getField2() {
        return field2;
    }

    public void setField2(String field2) {
        this.field2 = field2;
    }

    public String getField3() {
        return field3;
    }

    public void setField3(String field3) {
        this.field3 = field3;
    }

    public int getField4() {
        return field4;
    }

    public void setField4(int field4) {
        this.field4 = field4;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Entity entity = (Entity) o;

        if (field4 != entity.field4) return false;
        if (field1 != null ? !field1.equals(entity.field1) : entity.field1 != null) return false;
        if (field2 != null ? !field2.equals(entity.field2) : entity.field2 != null) return false;
        return field3 != null ? field3.equals(entity.field3) : entity.field3 == null;
    }

    @Override
    public int hashCode() {
        int result = field1 != null ? field1.hashCode() : 0;
        result = 31 * result + (field2 != null ? field2.hashCode() : 0);
        result = 31 * result + (field3 != null ? field3.hashCode() : 0);
        result = 31 * result + field4;
        return result;
    }

    @Override
    public String toString() {
        return "Entity{" +
                "field1='" + field1 + '\'' +
                ", field2='" + field2 + '\'' +
                ", field3='" + field3 + '\'' +
                ", field4=" + field4 +
                '}';
    }

}


After using Lombok we end up with:

package pl.avra.lombok;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class EntityWithLombok {

    private String field1;
    private String field2;
    private String field3;
    private int field4;

}



2. Immutable Classes

In immutable classes we do not want to allow anyone to modificate our fields so we can't create setters and not required constructors. 

package pl.avra.lombok;

public class ImmutableClass {

    private final String field1;
    private final String field2;
    private final String field3;
    private final int field4;

    public ImmutableClass(String field1, String field2, String field3, int field4) {
        this.field1 = field1;
        this.field2 = field2;
        this.field3 = field3;
        this.field4 = field4;
    }

    public String getField1() {
        return field1;
    }

    public String getField2() {
        return field2;
    }

    public String getField3() {
        return field3;
    }

    public int getField4() {
        return field4;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ImmutableClass that = (ImmutableClass) o;

        if (field4 != that.field4) return false;
        if (!field1.equals(that.field1)) return false;
        if (!field2.equals(that.field2)) return false;
        return field3.equals(that.field3);
    }

    @Override
    public int hashCode() {
        int result = field1.hashCode();
        result = 31 * result + field2.hashCode();
        result = 31 * result + field3.hashCode();
        result = 31 * result + field4;
        return result;
    }

    @Override
    public String toString() {
        return "ImmutableClass{" +
                "field1='" + field1 + '\'' +
                ", field2='" + field2 + '\'' +
                ", field3='" + field3 + '\'' +
                ", field4=" + field4 +
                '}';
    }
}
​


In Lombok we can replace everything but fields declarations with only one annotation:

package pl.avra.lombok;

import lombok.Value;

@Value
public class ImmutableClassLombok {

    String field1;
    String field2;
    String field3;
    int field4;

}
​


NOTE: We don't have to declare fields visibility, @Value will make them private and final for us.

3. Loggers

Many classes, such as services, need to log something. To achieve it we can use one of logging libraries, e. g. slf4j. Here's how to create a new logger with class prefix:

package pl.avra.lombok;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class UsingLogger {
    
    private static final Logger log = LoggerFactory.getLogger(UsingLogger.class);
    
    public void example() {
        log.info("some log message");
    }

}
​


Lombok has one annotation that makes this declaration and initialization for you.

package pl.avra.lombok;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class UsingLoggerLombok {

    public void example() {
        log.info("some log message");
    }

}
​



4. Final local variables

Since Java is strongly typed language we have to specify type for every variable.

package pl.avra.lombok;

import java.util.ArrayList;
import java.util.List;

public class LocalVariable {

    public void example1() {
        final SomeFactoryThatCreatesReallyUsefulThings someFactoryThatCreatesReallyUsefulThings = new SomeFactoryThatCreatesReallyUsefulThings();
    }

    public void example2() {
        List<SomeReallyLongNameOfValueObject> valueObjects = new ArrayList<>();
        valueObjects.add(new SomeReallyLongNameOfValueObject());
        valueObjects.add(new SomeReallyLongNameOfValueObject());
        valueObjects.add(new SomeReallyLongNameOfValueObject());
        for (final SomeReallyLongNameOfValueObject valueObject : valueObjects) {
            System.out.println(valueObject);
        }
    }

}


For final variables, we can infer type from initialization expression. Lombok provides val as a solution.

package pl.avra.lombok;

import java.util.ArrayList;
import java.util.List;

import lombok.val;

public class LocalVariableLombok {

    public void example1() {
        val someFactoryThatCreatesReallyUsefulThings = new SomeFactoryThatCreatesReallyUsefulThings();
    }

    public void example2() {
        List<SomeReallyLongNameOfValueObject> valueObjects = new ArrayList<>();
        valueObjects.add(new SomeReallyLongNameOfValueObject());
        valueObjects.add(new SomeReallyLongNameOfValueObject());
        valueObjects.add(new SomeReallyLongNameOfValueObject());
        for (val valueObject : valueObjects) {
            System.out.println(valueObject);
        }
    }

}
​

Go clean your code


Now you know how Lombok can increase your productivity and make your code cleaner. It's not a tutorial, so we have discussed only the basic use of annotations. Note that most of them have parameters to generate more 'suitable' code for you (e. g. Lombok can add @Autowired annotation for generated constructors). 

Hope you'll think about Lombok as a nice boilerplate generator to use in your next project, or maybe you want to migrate your current one?

Official page of the project.


Piotr Grosicki
Senior Java Developer at Avra Sp. z o. o.
<p>Loading...</p>