Carlos Chacin

Software Engineering Experiences


โ˜•๏ธ Immutables/AutoValue/Lombok ๐Ÿ”ฅ Which One?

Posted at April 12, 2020 /

OfficeDesk

In this article, we are going to compare some of the features of the Immutables.org library, Google AutoValue and Project Lombok:

The three libraries are based on an annotation processor to generate/modify code for us:

NOTE: Immutables and AutoValue generate new classes with the processor, and Lombok modifies the bytecode of the original class.

๐Ÿ’ก Overview

Immutables Java annotation processors to generate simple, safe, and consistent value objects. Do not repeat yourself, try Immutables, the most comprehensive tool in this field!

AutoValue provides an easier way to create immutable value classes, with a lot less code and less room for error, while not restricting your freedom to code almost any aspect of your class exactly the way you want it.

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java. Never write another getter or equals method again, with one annotation your class has a fully-featured builder, Automate your logging variables, and much more.

๐Ÿ“‡ The Model

We are going to create a class using the three libraries to be able to create an object representation with the following fields.

Optional<Integer> myOptional;
String myString;
List<String> myList;

๐Ÿ”ฉ Creating the model with AutoValue

package autovalue;

import com.google.auto.value.AutoValue;

import java.util.List;
import java.util.Optional;

@AutoValue
public abstract class MyModel {
    public abstract Optional<Integer> myOptional();

    public abstract String myString();

    public abstract List<String> myList();

    // Builder not generated by default
    // We have to write this boilerplate code
    @AutoValue.Builder
    public abstract static class Builder {
        public abstract Builder setMyOptional(Optional<Integer> myOptional);

        public abstract Builder setMyString(String myString);

        public abstract Builder setMyList(List<String> myList);

        public abstract MyModel build();
    }
}

๐Ÿ”ฉ Creating the model with Lombok

package lombok;

import java.util.List;
import java.util.Optional;

@Value
@Builder
public class MyModel {
    Optional<Integer> myOptional;

    String myString;

    List<String> myList;
}

๐Ÿ”ฉ Creating the model with Immutables

package immutables;

import org.immutables.value.Value;

import java.util.List;
import java.util.Optional;

@Value.Immutable
public interface MyModel {
    Optional<Integer> myOptional();

    String myString();

    List<String> myList();
}

๐ŸŒตTests

Letโ€™s check some Pseudo-code:

We are going to create two identical lists with the same element inside:

list1=List.of("OneValue")
list2=List.of("OneValue")

We are going to create two identical value objects like this:

MyModel1: (
  myOptional=Optional.of(1)
  myString="Hello"
  myList=list1 // Using list 1
)

MyModel2: (
  myOptional=Optional.of(1)
  myString="Hello"
  myList=list2 // Using list 2
)

Even when using different references for the lists, the objects should be equal by value.

model1 == model2 // TRUE

After mutating one of the lists, the comparison of the objects should be the same

list1.add("AnotherValue")

model1 == model2 // TRUE

๐Ÿญ Testing AutoValue

@Test
void immutability() {
    // Create 2 lists containing the same element
    var myList1 = new ArrayList<String>();
    myList1.add("OneValue");
    var myList2 = List.of("OneValue");

    // Create model 1, assigning the list1
    var myModel1 = new AutoValue_MyModel.Builder()
            .setMyOptional(Optional.of(1)) // ๐Ÿ˜ฅ ๐Ÿ”ด No helper for Optional
            .setMyString("Hello")
            .setMyList(myList1) // ๐Ÿ˜ฅ ๐Ÿ”ด No helper for List
            .build();

    // Create model 2, assigning the list2
    var myModel2 = new AutoValue_MyModel.Builder() // ๐Ÿ˜ฅ ๐Ÿ”ด No helper for copying
            .setMyOptional(Optional.of(1))
            .setMyString("Hello")
            .setMyList(myList2)
            .build();

    // Compare the 2 objects
    // Test passes since the fields contain the same values
    assertThat(myModel1).isEqualTo(myModel2);

    // Mutate the list used on Model 1
    myList1.add("AnotherValue");

    // Compare the 2 objects:
    // - PASSES objects are NOT EQUAL for AutoValue ๐Ÿ˜ฎ ๐Ÿ”ด
    assertThat(myModel1).isNotEqualTo(myModel2);
}

๐Ÿญ Testing Lombok

@Test
void immutability() {
    // Create a mutable list with 1 element
    var myList1 = new ArrayList<String>();
    myList1.add("OneValue");
    var myList2 = List.of("OneValue");

    // Create model 1, assigning the list1
    var myModel1 = MyModel.builder()
            .myOptional(Optional.of(1)) // ๐Ÿ˜ฅ ๐Ÿ”ด No helper for Optional
            .myString("Hello")
            .myList(myList1) // ๐Ÿ˜ฅ ๐Ÿ”ด No helper for List
            .build();

    // Create model 2, assigning the list2
    var myModel2 = MyModel.builder() ๐Ÿ˜ฅ ๐Ÿ”ด // No helper for copying
            .myOptional(Optional.of(1))
            .myString("Hello")
            .myList(myList2)
            .build();

    // Compare the 2 objects
    // Test passes since the fields contain the same values
    assertThat(myModel1).isEqualTo(myModel2);

    // Mutate the list used on Model 1
    myList1.add("AnotherValue");

    // Compare the 2 objects:
    // - PASSES objects NOT EQUAL for Lombok ๐Ÿ˜ฎ ๐Ÿ”ด
    assertThat(myModel1).isNotEqualTo(myModel2);
}

๐Ÿญ Testing Immutables

@Test
void immutability() {
   // Create a mutable list with 1 element
   var myList1 = new ArrayList<String>();
   myList1.add("OneValue");
   var myList2 = List.of("OneValue");

   // Create model 1, assigning the list1
   var myModel1 = ImmutableMyModel.builder()
           .myOptional(1) // ๐ŸŽฉ โœ… Helper for Optional
           .myString("Hello")
           .myList(myList1)
           .build();

   // Create model 2, assigning the list2
   var myModel2 = ImmutableMyModel.builder()
           .from(myModel1) // ๐ŸŽฉ โœ… Helper for copying
           .addMyList("OneValue") // ๐ŸŽฉ โœ… Helper for List
           .build();

   // Compare the 2 objects
   // Test passes since the fields contain the same values
   assertThat(myModel1).isEqualTo(myModel2);

   // Mutate the list used on Model 1
   myList1.add("AnotherValue");

   // Compare the 2 objects:
   // - Test PASSES objects ARE EQUAL for Immutables ๐ŸŽฉ โœ…
   assertThat(myModel1).isEqualTo(myModel2);
}

๐Ÿ“ˆ Results

ย  AutoValue Lombok Immutables
Line of Code to write/maintain 28 14 15
Builder (by default) ๐Ÿ”ด ๐Ÿ”ด โœ…
Required IDE Plugin โœ… ๐Ÿ”ด โœ…
Immutability ๐Ÿ”ด ๐Ÿ”ด โœ…
Helper for Optional ๐Ÿ”ด ๐Ÿ”ด โœ…
Helper for Collections ๐Ÿ”ด ๐Ÿ”ด โœ…
Helper for Copying ๐Ÿ”ด ๐Ÿ”ด โœ…

๐Ÿ”† Conclusions