Using Immutable Objects

§Using immutable objects

An immutable object cannot be modified after it was created. Immutable objects have two great advantages:

  • Code based on immutable objects is clearer and likelier to be correct. Bugs involving unexpected changes simply can’t occur.
  • Multiple threads can safely access immutable objects concurrently.

In Lagom, immutable objects are required in several places, such as:

  • Service request and response types
  • Persistent entity commands, events, and states
  • Publish and subscribe messages

Lagom doesn’t care how you define immutable objects. You can write out the constructor and getters by hand, but we recommend using third party tools to generate them instead. Using a third party tool, such as the Immutables tool or Lombok, is easier and less error-prone than coding by hand and the resulting code is shorter and easier to read.

The following sections provide more information on immutables:

§Mutable and immutable examples

In the following example of a mutable object, the setter methods can be used to modify the object after construction:

public class MutableUser {
  private String name;
  private String email;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getEmail() {
    return email;
  }

  public void setEmail(String email) {
    this.email = email;
  }

}

In contrast, in the following example of an immutable object, all fields are final and assigned at construction time (it contains no setter methods).

public class ImmutableUser {
  private final String name;
  private final String email;

  public ImmutableUser(String name, String email) {
    this.name = name;
    this.email = email;
  }

  public String getName() {
    return name;
  }

  public String getEmail() {
    return email;
  }

}

§Easier immutability

§Lombok example

The following example shows the definition of a User implemented with Lombok. Lombok handles the following details for you. It:

  • modifies fields to be private and final
  • creates getters for each field
  • creates correct equals, hashCode and a human-friendly toString
  • creates a constructor requiring all fields.
@Value
public class LombokUser {

  String name;

  String email;
}

The example does not demonstrate other useful Lombok feature like @Builder or @Wither which will help you create builder and copy methods. Be aware that Lombok is not an immutability library but a code generation library which means some setups might not create immutable objects. For example, Lombok’s @Data is equivalent to Lombok’s @Value but will also synthesize mutable methods. Don’t use Lombok’s @Data when creating immutable classes.

§Adding Lombok to a Maven project

To add Lombok to a Maven project, declare it as a simple dependency:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.12</version>
</dependency>

§Adding Lombok to an sbt projects

For sbt, add the following line to ????

val lombok = "org.projectlombok" % "lombok" % "1.16.12"
libraryDependencies += lombok

§Integrating Lombok with an IDE

Lombok integrates with popular IDEs:
* To use Lombok in IntelliJ IDEA you’ll need the Lombok Plugin for IntelliJ IDEA and you’ll also need to enable Annotation Processing (Settings / Build,Execution,Deployment / Compiler / Annotation Processors and tick Enable annotation processing)
* To Use Lombok in Eclipse, run java -jar lombok.jar (see the video at Project Lombok).

§Immutables tool example

Here is the corresponding definition of a User (like the above ImmutableUser) using Immutables:

@Value.Immutable
@ImmutableStyle
public interface AbstractUser {

  String getName();

  String getEmail();
}

Immutables generates for you:

  • builders (very convenient when your class has many fields)
  • correct implementations of equals, hashCode, toString
  • copy methods to make new instances based on old ones, e.g. withEmail

§Adding Immutables to a Maven project

<dependency>
    <groupId>com.lightbend.lagom</groupId>
    <artifactId>lagom-javadsl-immutables_${scala.binary.version}</artifactId>
    <version>${lagom.version}</version>
</dependency>

§Adding Immutables to an sbt project

libraryDependencies += lagomJavadslImmutables

§Integrating Immmutables with an IDE

Immutables integrates with popular IDEs. Follow the instructions for Eclipse or IntelliJ IDEA to add the Immutables annotation processor to your IDE.

§Collections

If an immutable object contains a collection, that collection must be immutable too.

Here is an example of an object with a mutable collection in it:

public class MutableUser2 {
  private final String name;
  private final List<String> phoneNumbers;

  public MutableUser2(String name, List<String> phoneNumbers) {
    this.name = name;
    this.phoneNumbers = phoneNumbers;
  }

  public String getName() {
    return name;
  }

  public List<String> getPhoneNumbers() {
    return phoneNumbers;
  }

}

The object might look like immutable since it only has final fields and no setters, but the List of phoneNumbers is mutable, because it can be changed by code like user.getPhoneNumbers().add("+468123456").

One possible fix would be to make a defensive copy of the List in the constructor and use Collections.unmodifiableList in the getter, like this:

public class ImmutableUser2 {
  private final String name;
  private final List<String> phoneNumbers;

  public ImmutableUser2(String name, List<String> phoneNumbers) {
    this.name = name;
    this.phoneNumbers = new ArrayList<>(phoneNumbers);
  }

  public String getName() {
    return name;
  }

  public List<String> getPhoneNumbers() {
    return Collections.unmodifiableList(phoneNumbers);
  }

}

But it is easy to make mistakes when coding this way, so we again recommend letting Immutable handle it for you, as follows.

Here is a new definition of a User2, like the above ImmutableUser2:

@Value.Immutable
@ImmutableStyle
public interface AbstractUser2 {

  String getName();

  List<String> getPhoneNumbers();
}

The getPhoneNumbers method will return an instance of com.google.common.collect.ImmutableList.

§Guava and PCollections

As seen above, Immutables uses Guava immutable collections by default.

The Guava collections are certainly better for this purpose than plain java.util collections. However, the Guava collections are cumbersome and inefficient for some common operations (for example, making a slightly modified copy of an existing collection).

Therefore, we recommend PCollections, which is a collection library that is designed from the ground up for immutability and efficiency.

This is how the above example looks like using a PCollection:

import com.lightbend.lagom.javadsl.immutable.ImmutableStyle;
import org.immutables.value.Value;
import org.pcollections.PVector;

@Value.Immutable
@ImmutableStyle
public interface AbstractUser3 {

  String getName();

  PVector<String> getPhoneNumbers();
}

This is how to define an optional collection initialized with the empty collection:

import com.lightbend.lagom.javadsl.immutable.ImmutableStyle;
import org.immutables.value.Value;
import org.pcollections.PVector;
import org.pcollections.TreePVector;

@Value.Immutable
@ImmutableStyle
public interface AbstractUser4 {

  String getName();

  @Value.Default
  default PVector<String> getPhoneNumbers() {
    return TreePVector.empty();
  }
}

§“Persistent” collections

There are two different and potentially confusing usages of the word “persistent” with respect to data.

You will see references, in the PCollections documentation and elsewhere, to “persistent” data structures. There, the word “persistent” means that even when you construct a modified copy of a collection, the original “persists”.

In the rest of this documentation, “persistent” refers instead to persistent storage, as in Persistent Entities and the examples below.

§Further reading

The Immutables documentation has more details on immutable collections here.

Found an error in this documentation? The source code for this page can be found here. Please feel free to edit and contribute a pull request.