§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
andfinal
- creates getters for each field
- creates correct
equals
,hashCode
and a human-friendlytoString
- 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.18.18"
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.