Immutable Class in Java – Complete Guide with Examples
An Immutable Class in Java is a class whose instances (objects) cannot be modified after creation. Once an object of an immutable class is created, its state remains constant throughout the lifetime of the object. Immutable objects are widely used in multi-threaded environments because they are inherently thread-safe and don’t require synchronization.
✅ Why Use Immutable Class?
- Thread-safety: Immutable objects can be safely shared between multiple threads without additional synchronization.
- Caching and Performance: Immutable objects can be cached and reused, reducing the need to create new objects frequently.
- Security: Since the object’s state can’t be changed, it prevents accidental or malicious modifications.
✅ Key Characteristics of Immutable Class
- Final Class: The class should be declared
final
to prevent subclassing which might override methods and break immutability. - Private Final Fields: All fields should be declared
private
andfinal
to prevent direct access and modification. - No Setter Methods: Do not provide setters. Only provide getters to expose field values.
- Deep Copy in Constructor: When the class has mutable fields (like arrays or collections), perform deep copying in the constructor and in the getter methods to prevent external modification.
✅ Example of Immutable Class
import java.util.Date;
public final class Employee {
private final int id;
private final String name;
private final Date joiningDate;
// Constructor performs deep copy of mutable objects
public Employee(int id, String name, Date joiningDate) {
this.id = id;
this.name = name;
this.joiningDate = new Date(joiningDate.getTime()); // Defensive copy
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public Date getJoiningDate() {
return new Date(joiningDate.getTime()); // Return copy to maintain immutability
}
@Override
public String toString() {
return "Employee { " +
"id=" + id +
", name='" + name + '\'' +
", joiningDate=" + joiningDate +
" }";
}
}
✅ Usage Example
import java.util.Date;
public class Main {
public static void main(String[] args) {
Date date = new Date();
Employee emp = new Employee(101, "John Doe", date);
System.out.println(emp);
// Attempt to modify original date object
date.setTime(0);
// The internal state of Employee remains unchanged
System.out.println(emp);
// Attempt to modify date retrieved via getter
emp.getJoiningDate().setTime(0);
// Employee’s joiningDate remains immutable
System.out.println(emp);
}
}
✅ Output Explanation
- Even though we modify the original
Date
object and the one obtained fromgetJoiningDate()
, the internal state of theEmployee
object remains unchanged. - This demonstrates how defensive copying preserves immutability.
✅ Best Practices for Immutable Class
- Use primitive types and immutable objects (like
String
,Integer
) for fields whenever possible. - For fields that are mutable objects (like
Date
, arrays, or collections), always use deep copy in constructors and getters. - Mark the class as
final
to prevent subclassing. - Avoid exposing mutable fields directly.
✅ When Should You Use Immutable Classes?
- When thread-safety is crucial.
- When object state should not change after creation.
- For value objects such as coordinates, currency, or configurations.
- To avoid defensive copying outside your control.
✅ Advantages and Disadvantages
Advantages | Disadvantages |
---|---|
Thread-safe by design | Higher memory footprint if many copies needed |
Simple to reason about object state | Performance overhead due to object copying |
Can be safely shared across threads | Cannot be partially updated, needs recreation |
✅ Conclusion
Immutable classes are a cornerstone of robust and thread-safe application design in Java. They offer simplicity, safety, and reliability in concurrent environments. However, they should be used judiciously where immutability is beneficial, and care must be taken with mutable fields.