skip to content

What is an Anemic Domain Model, why did Martin Fowler call it an anti-pattern, and what does the trade-off actually look like in practice?

level: seniorimportance: must knowfreq 60%

answer

  1. data-only entities + logic in stateless services
  2. invariants go unguarded
  3. Fowler's 2003 blog post coined the critique
  4. fine for simple CRUD, bad for real behavioral complexity
  5. rich model = self-enforcing methods instead

basics

~20 s

An anemic domain model is when your 'domain' objects are just data bags with getters and setters and no real logic, while all the actual business rules live in separate service classes. It looks object-oriented but behaves like plain procedural code.

solid answer

~40 s

An Anemic Domain Model has entities with fields and getters/setters but essentially no behavior; business logic instead sits in stateless service classes that pull data out of the entities, act on it, and push results back. Fowler calls this an anti-pattern because it forfeits OO's core benefit — encapsulating behavior with the data it operates on — while still paying the full cost of building an object model: classes, ORM mapping, associations. In practice the trade-off is nuanced: for genuinely simple CRUD logic it's basically a relabeled Transaction Script and is fine. The anti-pattern bites specifically when the domain has real behavioral complexity that's being pushed into services anyway, scattering related rules, causing duplication, and losing the invariant-enforcement a rich object would give for free.

go deeper

for a junior

Should recognize the pattern from a code example — a data-only class plus a separate service holding the logic — and describe it in plain terms.

for a middle

Should explain why Fowler considers it an anti-pattern (loss of encapsulation and invariant enforcement) and when it's actually an acceptable lightweight choice.

for a senior

Should identify concrete production failure modes, such as bypassable invariants and duplicated rule checks across services, and propose a specific refactor toward richer entity methods.

for a principal

Should assess, at a codebase or organizational level, whether an anemic style is a deliberate and appropriate fit versus accidental technical debt, and set direction for where investing in richer modeling is actually worth it.

## What the shape looks like in code An Anemic Domain Model looks object-oriented from the class diagram but behaves procedurally underneath. A typical `Order` entity exposes only `getOrderId()`, `getStatus()`, `setStatus()`, and similar accessors, with no methods that actually do anything. The business logic for cancelling an order — checking that its current status permits cancellation, releasing reserved inventory, recording the cancellation reason — lives instead in a separate stateless class, something like `OrderService.cancel(Order order)`, which reads the entity's state through getters, decides what to do, mutates it through setters, and calls a repository to persist the result. The critical mechanical detail is that **nothing inside Order itself guards its own state**: any code anywhere that has a reference to the entity can call `setStatus("SHIPPED")` directly, bypassing whatever transition rules `OrderService.cancel()` was supposed to enforce, because those rules live outside the object rather than inside it. ## Why it emerges This pattern usually emerges from a mix of tooling pressure and habit rather than deliberate design. - Many ORM frameworks, particularly older Hibernate/JPA-style setups, historically rewarded simple getter/setter entities — they're easier to proxy, lazy-load, and serialize — and penalized entities with complex constructors or embedded behavior. - A layered Controller-Service-Repository mental model, borrowed from procedural design, gets applied uncritically to what's nominally object-oriented code, and 'the model' quietly becomes just a data-transfer shape. - Sometimes this is a perfectly reasonable, deliberate choice: for pipelines that are inherently data-transformation shaped, like ETL or reporting, there often isn't any meaningful 'behavior' to encapsulate in the first place, and an anemic shape is simply the honest reflection of that. ## The trade-off Fowler actually argued The trade-off has real costs named on both sides, and treating it as always-wrong misses Fowler's actual argument. **The cost:** - business rules end up scattered and duplicated across multiple service methods that all touch the same entity, - invariants are not enforced at the type level so any code with a reference to the entity can push it into an invalid state, - and the design forfeits polymorphism — there's no calculateX() method on a subtype to override, because there's no calculateX() method on the entity at all, so equivalent logic ends up as branching inside a service instead. **The benefit**, which is genuinely worth naming: - it's simpler to reason about for logic that really is simple, - it plays extremely well with ORMs and serialization frameworks expecting plain data classes, - it avoids behavior-carrying entities colliding awkwardly with lazy-loading and proxying machinery in an ORM, - and stateless services are easy to test and mock independent of any entity's internal state. Fowler's own 2003 post on the subject makes this nuance explicit: the pattern is fine as a lightweight, Transaction-Script-style design; it becomes a genuine problem specifically when a team believes it is doing rich domain modeling — or claims to be practicing Domain-Driven Design — while the behavior keeps leaking out into services anyway, at which point you're paying for an object model's ceremony without collecting any of its benefits. As he puts it, 'the fundamental horror of this anti-pattern is that it's so contrary to the basic idea of object-oriented design.' ## The production failure mode The production failure mode is concrete and recurring: a rule like 'an order can only be cancelled while in PENDING status' is implemented correctly inside `OrderService.cancel()`, but a second code path — say a bulk admin-cancellation batch job — calls `order.setStatus("CANCELLED")` directly, or reimplements the check slightly differently and forgets to also release reserved inventory. The result is orders sitting in a cancelled state with inventory never released, a state-consistency bug that a rich domain method — `order.cancel()`, enforcing its own invariant internally and always releasing inventory as part of the same operation — would have prevented by construction, because there would be only one legal entry point for cancellation. A related, more diffuse symptom is **service-class bloat**: since Order itself can do nothing, every operation that touches an order lands in one of several thousand-line service classes, and 'where does the logic for X live' becomes 'somewhere in one of twelve service classes' rather than an obvious answer. ## Where it is most common This pattern is extremely common in enterprise Java and Spring codebases — `@Entity` classes with nothing but Lombok-generated getters and setters, paired with `@Service` classes holding all the actual logic — and the anemic-versus-rich argument is essentially Domain-Driven Design's tactical-pattern advocacy (rich objects that own and enforce their own invariants, per Eric Evans) applied directly to the older question these Patterns of Enterprise Application Architecture domain-logic patterns raise: put simply, an anemic model is a reasonable Transaction Script wearing an object-oriented costume, and the failure is only in mistaking the costume for the real thing.

  • Is an anemic domain model always wrong?
    No. For CRUD-shaped logic with little real behavior, it's effectively a Transaction Script wearing an OO-looking skin, and it's a reasonable, low-ceremony choice. It becomes a genuine problem specifically when a team believes it's building a rich domain model — or claims to be doing DDD — while the actual behavior keeps leaking into services anyway, causing the scattering and invariant gaps described above.
  • How would you refactor an anemic Order entity's cancel logic into a richer model?
    Move the state-transition guard and its side effects — checking the current status is legal, releasing inventory, recording the reason — into an order.cancel() method on the entity itself. Any caller that wants to cancel an order, whether it's a controller, a service, or a batch job, then has to go through that single guarded entry point, making illegal states unreachable instead of merely discouraged.
  • What ORM-related pressure historically pushed teams toward anemic models?
    Many ORMs traditionally reward plain getter/setter entities because they're simpler to proxy, lazy-load, and serialize, and they penalize entities with complex constructors or embedded behavior. Teams following the path of least resistance end up with anemic entities even in domains that would genuinely benefit from richer, self-enforcing behavior.

Like a hospital where a patient's chart sits in a folder that any staff member can scribble on directly, versus a chart that only lets you record a diagnosis by going through the attending physician, who checks it makes sense first.

saying these in an interview costs you the question

  • says anemic domain model is always bad with no exceptions
  • thinks it's identical to Transaction Script by definition rather than a similar-in-effect variant
  • doesn't mention loss of encapsulated invariants as the core cost
  • can't attribute or explain the origin of the critique
  • believes moving getters/setters behind an interface fixes the underlying problem

context