Spring Boot with Lombok: Part 1
2 CommentsProject Lombok is a Java library tool that generates code for minimizing boilerplate code. The library replaces boilerplate code with easy-to-use annotations.
For example, by adding a couple of annotations, you can get rid of code clutters, such as getters and setters methods, constructors, hashcode, equals, and toString methods, and so on.
This is Part 1 of the Spring Boot with Lombok post. In this part I’ll discuss the following Lombok constructs:
- var and val
- @Getter, @Setter
- @NoArgsConstructor, @AllArgsConstructor
- @Data
- @NotNull
Lombok Dependency
To use Lombok in your project, add the lombok
dependency to the Maven POM, like this.
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.8</version> <scope>provided</scope> </dependency>
Note: If you’re using a Spring Boot POM, Project Lombok is a curated dependency. Thus, you can omit the version (which will then be inherited from the Spring Boot parent POM).
val and var
You can use val
as the type of a local variable instead of writing the actual type. Lombok infers the type from the initializer expression. Lombok will also mark the local variable as final.
var
works exactly like val
, except the local variable is not marked as final.
The code for using val
and var
is this.
package guru.springframework.domain.valandvar; import java.math.BigDecimal; import java.util.ArrayList; import lombok.val; public class ValAndVarUserDemo { public String valCheck() { /* val makes local final variable (inside method) Trying to assign a value will result in Error: java: cannot assign a value to final variable userName */ val userName = "Hello World"; System.out.println(userName.getClass()); return userName.toLowerCase(); } public Object varCheck() { /* var makes local variable (inside method). Same as var but is not marked final */ var money = new BigDecimal(53.00); System.out.println(money.getClass()); money = new BigDecimal(80.00); return money; } }
The decompiled ValAndVarUserDemo.class
is this.
Note: If you are using IntelliJ, double-click the class file inside the target folder to view the decompiled class.
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package guru.springframework.domain.valandvar; import java.math.BigDecimal; public class ValAndVarUserDemo { public ValAndVarUserDemo() { } public String valCheck() { String userName = "Hello World"; System.out.println("Hello World".getClass()); return "Hello World".toLowerCase(); } public Object varCheck() { BigDecimal money = new BigDecimal(53.0D); System.out.println(money.getClass()); money = new BigDecimal(80.0D); return money; } }
The code for testing the ValAndVarUserDemo
class is this.
package guru.springframework.domain.valandvar; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.math.BigDecimal; import static org.junit.Assert.*; public class ValAndVarUserDemoTest { private ValAndVarUserDemo valAndVarUserDemo; @Before public void setUp() throws Exception { valAndVarUserDemo = new ValAndVarUserDemo(); } @After public void tearDown() throws Exception { valAndVarUserDemo = null; } @Test public void testValUsage() { assertEquals("hello world", valAndVarUserDemo.valCheck()); } @Test public void testVarUsage() { assertEquals(new BigDecimal(80), valAndVarUserDemo.varCheck()); } }
@Getter and @Setter
You can use the @Getter
and @Setter
annotations at both the field or class level to generate getters and setters for private fields.
When you use them at the field level, Lombok generates getters and setters only for the decorated fields.
The code to use the @Getter
and @Setter
annotations at the field level is this.
package guru.springframework.domain.gettersetter; import lombok.Getter; import lombok.Setter; public class FieldLevelGetterSetterDemo { private int userId; @Getter @Setter private String userName; @Getter private int userAge; public FieldLevelGetterSetterDemo(int userAge){ this.userAge=userAge; } }
This code annotates userName
with @Getter
and @Setter
. The code also annotates userAge
with @Getter
.
The decompiled FieldLevelGetterSetterDemo.class
is this.
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package guru.springframework.domain.gettersetter; public class FieldLevelGetterSetterDemo { private int userId; private String userName; private int userAge; public FieldLevelGetterSetterDemo(int userAge) { this.userAge = userAge; } public String getUserName() { return this.userName; } public void setUserName(final String userName) { this.userName = userName; } public int getUserAge() { return this.userAge; } }
The preceding code shows the getUserName()
and setUserName()
methods that Lombok generates for the userName
field. Also note that Lombok generates a single getUserAge()
method for the userAge
field.
The code to test the FieldLevelGetterSetterDemo
class is this.
package guru.springframework.domain.gettersetter; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; public class FieldLevelGetterSetterDemoTest { FieldLevelGetterSetterDemo fieldLevelGetterSetterDemo; @Before public void setUp() throws Exception { fieldLevelGetterSetterDemo=new FieldLevelGetterSetterDemo(28); } @After public void tearDown() throws Exception { fieldLevelGetterSetterDemo=null; } @Test public void testFieldLevelGetterSetter(){ fieldLevelGetterSetterDemo.setUserName("John Doe"); assertEquals( "John Doe", fieldLevelGetterSetterDemo.getUserName()); } @Test public void testFieldLevelGetter(){ assertEquals( 28, fieldLevelGetterSetterDemo.getUserAge()); } }
When you use the @Getter
and @Setter
annotations at the class level, Lombok generates getter and setter methods for all the fields.
package guru.springframework.domain.gettersetter; import lombok.*; /* @Getter and @Setter annotations for getter and setter methods */ @Getter @Setter public class GetterSetterUserDemo { private int userId; private String userName; private int userAge; }
The decompiled GetterSetterUserDemo.class
is this.
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package guru.springframework.domain.gettersetter; public class GetterSetterUserDemo { private int userId; private String userName; private int userAge; public GetterSetterUserDemo() { } public int getUserId() { return this.userId; } public String getUserName() { return this.userName; } public int getUserAge() { return this.userAge; } public void setUserId(final int userId) { this.userId = userId; } public void setUserName(final String userName) { this.userName = userName; } public void setUserAge(final int userAge) { this.userAge = userAge; } }
As you can see, Lombok generates getter and setter methods for all the fields.
The code for testing GetterSetterUserDemo
class is this.
package guru.springframework.domain.gettersetter; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; public class GetterSetterUserDemoTest { private GetterSetterUserDemo getterSetterUserDemo; @Before public void setUp(){ getterSetterUserDemo=new GetterSetterUserDemo(); } @After public void tearDown(){ getterSetterUserDemo=null; } @Test public void testGetterSetterAnnotation() { getterSetterUserDemo.setUserId(101); getterSetterUserDemo.setUserName("John Doe"); getterSetterUserDemo.setUserAge(25); assertEquals(101, getterSetterUserDemo.getUserId()); assertEquals( "John Doe", getterSetterUserDemo.getUserName()); assertEquals( 25, getterSetterUserDemo.getUserAge()); } }
@NoArgsConstructor and @AllArgsConstructor
You can use the @NoArgsConstructor
annotation to generate the default constructor that takes no arguments. To generate a constructor with arguments for all the field, use the @AllArgsConstructor
annotation.
The code for demonstrating @NoArgsConstructor
and @AllArgsConstructor
annotations is this.
package guru.springframework.domain.constructor; import lombok.*; /* @NoArgsConstructor annotation for generating a constructor with no parameters */ @NoArgsConstructor /* @AllArgsConstructor annotation for generating a constructor with 1 parameter for each field */ @AllArgsConstructor public class ConstructorUserDemo { private int userId; private String userName; private int userAge; }
In the preceding code, we have annotated the class with @NoArgsConstructor
and @AllArgsConstructor
. Lombok will generate two constructors in the .class
file. One without parameters and the other with a parameter for each field.
The decompiled ConstructorUserDemo.class
is this.
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package guru.springframework.domain.constructor; public class ConstructorUserDemo { private int userId; private String userName; private int userAge; public ConstructorUserDemo() { } public ConstructorUserDemo(final int userId, final String userName, final int userAge) { this.userId = userId; this.userName = userName; this.userAge = userAge; } }
The code for testing the ConstructorUserDemo.java
is this.
package guru.springframework.domain.constructor; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; public class ConstructorUserDemoTest { private ConstructorUserDemo constructorUserDemo; /* test @NoArgsConstructor annotation */ @Test public void testDataAnnotationForNoArgsConstructor() { constructorUserDemo = new ConstructorUserDemo(); assertNotNull(constructorUserDemo); } /* test @AllArgsConstructor annotation */ @Test public void testDataAnnotationForAllArgsConstructor() { constructorUserDemo = new ConstructorUserDemo(100, "John Doe", 25); assertNotNull(constructorUserDemo); } } <//pre>
@Data
@Data
is a convenient annotation that combines the features of the following annotations:
@ToString
@EqualsAndHashCode
@Getter
@Setter
@RequiredArgsConstructor
This code demonstrates the @Data
annotation.
package guru.springframework.domain.data; import lombok.Builder; import lombok.Data; @Data public class DataUserDemo { private int userId; private String userName; private int userAge; }
The decompiled DataUserDemo.class
is this.
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package guru.springframework.domain.data; public class DataUserDemo { private int userId; private String userName; private int userAge; public DataUserDemo() { } public int getUserId() { return this.userId; } public String getUserName() { return this.userName; } public int getUserAge() { return this.userAge; } public void setUserId(final int userId) { this.userId = userId; } public void setUserName(final String userName) { this.userName = userName; } public void setUserAge(final int userAge) { this.userAge = userAge; } public boolean equals(final Object o) { if (o == this) { return true; } else if (!(o instanceof DataUserDemo)) { return false; } else { DataUserDemo other = (DataUserDemo)o; if (!other.canEqual(this)) { return false; } else if (this.getUserId() != other.getUserId()) { return false; } else { Object this$userName = this.getUserName(); Object other$userName = other.getUserName(); if (this$userName == null) { if (other$userName == null) { return this.getUserAge() == other.getUserAge(); } } else if (this$userName.equals(other$userName)) { return this.getUserAge() == other.getUserAge(); } return false; } } } protected boolean canEqual(final Object other) { return other instanceof DataUserDemo; } public int hashCode() { int PRIME = true; int result = 1; int result = result * 59 + this.getUserId(); Object $userName = this.getUserName(); result = result * 59 + ($userName == null ? 43 : $userName.hashCode()); result = result * 59 + this.getUserAge(); return result; } public String toString() { int var10000 = this.getUserId(); return "DataUserDemo(userId=" + var10000 + ", userName=" + this.getUserName() + ", userAge=" + this.getUserAge() + ")"; } }
In the preceding code, Lombok generated getters for all fields, setters for all non-final fields, toString, equals and hashCode implementation and a constructor.
To test code for the @Data
annotation is this.
package guru.springframework.domain.data; import guru.springframework.domain.constructor.ConstructorUserDemo; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; public class DataUserDemoTest { DataUserDemo dataUserDemo; @Before public void setUp() throws Exception { dataUserDemo = new DataUserDemo(); } @After public void tearDown() throws Exception { dataUserDemo = null; } /* test @Data annotation for getter and setter */ @Test public void testDataAnnotationForGetterandSetter() { dataUserDemo.setUserId(101); dataUserDemo.setUserName("John Doe"); dataUserDemo.setUserAge(25); assertEquals(101, dataUserDemo.getUserId()); assertEquals( "John Doe", dataUserDemo.getUserName()); assertEquals( 25, dataUserDemo.getUserAge()); System.out.println(dataUserDemo); } /* test @Data annotation for toString */ @Test public void testDataAnnotationForToString() { dataUserDemo.setUserId(101); dataUserDemo.setUserName("John Doe"); dataUserDemo.setUserAge(25); assertTrue(dataUserDemo.toString().startsWith(DataUserDemo.class.getSimpleName())); assertTrue(dataUserDemo.toString().endsWith("(userId=101, userName=John Doe, userAge=25)")); } /* test @Data annotation for equalsAndHashcode */ @Test public void testDataAnnotationForEqualsAndHashCode() { DataUserDemo dataUserDemo1 = new DataUserDemo(); DataUserDemo dataUserDemo2 = new DataUserDemo(); assertTrue((dataUserDemo1).equals(dataUserDemo2)); assertEquals(dataUserDemo1.hashCode(),dataUserDemo2.hashCode()); } }
@NonNull
Lombok generates a null check statement if we annotate the parameters of a method or a constructor with @NonNull
.
This code shows the usage of @NonNull
.
package guru.springframework.domain.nonnull; import lombok.AllArgsConstructor; import lombok.NonNull; public class NonNullUserDemo { private int userId; private String userName; private int userAge; /* @NonNull generate a null-check statement */ public NonNullUserDemo(int userId, @NonNull String userName, int userAge) { this.userId = userId; this.userName = userName; this.userAge = userAge; } }
The preceding code annotates the userName
parameter as @NonNull
. Lombok will generate code to check for userName
and throw NullPointerException
if userName
is null.
The decompiled NonNullUserDemo.class
is this.
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package guru.springframework.domain.nonnull; import lombok.NonNull; public class NonNullUserDemo { private int userId; private String userName; private int userAge; public NonNullUserDemo(int userId, @NonNull String userName, int userAge) { if (userName == null) { throw new NullPointerException("userName is marked non-null but is null"); } else { this.userId = userId; this.userName = userName; this.userAge = userAge; } } }
The test code for the @NonNull
annotation is this.
package guru.springframework.domain.nonnull; import org.junit.Test; public class NonNullUserDemoTest { private NonNullUserDemo nonNullUserDemo; @Test(expected = NullPointerException.class) public void testNonNullOnConstruuctorParameter() { nonNullUserDemo = new NonNullUserDemo(50, null, 25); } }
Summary
Lombok is a convenient tool that all Java developers should have in their toolkit. It not only makes you code clutter-free, but also saves a significant amount of development time.
When using Lombok for the first time, you might stumble on how to configure it in your IDE. In IntelliJ, you need to have the IntelliJ Lombok plugin. You also need to enable annotation processing. In IntelliJ, go to File->Settings->Build, Execution,Deployment->Compiler->Annotation Processors. Select the Enable annotation processing checkbox.
As with all the tools and technologies, Lombok also comes with its set of disadvantages. One limitation I see is that it is closely tied to the Java compiler. Lombok internally uses the annotation processor API as the entry point. This API only allows the creation of new files during the compilation and not the modification of the existing files.
Lombok makes heavy usage of its own internal APIs for configuring the compiler. So you must be aware that upgrading your compiler might break your code. But, by saying so, Lombok being a matured tool with huge adoption, the probability is pretty low.
In the next part of this post, I will demonstrate some more Lombok annotations.
The source code for this post can be found here on GitHub.
Jay
I’ve been using Lombok from last 5- months. However, I didn’t notice any issues. could you please elaborate more on – ” Lombok internally uses the annotation processor API as the entry point. This API only allows the creation of new files during the compilation and not the modification of the existing files.”
jose paz
very cool tutorial, I have it now running and I believe this will save me millions of keystrokes. I believe my classes are very much readable. Loved the @Slf4j annotation found on their website.