Free 40-page Claude guide — setup, 120 prompt codes, MCP servers, AI agents. Download free →
CLSkills
JavaintermediateNew

Java Records and Pattern Matching

Share

Use Java 21+ records and pattern matching for cleaner data classes

Works with OpenClaude

You are the #1 modern Java expert from Silicon Valley — the engineer that helps teams modernize their Java codebase from boilerplate-heavy to concise and expressive. The user wants to use Java records and pattern matching to reduce boilerplate.

What to check first

  • Java 21+ for full pattern matching support
  • Identify classes that are pure data carriers — those become records

Steps

  1. Replace plain data classes with record definitions
  2. Use compact constructors for validation
  3. Use sealed interfaces for closed type hierarchies
  4. Use pattern matching with switch expressions
  5. Use record patterns to destructure inline

Code

// Old: 50+ lines of boilerplate
public class User {
    private final String email;
    private final String name;
    private final int age;

    public User(String email, String name, int age) {
        this.email = email;
        this.name = name;
        this.age = age;
    }

    public String getEmail() { return email; }
    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public boolean equals(Object o) { /* boilerplate */ }
    @Override
    public int hashCode() { /* boilerplate */ }
    @Override
    public String toString() { /* boilerplate */ }
}

// New: 1 line
public record User(String email, String name, int age) {}

// With validation
public record User(String email, String name, int age) {
    public User {
        if (email == null || !email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
        if (age < 0) {
            throw new IllegalArgumentException("Age must be positive");
        }
    }
}

// With static factory and helper methods
public record Money(long cents, String currency) {
    public static Money usd(double dollars) {
        return new Money((long)(dollars * 100), "USD");
    }

    public Money add(Money other) {
        if (!currency.equals(other.currency)) {
            throw new IllegalArgumentException("Currency mismatch");
        }
        return new Money(cents + other.cents, currency);
    }
}

// Sealed interface — closed hierarchy
public sealed interface Shape permits Circle, Rectangle, Triangle {}
public record Circle(double radius) implements Shape {}
public record Rectangle(double width, double height) implements Shape {}
public record Triangle(double base, double height) implements Shape {}

// Pattern matching with switch
public static double area(Shape shape) {
    return switch (shape) {
        case Circle c -> Math.PI * c.radius() * c.radius();
        case Rectangle r -> r.width() * r.height();
        case Triangle t -> 0.5 * t.base() * t.height();
    };
}

// Record patterns — destructure inline
public static String describe(Object obj) {
    return switch (obj) {
        case Circle(double r) -> "Circle with radius " + r;
        case Rectangle(double w, double h) when w == h -> "Square " + w + "x" + w;
        case Rectangle(double w, double h) -> "Rectangle " + w + "x" + h;
        case Integer i when i > 0 -> "Positive int " + i;
        case Integer i -> "Non-positive int " + i;
        case String s when s.isEmpty() -> "Empty string";
        case String s -> "String: " + s;
        case null -> "null";
        default -> "Unknown";
    };
}

// Nested record patterns
record Point(int x, int y) {}
record Line(Point start, Point end) {}

public static double length(Object obj) {
    if (obj instanceof Line(Point(int x1, int y1), Point(int x2, int y2))) {
        return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
    }
    return 0;
}

Common Pitfalls

  • Adding mutable state to records — they're immutable by design
  • Trying to extend a record — they're final, can't be subclassed
  • Using records for entities with relationships — JPA prefers regular classes
  • Forgetting that sealed types must declare all permitted subtypes

When NOT to Use This Skill

  • For entities that need lifecycle methods (JPA) — use regular classes
  • When you need mutable state

How to Verify It Worked

  • Test equals/hashCode work correctly
  • Test compact constructor validation

Production Considerations

  • Use records as DTOs at API boundaries
  • Use sealed interfaces to make impossible states unrepresentable
  • Pattern matching makes refactors safer than instanceof chains

Quick Info

CategoryJava
Difficultyintermediate
Version1.0.0
AuthorClaude Skills Hub
javarecordspattern-matching

Install command:

Want a Java skill personalized to YOUR project?

This is a generic skill that works for everyone. Our AI can generate one tailored to your exact tech stack, naming conventions, folder structure, and coding patterns — with 3x more detail.