Skip to main content
pragmatic clean code minimizing cognitive load in production java

Data Modeling with Modern Java

3 min read Chapter 8 of 25
Summary

Java 21's Algebraic Data Types—sealed hierarchies (sum types)...

Java 21's Algebraic Data Types—sealed hierarchies (sum types) and records (product types)—enable making illegal states unrepresentable at compile-time. Combined with pattern matching for switch and record patterns, they reduce defensive coding, runtime validation, and cognitive load, improving maintainability.

Data Modeling with Modern Java

Java 21 introduces several features that significantly enhance data modeling, making it easier to create robust, maintainable, and efficient code. At the heart of these enhancements are Algebraic Data Types (ADTs), which include Sum Types (sealed hierarchies) and Product Types (records). By leveraging these features, developers can make illegal states unrepresentable, reducing the need for defensive coding and minimizing the risk of runtime errors.

Sum Types: Sealed Hierarchies

Sealed hierarchies, as introduced in Java 21, allow for the definition of classes or interfaces that restrict which other classes or interfaces may extend or implement them. This feature enables exhaustive analysis by the compiler, ensuring that all possible variants of a domain model are handled. For instance, consider an OrderStatus sealed interface that permits only New, Shipped, Delivered, and Cancelled records. This design prevents the creation of invalid order statuses at compile-time.

public sealed interface OrderStatus permits New, Shipped, Delivered, Cancelled {}

public record New() implements OrderStatus {}

public record Shipped(String trackingId, Instant shippedAt) implements OrderStatus {}

public record Delivered(Instant deliveredAt) implements OrderStatus {}

public record Cancelled(String reason) implements OrderStatus {}

Product Types: Records

Records, introduced in Java 14 and further enhanced, provide a concise way to create immutable data carriers with built-in constructors, accessors, equals, hashCode, and toString. They are ideal for representing product types, where the state space is the product of the state spaces of its components. Records can be used in conjunction with sealed hierarchies to create robust and expressive domain models.

Pattern Matching for Switch

Java 21 finalizes pattern matching for switch expressions and statements, allowing for more expressive and concise logic branching. This feature, combined with sealed hierarchies and records, enables developers to write exhaustive and expressive code that handles all possible variants of a domain model.

public String getStatusMessage(OrderStatus status) {
    return switch (status) {
        case New() -> "Order created";
        case Shipped(var id, _) -> "In transit: " + id;
        case Delivered(var time) -> "Arrived at " + time;
        case Cancelled(var reason) -> "Void: " + reason;
    };
}

Making Illegal States Unrepresentable

By leveraging sealed hierarchies, records, and pattern matching, developers can make illegal states unrepresentable, reducing the need for defensive coding and runtime validation. This approach not only improves code robustness but also enhances readability and maintainability.

FeatureJava 8/11 EraModern Java (21+)
Data CarrierPOJO (Lombok/Boilerplate)Records (Native)
Hierarchy ControlFinal/Abstract onlySealed (Regulated inheritance)
Logic Branchingif/else instanceof checkPattern Matching for switch
ExtractionManual gettersRecord Deconstruction Patterns
ValidityRuntime validation (Hibernate)Type-system level constraints

In conclusion, Java 21+ features such as sealed hierarchies, records, and pattern matching provide a powerful toolkit for data modeling. By applying these features, developers can create more robust, maintainable, and efficient code, reducing the risk of runtime errors and improving overall code quality.

Sources