Creating a new object based on an existing one is a common pattern in Java, especially when working with DTOs (Data Transfer Objects), view models, or cloning domain objects with slight modifications. In this article, we’ll explore the best practices for copying data from an existing object into a new instance, ensuring immutability, separation of concerns, and clarity.
Why You Might Want to Clone or Map Objects
Here are a few common scenarios:
- Creating a simplified view model (DTO) from a full entity.
- Transferring only safe or relevant data between layers.
- Applying transformations while keeping the original object untouched.
- Resetting or repurposing objects with pre-filled values.
The Simple Way: Field-by-Field Constructor
The most straightforward approach is using a constructor that takes the original object as a parameter and copies the relevant fields.
Example:
public class AccountSummaryDto {
private String id;
private String status;
private int refreshCount;
public AccountSummaryDto(AccountEntity original) {
this.id = original.getId();
this.status = original.getStatus();
this.refreshCount = original.getRefreshCount();
}
}
This works great when the class has a small number of fields or when you want full control over what’s copied.
Best Practices
1. Use Defensive Copies for Mutable Fields
If your class contains mutable objects like List
, Map
, or LocalDateTime
, use defensive copying:
this.tags = new ArrayList<>(original.getTags());
This prevents accidental changes to the new object from impacting the original.
2. Handle Nulls Gracefully
If the source fields can be null
, use conditional logic or Optional
to avoid NullPointerException
.
this.expiryDate = original.getExpiryDate() != null
? LocalDate.from(original.getExpiryDate())
: null;
3. Avoid Shared References to Custom Objects
For custom inner objects, it’s often safer to clone or wrap them:
this.accessDetails = new AccessDetails(original.getAccessDetails());
This assumes AccessDetails
has its own copy constructor.
When to Use Mapping Libraries
When objects become large or deeply nested, manual copying can be tedious and error-prone. Use libraries like:
- MapStruct – compile-time mapping with zero runtime overhead.
- ModelMapper – runtime mapping, useful for prototyping.
- Dozer – older, but still used in some legacy systems.
Example with MapStruct:
@Mapper
public interface AccountMapper {
AccountSummaryDto toDto(AccountEntity entity);
}
Anti-Patterns to Avoid
- Shallow copies of mutable fields – can lead to unexpected side effects.
- Using serialization for cloning – it’s slow and often unnecessary.
- Copying too much – DTOs should carry only what’s needed.
Summary
Creating a new object from an existing one is easy in Java but should be done with care:
- Use copy constructors or builder patterns for clarity and control.
- Ensure immutability with defensive copies.
- Use mappers or mapping libraries when objects grow in size or complexity.
Sample Use Case in a Real Project
Let’s say you have a ConsentEntity
class with 10+ fields, and you need to expose only part of the data:
public class ConsentDto {
private String consentId;
private String userStatus;
public ConsentDto(ConsentEntity entity) {
this.consentId = entity.getId();
this.userStatus = entity.getStatus();
}
}
This keeps your API clean, secure, and decoupled from internal representations.
Want to Learn More?
- Java Constructors Explained
- MapStruct Official Docs
- Effective Java (Book) – Chapter on Object Construction
If you’re building enterprise-level applications or clean architecture systems, object copying is a pattern you’ll use frequently. Make it safe, efficient, and readable—and your future self (and teammates) will thank you.