Spring Boot with Lombok: Part 1

Spring Boot with Lombok: Part 1

0 Comments

Project 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.

About SFG Contributor

Staff writer account for Spring Framework Guru

    You May Also Like