Project Lombok Intro

Project Lombok helps to reduce boilerplate code.

Adding Lombok to Project

For Maven project, add Maven Dependency. see https://projectlombok.org/setup/maven

1
2
3
4
5
6
7
8
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>

For Gradle project. see https://projectlombok.org/setup/gradle

1
2
3
4
5
6
7
8
9
10
11
repositories {
mavenCentral()
}

dependencies {
compileOnly 'org.projectlombok:lombok:1.18.30'
annotationProcessor 'org.projectlombok:lombok:1.18.30'

testCompileOnly 'org.projectlombok:lombok:1.18.30'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.30'
}

NOTE: the maven dependency scope is provided because lombok is not needed at runtime.

Lombok works as long as it is in the project classpath. However, you still need to setup lombok in the IDE if you are using an IDE.

For Intellij, You can install the Lombok plugin. see https://projectlombok.org/setup/intellij

For Eclipse, you can execute the jar file in ~/.m2/repository/org/projectlombok/lombok/1.18.30/lombok-*.jar

Sample Class with Lombok Annotations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import lombok.*;
import java.util.List;

@Data
@Accessors(chain = true, makeFinal = true)
@NoArgsConstructor
@AllArgsConstructor
public class Book {
private String title;
private String subtitle;
private String category;
private float price;
private List<String> authors;
}

@Data

@Data is shortcut for

  • @Getter
  • @Setter
  • @RequiredArgsConstructor
  • @ToString
  • @EqualsAndHashCode.

@Data is very convenient because it generates most of the boilerplate code for you.

1
2
3
4
5
@Data
public class Book {
private String title;
private String category;
}

Constructors

use @NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor to generate constructors.

  • @NoArgsConstructor - Generates a no-args constructor
  • @RequiredArgsConstructor - Generates a constructor with required arguments. Required arguments are final fields and fields with constraints such as @NonNull.
  • @AllArgsConstructor - Generates an all-args constructor.
1
2
3
4
5
6
7
8
@Data
@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
public class Book {
private @NonNull String title;
private String category;
}

Generate Getter and Setter

use @Getter and @Setter to generate default getter and setter.

1
2
3
4
5
6
@Getter
@Setter
public class Book {
private String title;
private String category;
}

Accessors

@Accessors was introduced as experimental feature in lombok v0.11.0.

The @Accessors annotation is used to configure how lombok generates and looks for getters, setters, and with-ers.

fluent attribut - If true, accessors will be named after the field and not include a get or set prefix.

chain attribute - If true, setters return this instead of void.

makeFinal attribute - will create final getters, setters, and with-ers.

Be VERY careful when using fluent attribute. Fluent setters changes the name of the setter methods. Setter methods that starts with set are necessary for some libraries and frameworks. Jackson won’t work with fluent setters.

Class with @Accessors annotation

1
2
3
4
5
6
7
8
9
10
11
12
import lombok.*;
import lombok.experimental.Accessors;

@Data
@Accessors(fluent = true, chain = true)
public class Book {
private String title;
private String subtitle;
private String category;
private float price;
private List<String> authors;
}

To use the fluent setter

1
2
3
4
5
Book book = new Book().title("my title")
.subtitle("my subtitle")
.category("tech")
.price(20.0f)
.authors(Arrays.asList("Mike"));

Equals and hashCode

use @EqualsAndHashCode to implement equals and hashCode method. By default, it uses non-static, non-transient fields. You can also customize the fields to be used.

To exclude a field, use @EqualsAndHashCode.Exclude annotation.

Alternatively, you can specify exactly which fields or methods you wish to be used by marking them with @EqualsAndHashCode.Include and using @EqualsAndHashCode(onlyExplicitlyIncluded = true).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Getter
@Setter
@ToString
@EqualsAndHashCode
public class Book {
private String title;
private String category;
private List<String> authors;

@EqualsAndHashCode.Exclude
private float price;

public static void main(String[] args) {
Book book = new Book();
book.setTitle("Java 11 Book");
book.setCategory("Java");
book.setPrice(49.99f);
book.setAuthors(List.of("Bob", "Alice"));
System.out.println(book.hashCode());
}
}

ToString

generate an implementation of toString() method.

To skip a field, use @ToString.Exclude annotation.

1
2
3
4
5
6
7
8
9
10
11
12
@ToString
@Getter
@Setter
public class Book {
private String title;
private String subtitle;
private String category;
private float price;

@ToString.Exclude
private List<String> authors;
}

Sample output:

1
Book(title=Java 11 Cookbook, subtitle=null, category=Java, price=49.99)

With

@With - to construct a clone of the object, but with a new value for this one field.

1
2
3
4
5
6
7
8
9
10
@Data
@AllArgsConstructor
@ToString
public class Book {
@With(AccessLevel.PROTECTED)
private String title;

@With
private String subtitle;
}

Demo

1
2
3
4
Book book = new Book("Title", "Subtitle");

// construct a clone with new value for title
Book cloneBook = book.withTitle("New Title");

Null Check

You use @NonNull annotation to do null checks. If it is put on a method or constructor argument, it will throw NullPointerException when the provided value is null. If it is put on a field, then any generated method will have a null check.

1
2
3
public void setTitle(@NonNull String title) {
this.title = title;
}

If null is passed as parameter, then NullPointerException will be thrown. The message looks like this:

1
Exception in thread "main" java.lang.NullPointerException: title is marked non-null but is null

Builder Pattern

@Builder annotation provides builder API for your class.

Java class with a Builder

1
2
3
4
5
6
7
8
9
@Data
@Builder
public class Book {
private String title;
private String subtitle;
private String category;
private float price;
private List<String> authors;
}

To use the Builder

1
2
3
4
5
6
7
Book b1 = Book.builder()
.title("my title")
.subtitle("my subtitle")
.category("tech")
.price(20.0f)
.authors(Arrays.asList("Mike"))
.build();

Logger

Lombok provides various annotations for creating log field.

For Example, if you are using Slf4j, then @Slf4j annotation will create a log field of type org.slf4j.Logger for you

1
2
3
4
5
6
@Slf4j
public class LogTest {
public void testing(int param) {
log.info("Entering {} method with param = {}", "testing", param);
}
}

Synchronized

@Synchronized is a safer variant of the synchronized method modifier. The synchronized method modifier locks on this, but this annotation locks on a field named $lock.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import lombok.Synchronized;

public class SynchronizedExample {
private final Object readLock = new Object();

@Synchronized
public static void hello() {
System.out.println("world");
}

@Synchronized
public int answerToLife() {
return 42;
}

@Synchronized("readLock")
public void foo() {
System.out.println("bar");
}
}

is equivalent to

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 public class SynchronizedExample {
private static final Object $LOCK = new Object[0];
private final Object $lock = new Object[0];
private final Object readLock = new Object();

public static void hello() {
synchronized($LOCK) {
System.out.println("world");
}
}

public int answerToLife() {
synchronized($lock) {
return 42;
}
}

public void foo() {
synchronized(readLock) {
System.out.println("bar");
}
}
}

SneakyThrows

@SneakyThrows can be used to sneakily throw checked exceptions without actually declaring this in your method’s throws clause.

Checked Exception force the caller to handle or rethrow the exception. This can bubble up and created chunky code. @SneakyThrows tricks the Compiler to

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class App {
public static void main(String[] args) {
new App().printFile("test.txt");
}

@SneakyThrows
public void printFile(String path) {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(path);

String data = IOUtils.toString(inputStream, StandardCharsets.UTF_8);

System.out.println(data);
}
}

Without Lombok

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class App {
public static void main(String[] args) {
try {
new App().printFile("test.txtt");
} catch (IOException e) {
e.printStackTrace();
}
}

public void printFile(String path) throws IOException{
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(path);

String data = IOUtils.toString(inputStream, StandardCharsets.UTF_8);

System.out.println(data);
}
}

Reference