I care a lot about object validation — it keeps bugs out of the system and makes reasoning about code so much easier. Where and how you validate, though, depends on what you’re building: should the object be immutable, will an ORM like Hibernate manage it, or are you following a functional style? In this post I’ll walk through the trade-offs I usually consider: constructor validation, immutability, and a few alternative approaches that have worked well for me.
Funny story: I once spent an entire afternoon chasing a subtle bug that turned out to be an empty string sneaking into a value object. After that, I’m biased toward validating early.
Constructor Validation: Enforcing Correctness at Creation
One straightforward way I make sure objects are valid is to check inputs right inside the constructor. The idea is simple: if an object can’t be created in a valid state, don’t create it. Here’s a compact example I often use:
public class UserRequest {
private final String username;
public UserRequest(String username) {
if (username == null || username.length() < 3) {
throw new IllegalArgumentException("Username must be at least 3 characters long.");
}
this.username = username;
}
public String getUsername() { return username; }
}
✅ Advantages of Constructor Validation
- It prevents invalid objects from existing — which means fewer defensive checks scattered around your code.
- Validation logic stays inside the class where it belongs.
- It plays nicely with immutable objects: if construction enforces correctness, the instance can be trusted afterwards.
⚠️ Challenges with Constructor Validation
- ORM and dependency-injection frameworks (Hibernate, JPA, etc.) often need a no-arg constructor. That can mean your validation gets bypassed during reflection-based construction.
- Serialization/deserialization (Jackson, etc.) sometimes populate fields without going through your constructor, so watch out.
- For very large objects, doing heavy validation at construction can add measurable overhead.
- The builder pattern complicates constructor validation — builders assemble state in steps, so enforcing everything in a single constructor isn’t always practical.
Alternative: Static Factory Methods
If you want flexibility but still want validation, a static factory method is a neat pattern I’ve used many times:
public class UserRequest {
private final String username;
private UserRequest(String username) {
this.username = username;
}
public static UserRequest create(String username) {
if (username == null || username.length() < 3) {
throw new IllegalArgumentException("Username must be at least 3 characters long.");
}
return new UserRequest(username);
}
}
✅ Advantages of Static Factory Methods
- Lets you validate inputs before constructing the object.
- You can return cached instances or different implementations depending on input.
- Often easier to integrate with frameworks that have strict construction requirements.
The Role of Immutability in Object Validation
I like immutability for value objects — once created, they don’t change, and that gives you strong guarantees. That said, immutability isn’t always practical, especially for ORM-managed entities.
Immutable Object Example
public class Address {
private final String city;
private final String street;
public Address(String city, String street) {
if (city == null || street == null) {
throw new IllegalArgumentException("City and street cannot be null.");
}
this.city = city;
this.street = street;
}
public String getCity() { return city; }
public String getStreet() { return street; }
}
✅ When to Use Immutability
- Value objects that don’t change (money, coordinates, etc.).
- Log entries or audit records where history should be preserved.
- Functional-style code where you want to avoid side effects.
❌ When Immutability Is Impractical
- ORM-managed entities that require change tracking.
- Objects that are updated frequently in-place.
- Complex constructions that are naturally implemented with builders.
Best Practice: Combining Mutable Entities with Immutable Value Objects
In practice I often pick a hybrid approach: keep entities mutable where the framework needs it, but make the value objects they contain immutable. It reduces accidental state changes while staying compatible with ORMs.
Example: Mutable Entity with Immutable Value Object
@Embeddable
public class Address {
private final String city;
private final String street;
protected Address() {} // Required for JPA
public Address(String city, String street) {
this.city = city;
this.street = street;
}
}
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
@Embedded
private Address address; // Immutable
protected User() {}
public User(String username, Address address) {
this.username = username;
this.address = address;
}
public void updateUsername(String newUsername) {
this.username = newUsername;
}
}
✅ Why This Works
- The entity remains easy for the ORM to manage.
- Embedded immutable value objects (like
Address) prevent accidental mutations and simplify reasoning about data.
Wrap-up
There isn’t a single right answer — only trade-offs. My simple checklist when I decide:
- If I need a rock-solid, always-valid instance, validate early (constructor or factory).
- If the framework or serializers get in the way, prefer factories or builders with validation.
- Make value objects immutable whenever it makes sense; keep entities mutable when the persistence layer requires it.
Hopefully these notes help you make pragmatic choices in your codebase. If you’d like, I can take a specific class from your project and sketch a safe, tested implementation using one of these patterns. Happy to help with a concrete example!