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
- Lombok Maven dependency
- Lombok Gradle dependency
- Project Lombok Annotations Examples
- 1. val
- 2. var
- 3. @NonNull
- 4. @Cleanup
- 5. @Data lombok
- 6. @Value
- 7. Lombok Builder
- 7.1 Lombok builder example in Java
- 7.2 Lombok builder default
- 7.3 Lombok @builder list
- 7.4 Lombok builder custom build method
- 7.5 Lombok builder with prefix
- 7.6 Lombok constructor annotations
- 7.7 lombok builder public constructor or private constructor
- 7.8 Lombok builder exclude field or ignore field
- 7.9 Lombok singular annotation
- 8. @Getter
- 9. @Log
- 10. @UtilityClass
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 toString
, equals
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 callSuper
, includeFieldNames
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 toString
, equals
, 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 item | Lombok generates an adder method, which can add a single item to the collection. |
Adder method for collection of items | Lombok generates an adder method, which can add all the elements of another collection to the collection. |
Clear method | Clear method is generated by Lombok to clear all the elements from the collection. |
No setter method | No setter method is generated for the collection to avoid replacing the existing content |
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.