Objects carry behavior
Use objects when the type must protect rules, transitions, or business meaning.
One of the most useful OOAD distinctions is the difference between an object and a data structure. Objects hide data behind behavior. Data structures expose data and are manipulated by external procedures. Both are valid. The design challenge is choosing the right one for the right kind of problem.
A checkout request DTO should usually be a data structure. An Order aggregate that owns payment and status rules should usually be an object. When teams confuse the two, they end up with anemic domain models, bloated services, or overengineered wrappers around simple transport data.
Java makes both styles easy. Records, DTOs, and response payloads are excellent data structures. Rich domain types are better when behavior and invariants matter.
The mistake is not using data structures. The mistake is using them at the center of a complex domain and then letting services carry all the business meaning. That is how an anemic domain model emerges.
Conversely, the mistake is also not turning every request and response shape into a rich object with methods it does not need. OOAD works best when the style matches the responsibility.
Use objects when the type must protect rules, transitions, or business meaning.
Use DTOs, records, or simple carriers when the main job is transport or serialization.
Many design problems come from putting domain behavior into services while keeping the model passive.
package org.javaomnibus.ecommerce.foundations;
import java.util.List;
public record CheckoutRequest(String customerId, List items) {}
public record CheckoutItem(String sku, int quantity) {}
public final class Order {
private final String customerId;
private final List lines;
private boolean submitted;
public Order(String customerId, List lines) {
this.customerId = customerId;
this.lines = List.copyOf(lines);
}
public void submit() {
if (lines.isEmpty()) {
throw new IllegalStateException("An order must contain at least one line");
}
this.submitted = true;
}
public boolean isSubmitted() {
return submitted;
}
}
record OrderLine(String sku, int quantity) {}
Rich objects require thoughtful modeling. DTOs are faster to create. The right choice depends on where the complexity lives and whether the type is central to business behavior or mainly a boundary shape.