SyntaxStudy
Sign Up
Java Records, Sealed Classes, and Class Design Patterns
Java Beginner 1 min read

Records, Sealed Classes, and Class Design Patterns

Java 16 introduced records as a concise way to model immutable data. A record declaration automatically provides a canonical constructor, private final fields, public accessors, equals, hashCode, and toString. Records are ideal for value objects like coordinates, DTOs, and configuration data where you just need to carry data without mutable state. Java 17 introduced sealed classes, which restrict the set of classes that can extend a given class or implement a given interface. By listing permitted subclasses with the permits clause, you make the class hierarchy explicit and exhaustive. Sealed hierarchies work well with pattern matching in switch statements because the compiler can verify that all cases are covered. Common class design patterns include the Builder pattern for constructing objects with many optional parameters, the Singleton pattern for ensuring only one instance of a class exists, and value objects for representing domain concepts without identity. The Builder pattern avoids telescoping constructors and makes construction code readable by using a fluent API.
Example
// Java 16+ record
record Point(double x, double y) {
    // Compact canonical constructor with validation
    Point {
        if (Double.isNaN(x) || Double.isNaN(y))
            throw new IllegalArgumentException("NaN coordinates not allowed");
    }

    // Additional methods are allowed
    double distanceTo(Point other) {
        double dx = this.x - other.x;
        double dy = this.y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }
}

// Builder pattern
class HttpRequest {
    private final String url;
    private final String method;
    private final int    timeoutMs;
    private final java.util.Map<String, String> headers;

    private HttpRequest(Builder b) {
        this.url       = b.url;
        this.method    = b.method;
        this.timeoutMs = b.timeoutMs;
        this.headers   = java.util.Collections.unmodifiableMap(b.headers);
    }

    @Override public String toString() {
        return method + " " + url + " (timeout=" + timeoutMs + "ms, headers=" + headers + ")";
    }

    static class Builder {
        private final String url;
        private String method  = "GET";
        private int    timeoutMs = 5_000;
        private final java.util.Map<String, String> headers = new java.util.LinkedHashMap<>();

        Builder(String url) { this.url = url; }
        Builder method(String m)          { this.method = m; return this; }
        Builder timeout(int ms)           { this.timeoutMs = ms; return this; }
        Builder header(String k, String v){ this.headers.put(k, v); return this; }
        HttpRequest build()               { return new HttpRequest(this); }
    }
}

public class ClassDesignPatterns {
    public static void main(String[] args) {
        // Record usage
        Point a = new Point(0, 0);
        Point b = new Point(3, 4);
        System.out.println("Distance: " + a.distanceTo(b)); // 5.0
        System.out.println(b);                               // Point[x=3.0, y=4.0]

        // Builder pattern
        HttpRequest req = new HttpRequest.Builder("https://api.example.com/data")
            .method("POST")
            .timeout(10_000)
            .header("Content-Type", "application/json")
            .header("Authorization", "Bearer token123")
            .build();
        System.out.println(req);
    }
}