Primitive Obsession
Primitive Obsession models rich domain concepts with raw primitives, scattering validation and enabling argument confusion. Wrap concepts in immutable value objects so rules live in one place and invalid states cannot be built.
Primitive Obsession is the habit of representing domain concepts with language primitives — String, int, double, Map — rather than purpose-built types. A user ID, an email, a price, and a temperature are all just String or double, so nothing distinguishes them and nothing guards their rules.
Why It Happens
Primitives are built in and cost nothing to declare. Early in a project a phone number is "just a string," and creating a PhoneNumber class feels like ceremony. As the system grows, the concept acquires rules (formatting, validation, equality, units), but the primitive representation stays, and the rules leak into every caller.
Why It Hurts
Validation is duplicated wherever the value is used, so some paths check it and others forget. Two unrelated String parameters can be passed in the wrong order with no error. Units are invisible: was that distance meters or miles? Business logic that belongs on the concept (currency conversion, ID formatting) has no home and spreads as free functions. The type system, the cheapest available tool for correctness, sits idle.
Warning Signs
- IDs, money, emails, and quantities all typed as
String/int/double. - The same validation or format check repeated in many places.
- Numbers without units, leading to unit-mismatch bugs.
- Long parameter lists of same-typed primitives that are easy to transpose.
Better Alternatives
Introduce value objects: small immutable types (Money, EmailAddress, UserId, Celsius) that validate on construction and centralize behavior. This makes invalid states unrepresentable and gives each concept a single source of truth for its rules. Domain-driven design treats these as the building blocks of the model. Type-driven design uses distinct types so the compiler rejects mixing a UserId with an OrderId.
How to Refactor Out of It
Identify a primitive that carries rules or is frequently confused. Create a value object wrapping it, with validation in the constructor and an explicit accessor. Replace the primitive at creation points first, then propagate the type outward and let the compiler locate every usage. Move duplicated validation and behavior onto the new type. Keep value objects immutable and define equality by value. Tackle one concept at a time; each conversion typically deletes scattered checks and clarifies signatures.