Lombok advanced features

You are currently viewing Lombok advanced features
Lombok advanced features & annotations

Lombok advanced features val, var, @NonNull, @Cleanup, @Data, @Value, @Builder, @Singular, @Getter(lazy=true), @Log, @UtilityClass and many more are great tools for any developer. Using these you can shorten your code and get great level of automation for recurring tasks.

Project Lombok is a very simple to use java library which is added as a dependency into source code and annotation processing is enabled into IDE. Magic happens at compile time, the generated binary files has all the auto generated code in form of bytecode.

Introduction to Project Lombok

Project Lombok is a library tool for Java, it is used to remove/minimize the boilerplate code and reduce the development time by just using some of its own annotations. Lombok increases the source code readability to great extend and reduces required space. Lombok’s all boilerplate code is added at the compile time in the ‘.class’ file only. Since this additional code is not present in our source code files, which reduces the lines of code (LOC) and helps in readability & maintainability.

Lombok Maven dependency

In order to add lombok project to the classpath of your Java Project, add maven dependency in your pom.xml

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>provided</scope>
</dependency>

Lombok Gradle dependency

For adding lombok project to the classpath of Java Project, add gradle dependency to build.gradle file.

providedCompile group: 'org.projectlombok', name: 'lombok'

Project Lombok Annotations Examples

Lombok Annotations are used in a number of ways. Every annotation has special purpose and a defined way of usage. Most usually annotations are explained with details and well defined examples.

1. val

Lombok’s val can be used as the type of a local variable declaration instead of actually writing the type. When you do this, the type will be inferred from the initializer expression. The local variable will also be made final. This feature works on local variables and on foreach loops only, not on fields. The initializer expression is required.

val is actually a ‘type’ of sorts, and exists as a real class in the lombok package. You must import it for val to work (or use lombok.val as the type). The existence of this type on a local variable declaration triggers both the adding of the final keyword as well as copying the type of the initializing expression which overwrites the ‘fake’ val type.

public class ValExample { 
  public String example() {
    val example = new ArrayList<String>();
    example.add("Hello, World!");
    val foo = example.get(0);
    return foo.toLowerCase();
  }
  
  public void example2() {
    val map = new HashMap<Integer, String>();
    map.put(0, "zero");
    map.put(5, "five");
    for (val entry : map.entrySet()) {
      System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
    }
  }
}

2. var

Lombok’s var works exactly like val, except the local variable is not marked as final.

The type is still entirely derived from the mandatory initialized expression, and any further assignments, while now legal (because the variable is no longer final), aren’t looked at to determine the appropriate type.
For example, var x = "Hello"; x = Color.RED; does not work; the type of x will be inferred to be java.lang.String and thus, the x = Color.RED assignment will fail. If the type of x was inferred to be java.lang.Object this code would have compiled, but that’s not howvar works.

3. @NonNull

You can use @NonNull lombok annotation on the parameter of a method or constructor to have lombok generate a null-check statement for you.

Lombok has always treated various annotations generally named @NonNull on a field as a signal to generate a null-check if lombok generates an entire method or constructor for you, via for example @Data. Now, however, using lombok’s own @lombok.NonNull on a parameter results in the insertion of just the null-check statement inside your own method or constructor.

The null-check looks like if (param == null) throw new NullPointerException("param is marked @NonNull but is null"); and will be inserted at the very top of your method. For constructors, the null-check will be inserted immediately following any explicit this() or super() calls.

If a null-check is already present at the top, no additional null-check will be generated.

public NonNullExample(@NonNull Person person) {
  super("Hello");
  this.name = person.getName();
}

4. @Cleanup

Lombok’s @Cleanup annotation is used to ensure a given resource is automatically cleaned up before the code execution path exits your current scope. You do this by annotating any local variable declaration with the @Cleanup annotation like:
@Cleanup InputStream in = new FileInputStream("some/file");
As a result, at the end of the scope you’re in, in.close() is called. This call is guaranteed to run by way of a try/finally construct. Look at the example below to see how this works.

If the type of object you’d like to cleanup does not have a close() method, but some other no-argument method, you can specify the name of this method like so:
@Cleanup("dispose") org.eclipse.swt.widgets.CoolBar bar = new CoolBar(parent, 0);
By default, the cleanup method is presumed to be close(). A cleanup method that takes 1 or more arguments cannot be called via @Cleanup.

import lombok.Cleanup;
import java.io.*;

public class CleanupExample {
  public static void main(String[] args) throws IOException {
    @Cleanup InputStream in = new FileInputStream(args[0]);
    @Cleanup OutputStream out = new FileOutputStream(args[1]);
    byte[] b = new byte[10000];
    while (true) {
      int r = in.read(b);
      if (r == -1) break;
      out.write(b, 0, r);
    }
  }
}

5. @Data lombok

@Data Lombok annotation is a convenient shortcut annotation that bundles the features of @ToString,  @EqualsAndHashCode,  @Getter / @Setter and @RequiredArgsConstructor together: In other words, @Data generates all the boilerplate that is normally associated with simple POJOs (Plain Old Java Objects) and beans: getters for all fields, setters for all non-final fields, and appropriate toStringequals and hashCode implementations that involve the fields of the class, and a constructor that initializes all final fields, as well as all non-final fields with no initializer that have been marked with @NonNull, in order to ensure the field is never null.

@Data is like having implicit @Getter@Setter@ToString@EqualsAndHashCode and @RequiredArgsConstructor annotations on the class (except that no constructor will be generated if any explicitly written constructors already exist). However, the parameters of these annotations (such as callSuperincludeFieldNames and exclude) cannot be set with @Data. If you need to set non-default values for any of these parameters, just add those annotations explicitly; @Data is smart enough to defer to those annotations.

All generated getters and setters will be public. To override the access level, annotate the field or class with an explicit @Setter and/or @Getter annotation. You can also use this annotation (by combining it with AccessLevel.NONE) to suppress generating a getter and/or setter altogether.

All fields marked as transient will not be considered for hashCode and equals. All static fields will be skipped entirely (not considered for any of the generated methods, and no setter/getter will be made for them).

If the class already contains a method with the same name and parameter count as any method that would normally be generated, that method is not generated, and no warning or error is emitted. For example, if you already have a method with signature equals(AnyType param), no equals method will be generated, even though technically it might be an entirely different method due to having different parameter types. The same rule applies to the constructor (any explicit constructor will prevent @Data from generating one), as well as toStringequals, and all getters and setters. You can mark any constructor or method with @lombok.experimental.Tolerate to hide them from lombok.

@Data can handle generics parameters for fields just fine. In order to reduce the boilerplate when constructing objects for classes with generics, you can use the staticConstructor parameter to generate a private constructor, as well as a static method that returns a new instance. This way, javac will infer the variable name. Thus, by declaring like so: @Data(staticConstructor="of") class Foo<T> { private T x;} you can create new instances of Foo by writing: Foo.of(5); instead of having to write: new Foo<Integer>(5);

import lombok.AccessLevel;
import lombok.Setter;
import lombok.Data;
import lombok.ToString;

@Data public class DataExample {
  private final String name;
  @Setter(AccessLevel.PACKAGE) private int age;
  private double score;
  private String[] tags;
  
  @ToString(includeFieldNames=true)
  @Data(staticConstructor="of")
  public static class Exercise<T> {
    private final String name;
    private final T value;
  }
}

6. @Value

Lombok’s @Value is the immutable variant of @Data; all fields are made private and final by default, and setters are not generated. The class itself is also made final by default, because immutability is not something that can be forced onto a subclass. Like @Data, useful toString()equals() and hashCode() methods are also generated, each field gets a getter method, and a constructor that covers every argument (except final fields that are initialized in the field declaration) is also generated.

In practice, @Value is shorthand for: final @ToString @EqualsAndHashCode @AllArgsConstructor @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @Getter, except that explicitly including an implementation of any of the relevant methods simply means that part won’t be generated and no warning will be emitted. For example, if you write your own toString, no error occurs, and lombok will not generate a toString. Also, any explicit constructor, no matter the arguments list, implies lombok will not generate a constructor. If you do want lombok to generate the all-args constructor, add @AllArgsConstructor to the class. You can mark any constructor or method with @lombok.experimental.Tolerate to hide them from lombok.

It is possible to override the final-by-default and private-by-default behavior using either an explicit access level on a field, or by using the @NonFinal or @PackagePrivate annotations.
It is possible to override any default behavior for any of the ‘parts’ that make up @Value by explicitly using that annotation.

import lombok.AccessLevel;
import lombok.experimental.NonFinal;
import lombok.experimental.Value;
import lombok.experimental.Wither;
import lombok.ToString;

@Value public class ValueExample {
  String name;
  @Wither(AccessLevel.PACKAGE) @NonFinal int age;
  double score;
  protected String[] tags;
  
  @ToString(includeFieldNames=true)
  @Value(staticConstructor="of")
  public static class Exercise<T> {
    String name;
    T value;
  }
}

7. Lombok Builder

Lombok builder annotation is the most widely used annotation, a class annotated with @Builder does a lot of work automatically for you. Following tasks are automatically performed by lombok builder annotation:

  • An inner static class, with the same type arguments as the static method (called the builder) is created.
  • In the builder: One private non-static and non-final field for each one of the parameters of the target class.
  • In the builder: A private no-args empty constructor is automatically created.
  • In the builder: A setter method for each one of the parameters with the same type of original parameter and using parameter’s name (not adding any prefix like set). Each method returns the builder object which helps in setters calls which can be chained.
  • In the builder: The most important method is build() which calls the method and builds the object using all passed fields, It returns the same type as the target.
  • In the builder:  Detail toString() implementation including all fields by default.
  • A builder() method, which is responsible for creating a new instance of the builder.

7.1 Lombok builder example in Java

On class level you should have annotation @Builder. The with same class name, you can call the method builder() which is followed by initialization of fields. At the end of the builder’s field setter chain, the method build() is called which builds the instance using all supplied fields values and returns the object with the same type on which class the builder() method is called.

Student st = Student.builder().name("Adam").admissionNo(1001).build();

7.2 Lombok builder default

Lombok builder default is used to initialize the fields when the build() method is called, without it you can’t have any default initialization while using Lombok Builder. All the fields annotated with @Builder.Default annotation must have an expression for initialization. This  expression is used in default initialization if during builder call, no value is supplied.

Lombok builder default Example

Following is the example to set default values for different type of fields

@Data
@Builder
public class Student {
   @Builder.Default
   int admissionNo = 0;
   String name;
   boolean needTransport;
   boolean stayingInHostal;
   @Builder.Default
   String admissionReference = "Direct";
   @Builder.Default
   BigDecimal fees = BigDecimal.ZERO;
   @Builder.Default
   Set<String> addresses = new HashSet<>();
}

7.3 Lombok @builder list

Setting a list or map in the builder is very easy. In Student class if we have a list of addresses the this list can be initialized either using @Builder.Default annotation for default initialization or in builder method we can pass a list of addresses while building the object using lombok builder, as shown in following example

  List<String> list = new ArrayList<>();
  list.add(‘address1”);
  list.add(‘address2”);
  Student st = Student.builder().name("Adam").admissionNo(1001).addresses(list).build();

7.4 Lombok builder custom build method

Sometimes you may require some customization in the builder method. For example, putting a validation on permissible values, driving a value from other fields, one of multiple fields to be set only.

To create a custom build method, create a static builder class under the same pojo bean with the same name as the class and add a suffix “Builder”. If the class name is ‘Student” then the static builder class name will be ‘StudentBuilder’. Now add the properties you want to validate or restrict and add required validation methods. In next section, you can see how you can create custom build method as well as how you can make few fields as mandatory.

Lombok builder mandatory fields Example

In the following example, we have 3 validations with students. A student’s name can’t be null which is done using the help of NonNull and a student’s name can’t accept blank strings (custom validation). A student can either stay in a hostel or needs a transport, but a student can’t have both (custom validation).

@Data
@Builder
public class Student {

  @Builder.Default
  int admissionNo = 0;
  @lombok.NonNull
  String name;
  boolean needTransport;
  boolean stayingInHostal;
  @Builder.Default
  String admissionReference = "Direct";
  @Builder.Default
  BigDecimal fees = BigDecimal.ZERO;
  @Builder.Default
  Set<String> addresses = new HashSet<>();

  public static class StudentBuilder {
     boolean needTransport;
     boolean stayingInHostal;
     String name;

     public StudentBuilder name(String name){
        this.name = name;
        if (name == "") {
           throw new IllegalStateException("Blank value is not acceptable in 'Name' ");
        }
        return this;
     }


     public StudentBuilder needTransport(boolean needTransport) {
        this.needTransport = needTransport;
        validateNameOrAddmissionNo();
        return this;
     }

     public StudentBuilder stayingInHostal(boolean stayingInHostal) {
        this.stayingInHostal = stayingInHostal;
        validateNameOrAddmissionNo();
        return this;
     }

     private void validateNameOrAddmissionNo() {
        if (needTransport && stayingInHostal) {
           throw new IllegalStateException("'needTransport' or 'stayingInHostal' both can't be true");
        }
     }
  }
}

7.5 Lombok builder with prefix

Lombok builder has a prefix option with default as blank string. If you want to pre-append any string for every auto-generated method, then you can use this option. For example if we want to have a builder, whose all methods start with “with” then we will pass “setterPrefix” text while using the builder annotation, like this

@Data
@Builder(setterPrefix = "with")
public class Student {

  String name;
  String admissionNo;
}

Then while calling the builder, you can see the methods has prefix as “with”

Student st = Student.builder().withName("").withAdmissionNo(1001).build();

7.6 Lombok constructor annotations

To create constructors, lombok provides 3 annotations. @NoArgsConstructor, @AllArgsConstructor and @RequiredArgsConstructor. Following is the explanation for individual annotation:

@NoArgsConstructor will generate a default constructor without any parameter.

@AllArgsConstructor will generate a constructor with all parameters in the sequence, they are present in class.

@RequiredArgsConstructor will generate a constructor for only required fields which have @NotNull annotation.

Lombok builder and constructor example

In the following code, we have applied all 3 Lombok constructor annotations to the student class, at the top level of class. Every annotation should be specified in a new line, so that Lombok can identify it clearly.

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@RequiredArgsConstructor
public class Student {
  @Builder.Default
  int admissionNo = 0;
  @lombok.NonNull
  String name;
  boolean needTransport;
  boolean stayingInHostal;
  @Builder.Default
  String admissionReference = "Direct";
  @Builder.Default
  BigDecimal fees = BigDecimal.ZERO;
  @Builder.Default
  Set<String> addresses = new HashSet<>();
}

With using all 3 annotations, now ‘student’ class will have the following three constructors.

// default constructor
Student()
// constructor with all fields
Student(int, String, boolean, boolean, String, BigDecimal, Set<String>)
// constructor with only required fields
Student(String)

7.7 lombok builder public constructor or private constructor

In all 3 constructor annotations, @NoArgsConstructor, @AllArgsConstructor and @RequiredArgsConstructor. You can mention the access level that what kind of access modifier should be applied to the constructor. The possible AccessLevel values are: PUBLIC, MODULE, PROTECTED, PACKAGE, PRIVATE and NONE.

Example of using AccessLevel can be seen below:

@Data
@Builder
@AllArgsConstructor(access = AccessLevel.PUBLIC)
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@RequiredArgsConstructor(access = AccessLevel.MODULE)
public class Student {
  @Builder.Default
  int admissionNo = 0;
  @lombok.NonNull
  String name;
  boolean needTransport;
  boolean stayingInHostal;
  @Builder.Default
  String admissionReference = "Direct";
  @Builder.Default
  BigDecimal fees = BigDecimal.ZERO;
  @Builder.Default
  Set<String> addresses = new HashSet<>();
}

7.8 Lombok builder exclude field or ignore field

From lombok builder to exclude or ignore any field, you need to put @Builder annotation on a customer constructor or a static method. For example in Student class, I want to have a builder using only 2 fields and ignore all other fields. Then create a constructor for these fields and put annotation on top of that constructor. 

In the following example, we want builder to use only name and admissionReference fields, rest all properties should be ignored.

@Data
@Builder
public class Student {
  @Builder.Default
  int admissionNo = 0;
  @lombok.NonNull
  String name;
  boolean needTransport;
  boolean stayingInHostal;
  @Builder.Default
  String admissionReference = "Direct";
  @Builder.Default
  BigDecimal fees = BigDecimal.ZERO;
  @Builder.Default
  Set<String> addresses = new HashSet<>();

  @Builder
  public Student(String name, String admissionReference){
  }
}

7.9 Lombok singular annotation

The @Singular annotation is applied along with @Builder annotation. Any collection annotated with @Singular annotation, Lombok creates special methods to handle that collection. No setter method is provided for @Singular annotated collection, to avoid replacing the entire collection. 

If you use @Singular annotation to any collection, then following 

Adder method for single itemLombok generates an adder method, which can add a single item to the collection.
Adder method for collection of itemsLombok generates an adder method, which can add all the elements of another collection to the collection.
Clear methodClear method is generated by Lombok to clear all the elements from the collection.
No setter methodNo setter method is generated for the collection to avoid replacing the existing content
@Singular annotation impact

Lombok Singular Annotation example

import lombok.Builder;
import lombok.Singular;
import java.util.Set;
@Builder
public class Builder1Example {
 @Builder.Default 
 private long createdAt = System.currentTimeMillis();
 private String firstName;
 private String lastName;
 @Singular 
 private Set<String> addresses;
}

8. @Getter

Lombok’s @Getter let you generate on-fly a getter which will calculate a value once, the first time this getter is called, and cache it from then on. This can be useful if calculating the value takes a lot of CPU, or the value takes a lot of memory. To use this feature, create a private final variable, initialize it with the expression that’s expensive to run, and annotate your field with @Getter(lazy=true). The field will be hidden from the rest of your code, and the expression will be evaluated no more than once, when the getter is first called. There are no magic marker values (i.e. even if the result of your expensive calculation is null, the result is cached) and your expensive calculation need not be thread-safe, as lombok takes care of locking.

import lombok.Getter;

public class GetterLazyExample {
  @Getter(lazy=true) private final double[] cached = expensive();
  
  private double[] expensive() {
    double[] result = new double[1000000];
    for (int i = 0; i < result.length; i++) {
      result[i] = Math.asin(i);
    }
    return result;
  }
}

9. @Log

@Log Lombok Annotation provides different variants of Logging utilities. You can put the variant of @Log on your class (whichever one applies to the logging system you use); you then have a static final log field, initialized to the name of your class, which you can then use to write log statements. The logger is named log and the field’s type depends on which logger you have selected.

By default, the topic (or name) of the logger will be the class name of the class annotated with the @Log annotation. This can be customized by specifying the topic parameter. For example: @XSlf4j(topic="reporting").

  • @Log
  • @Log4j
  • @Log4j2
  • @Slf4j
  • @XSlf4j
  • @CommonsLog
  • @Flogger@JBossLog
import lombok.extern.java.Log;
import lombok.extern.slf4j.Slf4j;

@Log
public class LogExample {
  
  public static void main(String... args) {
    log.severe("Something's wrong here");
  }
}

@Slf4j
public class LogExampleOther {
  
  public static void main(String... args) {
    log.error("Something else is wrong here");
  }
}

@CommonsLog(topic="CounterLog")
public class LogExampleCategory {

  public static void main(String... args) {
    log.error("Calling the 'CounterLog' with a message");
  }
}

10. @UtilityClass

@UtilityClass is a class that is just a namespace for functions. No instances of it can exist, and all its members are static. For example, java.lang.Math and java.util.Collections are well known utility classes. This annotation automatically turns the annotated class into one.

A utility class cannot be instantiated. By marking your class with @UtilityClass, lombok will automatically generate a private constructor that throws an exception, flags as error any explicit constructors you add, and marks the class final. If the class is an inner class, the class is also marked static.

All members of a utility class are automatically marked as static. Even fields and inner classes.

import lombok.experimental.UtilityClass;

@UtilityClass
public class UtilityClassExample {
  private final int CONSTANT = 5;

  public int addSomething(int in) {
    return in + CONSTANT;
  }
}

Conclusion

We have tried to summarize all mostly used annotations here to represent the working of Lombok annotations and Lombok’s advanced features. The reference of this annotations are taken from Lombok’s documentation page.