Java Omnibus
OOAD & Architecture / Anti-patterns / Anemic Domain Model in Java
Anti-Patterns Java Order Management
Anti-Patterns

An anemic domain model appears when objects carry data but the real business behavior lives somewhere else.

An anemic domain model is one of the most common anti-patterns in enterprise Java. The entities exist, but they mostly expose fields and accessors. The real rules live in services, utilities, controllers, or repositories. This makes the model look object-oriented on the surface while behaving procedurally underneath.

Why This Topic Matters

Why it matters in real Java systems

In an order-management system, Order, Payment, and Shipment should usually protect at least some important rules and state transitions. When they become passive records, the business logic starts scattering into service layers, and the domain becomes harder to understand and defend.

Core Explanation

The failure shape behind this anti-pattern

An anemic model is often the result of convenience or framework defaults. It feels easy to persist entities and keep behavior in services, especially when teams are moving quickly. The long-term cost is that the domain loses its voice. Objects no longer express business meaning directly.

For Java teams, this is especially important because anemic models can coexist with sophisticated frameworks and still degrade design quality. The presence of entities and repositories does not guarantee real OO design.

The fix is not to force all behavior into entities. The fix is to restore meaningful domain ownership where the object should naturally protect state and rules.

Concept Breakdown

Key signals to watch for

Signal

Data without behavior

The domain objects exist, but they do not own the rules that give their data meaning.

Signal

Services become overly procedural

Service classes start simulating object behavior by manipulating passive entities from the outside.

Signal

Invariants become fragile

If the object does not defend its own state transitions, every caller must remember the rules separately.

Java Example

Java example in the Order Management domain

Java example: passive entity with rules living elsewhere
package org.javaomnibus.ecommerce.antipatterns;

public final class Order {
    private String status;
    private String customerId;
    private double total;

    public String getStatus() { return status; }
    public void setStatus(String status) { this.status = status; }
    public String getCustomerId() { return customerId; }
    public void setCustomerId(String customerId) { this.customerId = customerId; }
    public double getTotal() { return total; }
    public void setTotal(double total) { this.total = total; }
}

public final class OrderPaymentService {
    public void markPaid(Order order) {
        if (!"DRAFT".equals(order.getStatus())) {
            throw new IllegalStateException("Only draft orders can be paid");
        }
        order.setStatus("PAID");
    }
}
Code Walkthrough

Step by step

  1. Order holds the state, but OrderPaymentService owns the rule that gives the state meaning.
  2. That pattern repeats across many services in an anemic model.
  3. The object becomes less trustworthy because anyone can mutate it without going through meaningful behavior.
  4. A healthier design would let the object defend at least its core lifecycle transitions.
Real-World Usage

Where this shows up in practice

  • Persistence-first enterprise Java applications
  • Service-heavy architectures where domain objects became DTO-like
  • Systems where invariants are repeatedly revalidated in multiple services
Trade-Offs

How to respond proportionately

Not every data structure should be rich. Some boundaries are intentionally simple. The anti-pattern is not about having any simple objects. It is about weakening core domain concepts that should own business meaning.

Common Mistakes

Frequent mistakes to watch for

  • Over-correcting by moving orchestration concerns into entities
  • Equating every getter/setter entity with an anemic model without considering context
  • Leaving stringly typed states and externally managed transitions in place
  • Treating services as the default home for all business rules
Related Pages

Related concepts


What To Read Next