Java Omnibus
OOAD & Architecture / Anti-patterns / Primitive Obsession in Java
Anti-Patterns Java Order Management
Anti-Patterns

Primitive obsession hides domain meaning behind strings, numbers, and booleans that cannot defend themselves.

Primitive obsession is one of the most widespread anti-patterns because it feels efficient at first. Instead of creating types for money, email, status, country, or order identifiers, teams keep using strings, numbers, and booleans. The problem is that primitives are cheap containers but poor guardians of meaning.

Why This Topic Matters

Why it matters in real Java systems

In Java order systems, status codes, currency values, payment methods, addresses, and identifiers often start as raw strings. That pushes validation, normalization, and business meaning outward into services, utilities, and controllers, which multiplies duplication and weakens the model.

Core Explanation

The failure shape behind this anti-pattern

Primitive obsession usually points to missing value objects, enums, or small domain types. The issue is not that primitives are evil. The issue is that important concepts become less safe and less readable when they are represented only as general-purpose scalar values.

For Java developers, this is especially important because strong type design is one of the easiest ways to improve correctness and readability at the same time. A small type like OrderId, Money, or EmailAddress often removes entire categories of repetition and confusion.

The best refactorings here are often incremental. Start with the most repeated or most error-prone raw concepts and give them clearer representation.

Concept Breakdown

Key signals to watch for

Signal

Meaning is externalized

A raw string does not know whether it is a valid order ID, country code, or payment state.

Signal

Validation spreads

When the value cannot defend itself, every caller must remember the rules separately.

Signal

The model loses precision

Different concepts become harder to distinguish because they all look like String or int.

Java Example

Java example in the Order Management domain

Java example: raw primitives hiding domain concepts
package org.javaomnibus.ecommerce.antipatterns;

public record PaymentRequest(
    String orderId,
    String customerEmail,
    String currency,
    double amount,
    String paymentMethod,
    String status
) {}
Code Walkthrough

Step by step

  1. Several different domain concepts are flattened into generic primitive forms.
  2. Each caller now has to know what counts as a valid order ID, email, currency, payment method, and status.
  3. This is why validation and normalization tend to spread in primitive-obsessed codebases.
  4. Refactoring toward small types restores precision and local ownership of meaning.
Real-World Usage

Where this shows up in practice

  • Introducing Money, EmailAddress, OrderId, CountryCode, and status enums into legacy Java code
  • Reducing repeated validation and conversion logic
  • Making service and API boundaries safer and more self-explanatory
Trade-Offs

How to respond proportionately

Too many tiny types introduced without clear value can annoy readers. The best candidates are concepts that carry repeated rules, high business meaning, or frequent misuse risk.

Common Mistakes

Frequent mistakes to watch for

  • Wrapping primitives without adding validation or meaning
  • Keeping the same scattered logic after introducing the new types
  • Leaving state strings in place even when legal transitions matter
  • Assuming primitive obsession is harmless because the tests currently pass
Related Pages

Related concepts


What To Read Next