This chapter focuses on modern Java features that make domain code safer and easier to read. You will learn enums, records, BigDecimal, wrapper-type pitfalls, value objects, factory methods, and code-reading patterns that help Java stay expressive without becoming noisy. The project context is still the e-commerce app, but the real goal is cleaner Java reasoning for interviews and real code.

Why Chapter 7 is about cleaner Java, not flashy Java

Once the project has safe objects and clearer failure handling, the next step is making the domain code more expressive. Java has accumulated language and library tools that can reduce boilerplate and sharpen meaning, but they only help when they fit the design. This chapter focuses on the features that most often improve everyday domain code rather than features that merely look modern.

The main question is always the same: does this feature help the reader understand the model better? If the answer is yes, the feature is helping. If the answer is no, modern syntax is just another distraction.

  • Modern Java should improve intent, not just shorten code.
  • Cleaner domain code comes from good modeling plus the right language features.
  • Interview answers get stronger when you explain why a feature helps the design.

enum OrderStatus {
    DRAFT,
    PAID,
    SHIPPED,
    CANCELLED
}

Enums and closed sets of meaning

Enums are ideal when a value belongs to a closed, meaningful set such as order status, payment state, or shipment speed. They are better than free-form strings because they make invalid states harder to express and give the compiler a chance to help.

A strong Java designer uses enums when the domain has a fixed vocabulary. That is both a readability improvement and a correctness improvement. The key interview idea is that enums model meaning, not just a list of constants.

  • Enums are safer than open-ended string values for fixed domains.
  • They make illegal states harder to represent.
  • Enums improve both readability and type safety.

public enum PaymentState {
    PENDING,
    AUTHORIZED,
    FAILED,
    REFUNDED
}

Records and value-shaped data

Records are useful when a type mainly exists to carry a small set of related values with clear value semantics. They reduce boilerplate for constructors, accessors, equals, hashCode, and toString, but the deeper benefit is design clarity: a record communicates that the type is mostly a value, not a mutable behavior-heavy entity.

That does not mean records replace all classes. Rich domain entities with identity, lifecycle, and changing state still often deserve full classes. The skill is knowing when value-shaped data is the right fit.

  • Records are strong for small immutable-style value carriers.
  • They communicate value semantics clearly.
  • Not every domain type should become a record.

public record MoneySummary(BigDecimal subtotal, BigDecimal tax, BigDecimal total) {}

BigDecimal and money correctness

Money is where many beginner Java habits break down. Using double feels simple, but binary floating-point representation can produce precision surprises that are unacceptable in financial logic. BigDecimal exists because some domains require exact decimal reasoning.

The important idea is not only "use BigDecimal for money." It is understanding why: domain correctness sometimes matters more than convenience. Once a learner explains precision tradeoffs clearly, they move from syntax knowledge into engineering judgment.

  • double is convenient but not ideal for exact money calculations.
  • BigDecimal improves decimal precision for financial logic.
  • Correctness requirements should shape your numeric choice.

BigDecimal subtotal = new BigDecimal("19.99");
BigDecimal tax = new BigDecimal("1.60");
BigDecimal total = subtotal.add(tax);

Wrapper types, autoboxing, and value objects

Wrapper types such as Integer and Boolean are useful, but they come with caveats around nullability, identity, and autoboxing overhead. Strong Java answers understand when a primitive is enough and when an object type is required. Value objects push the design further by grouping meaningful data into a dedicated type, reducing the spread of raw strings and loosely related values.

This is where modern Java becomes cleaner domain code. Instead of passing raw values everywhere, you can create meaningful types that express intent and reduce accidental misuse.

  • Primitives and wrapper objects have different tradeoffs.
  • Autoboxing can hide conversions and null risks.
  • Value objects make domain intent more explicit.

public record EmailAddress(String value) {
    public EmailAddress {
        if (value == null || value.isBlank()) throw new IllegalArgumentException("email");
    }
}

Static factory methods and code-reading choices

Constructors are not the only way to create objects. Static factory methods can name intent more clearly, control creation rules, or reuse instances when appropriate. They often read better than overloaded constructors because the method name can explain the difference directly.

This topic also becomes a code-reading exercise. Modern Java asks the reader to interpret records, enums, value objects, and factory methods quickly. The skill is not memorizing features one by one. It is recognizing how each feature changes the shape and meaning of the code you are reading.

  • Factory methods can express creation intent better than overloaded constructors.
  • Named creation paths improve readability.
  • Good code reading means understanding what a feature communicates about the design.

public final class OrderId {
    private final String value;

    private OrderId(String value) {
        this.value = value;
    }

    public static OrderId from(String raw) {
        return new OrderId(raw.trim());
    }
}

Chapter takeaway

Modern Java is not about collecting new syntax. It is about choosing features that express intent clearly, model values honestly, and reduce common bugs in everyday backend code.