Immutable Objects

§Immutable Objects

An immutable object is an object that 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

§Mutable vs. immutable

Here is an example of a mutable object:

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;
  }

}

The setter methods can be used to modify the object after construction.

Here, by contrast, is an immutable object:

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;
  }

}

All fields are final and are assigned at construction time. There are no setter methods.

§Immutables

Lagom doesn’t care how you define your immutable objects. You can write out the constructor and getters by hand, as in the above sample. But we recommend using the Immutables tool to generate them instead. Using Immutables is easier and less error-prone than writing everything out by hand, and the resulting code is shorter and easier to read.

Here is the corresponding definition of a User, like the above ImmutableUser:

@Value.Immutable
@ImmutableStyle
public interface AbstractUser {

  String getName();

  String getEmail();
}

For free you get things like:

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

Immutables supports different “styles” of object. Compared to the default style, @ImmutableStyle follows a convention that the abstract class or interface name starts with Abstract and that is trimmed from the generated class, e.g. AbstractUser vs User. To use the @ImmutableStyle annotation you need to add lagomJavadslImmutables to your project’s library dependencies.

In maven:

<dependency>
    <groupId>com.lightbend.lagom</groupId>
    <artifactId>lagom-javadsl-immutables_2.11</artifactId>
    <version>${lagom.version}</version>
</dependency>

In sbt:

lazy val usersApi = (project in file("usersApi"))
  .settings(libraryDependencies += lagomJavadslImmutables)

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.

§Example of PersistentEntity Commands

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.lightbend.lagom.javadsl.immutable.ImmutableStyle;
import com.lightbend.lagom.javadsl.persistence.PersistentEntity;
import com.lightbend.lagom.serialization.Jsonable;
import org.immutables.value.Value;
import akka.Done;

public interface BlogCommand extends Jsonable {

  //#AddPost
  @Value.Immutable
  @ImmutableStyle
  @JsonDeserialize(as = AddPost.class)
  interface AbstractAddPost
    extends BlogCommand, PersistentEntity.ReplyType<AddPostDone> {

    @Value.Parameter
    PostContent getContent();
  }
  //#AddPost

  @Value.Immutable
  @ImmutableStyle
  @JsonDeserialize(as = AddPostDone.class)
  interface AbstractAddPostDone extends Jsonable {
    @Value.Parameter
    String getPostId();
  }

  @Value.Immutable(singleton = true, builder = false)
  @ImmutableStyle
  @JsonDeserialize(as = GetPost.class)
  public abstract class AbstractGetPost implements BlogCommand, PersistentEntity.ReplyType<PostContent> {
    protected AbstractGetPost() {
    }
  }

  @Value.Immutable
  @ImmutableStyle
  @JsonDeserialize(as = ChangeBody.class)
  interface AbstractChangeBody extends BlogCommand, PersistentEntity.ReplyType<Done> {
    @Value.Parameter
    String getBody();
  }

  @Value.Immutable(singleton = true, builder = false)
  @ImmutableStyle
  @JsonDeserialize(as = Publish.class)
  public abstract class AbstractPublish implements BlogCommand, PersistentEntity.ReplyType<Done> {
    protected AbstractPublish() {
    }
  }

}

A few things worth noting here:

  • @Value.Parameter can be added to one or more properties to generate constructor method, which is more convenient than the full builder for classes with only a few properties
  • singleton = true can be used for objects that don’t have any properties, i.e. singleton instances, also note that the visibility of the constructor should be reduced for such classes

§Example of PersistentEntity Events

interface BlogEvent extends Jsonable, AggregateEvent<BlogEvent> {

  @Override
  default public AggregateEventTag<BlogEvent> aggregateTag() {
    return BlogEventTag.INSTANCE;
  }

  @Value.Immutable
  @ImmutableStyle
  @JsonDeserialize(as = PostAdded.class)
  interface AbstractPostAdded extends BlogEvent {
    String getPostId();

    PostContent getContent();
  }

  @Value.Immutable
  @ImmutableStyle
  @JsonDeserialize(as = BodyChanged.class)
  interface AbstractBodyChanged extends BlogEvent {
    @Value.Parameter
    String getBody();
  }

  @Value.Immutable
  @ImmutableStyle
  @JsonDeserialize(as = PostPublished.class)
  interface AbstractPostPublished extends BlogEvent {
    @Value.Parameter
    String getPostId();
  }

}

§Example of PersistentEntity State

@Value.Immutable
@ImmutableStyle
@JsonDeserialize(as = BlogState.class)
public abstract class AbstractBlogState implements Jsonable {

  public static final BlogState EMPTY = BlogState.of(Optional.empty());

  @Value.Parameter
  public abstract Optional<PostContent> getContent();

  @Value.Default
  @JsonProperty("isPublished")
  public boolean isPublished() {
    return false;
  }

  public BlogState withBody(String body) {
    if (isEmpty())
      throw new IllegalStateException("Can't set body without content");
    return BlogState.builder().from(this).content(
      Optional.of(getContent().get().withBody(body))).build();
  }

  @JsonIgnore
  public boolean isEmpty() {
    return getContent() == null;
  }

}
@Value.Immutable
@ImmutableStyle
@JsonDeserialize(as = PostContent.class)
public interface AbstractPostContent extends Jsonable {
  @Value.Parameter
  public String getTitle();

  @Value.Parameter
  public String getBody();

}

A few things worth noting 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.