Favoring Immutability in Object Design
Immutable objects cannot have their observable state changed after construction, which makes them easier to reason about and safer to share. In Java backend systems, immutability can reduce synchronization concerns, prevent accidental state changes, and make value-like objects more predictable.
An immutable object establishes its complete state during construction and then exposes no operation that changes that state. Its fields are typically `private` and often `final`, with no setters or other mutating methods. If a different state is needed, the design creates a new object instead.
Immutability is especially useful for value objects such as money, coordinates, identifiers, or date-like representations. Two instances can be treated according to their values rather than requiring callers to coordinate ownership of changing state. This reduces the number of hidden dependencies between parts of an application.
Immutable objects are also easier to share between threads because concurrent code cannot modify their state. However, `final` fields alone do not automatically make an object deeply immutable: a field may reference a mutable collection or another mutable object. Defensive copying and immutable collection types may be required for deep immutability.
There can be a performance trade-off because changing logical state may require allocating another object. In practice, the benefits are often strongest for small domain values and shared configuration or state representations, while heavily mutated internal data structures may be better kept mutable behind a controlled boundary.
Key points
- Construct complete state before publication and expose no mutators.
- `final` fields help enforce stable references but do not guarantee deep immutability.
- Use immutable value objects to make sharing and equality easier to reason about.
- Defensively copy mutable inputs and outputs when deep immutability is required.
- Immutability simplifies concurrent access but may create additional allocations.
Interview questions
What makes a Java class immutable?
An immutable class establishes its state during construction and does not provide operations that change that state. Its state is usually stored in private fields, commonly with `final` references. The class should also prevent callers from mutating referenced objects, for example by making defensive copies. The class itself is often made `final` to prevent subclasses from introducing mutable behavior.
Why is immutability useful in concurrent Java applications?
An immutable object's state cannot change after construction, so multiple threads can safely read the same instance without coordinating mutations to that state. This removes many race conditions associated with shared mutable objects. Immutable objects can therefore be useful for messages, configuration, cached values, and value objects. Publication and construction still need to follow safe Java concurrency practices.
Why doesn't `private final List<String> roles` make a class immutable?
`final` prevents the field from being reassigned, but it does not prevent the referenced list from changing. If the constructor stores a caller-owned mutable list, the caller can modify that list later and indirectly change the object's state. Returning the same mutable list would create another mutation path. A defensive copy or an immutable collection such as one returned by `List.copyOf` closes those paths.
What are the trade-offs of immutable objects?
The main cost is that a logical state change may require constructing another object, which can increase allocation and garbage-collection pressure in mutation-heavy workloads. Immutability also requires careful handling of nested mutable state and may require copying collections. The benefits are simpler reasoning, safer sharing, easier caching, and fewer synchronization concerns. The right boundary is often to keep externally visible domain values immutable while allowing carefully encapsulated mutable structures internally.
How would you design an immutable value object for money?
I would make the class immutable, validate its construction inputs, and represent the monetary amount with an appropriate exact numeric type such as `BigDecimal`, together with an explicit currency. Operations such as addition would return a new money instance rather than changing the receiver. Equality and hashing should reflect the value semantics chosen for the domain. Any mutable collaborators would need to be protected from external modification.