Java 25 Records and Sealed Classes Explained with Real-World Examples and Best Practices

2 min read

Java 25 Records and Sealed Classes Explained with Real-World Examples and Best Practices

Java 25 Records and Sealed Classes Explained with Real-World Examples and Best Practices

1. Introduction

Java 25 continues to refine the language with modern features that make code safer, more expressive, and easier to maintain. Two of the most impactful features are Records and Sealed Classes. These features help developers create immutable data models and enforce strict type hierarchies, reducing bugs and improving readability. This article provides a clear, technical walkthrough of how Records and Sealed Classes work together in Java 25, and how they solve real-world problems in enterprise applications.

2. Problem

In traditional Java applications, developers often face these recurring issues:

  • Excessive boilerplate code for data classes, including getters, constructors, equals(), hashCode(), and toString().
  • Mutable objects that can be accidentally changed, leading to hidden side effects.
  • Weakly controlled inheritance, where any class can extend a base class, making the codebase hard to reason about.
  • Large “if-else” or “switch” statements using enums and instanceof checks that are both fragile and verbose.

In real-world systems such as payment processing or order fulfillment, these problems can cause serious bugs, such as incorrect transaction handling, unauthorized subclassing, or inconsistent business rules.

3. Solution

Java 25 Records and Sealed Classes solve these problems in a clean, type-safe way:

  • Records provide a concise syntax for immutable data carriers.
  • Automatically generate constructor, getters, equals(), hashCode(), and toString().
  • Encourage functional-style programming and thread-safe design.
  • Sealed Classes restrict which classes can extend or implement a class or interface.
  • Make inheritance explicit and controlled.
  • Work seamlessly with pattern matching to simplify complex conditional logic.

When combined, these features allow developers to model domain logic in a way that is safer, clearer, and easier to maintain.

4. Implementation

Real-Life Scenario: E-Commerce Order Processing System

Assume you are building an order processing system for an online store. An order payment can be made in different ways: credit card, PayPal, or bank transfer. Each payment type has different fields and validation logic.

Step 1: Define a sealed interface for payment types

public sealed interface PaymentMethod
        permits CreditCardPayment, PayPalPayment, BankTransferPayment {
}

Step 2: Use Records to define immutable payment data types

public record CreditCardPayment(
        String cardNumber,
        String cardHolderName,
        String expiryMonth,
        String expiryYear
) implements PaymentMethod {
}

public record PayPalPayment(
        String email
) implements PaymentMethod {
}

public record BankTransferPayment(
        String accountNumber,
        String bankCode
) implements PaymentMethod {
}

Step 3: Create an Order record that uses the sealed type

import java.math.BigDecimal;

public record Order(
        String orderId,
        BigDecimal amount,
        PaymentMethod paymentMethod
) {
}

Step 4: Business logic using pattern matching with sealed classes

public class PaymentProcessor {

    public void process(Order order) {
        PaymentMethod method = order.paymentMethod();

        String result = switch (method) {
            case CreditCardPayment cc -> processCreditCard(cc);
            case PayPalPayment pp -> processPayPal(pp);
            case BankTransferPayment bt -> processBankTransfer(bt);
        };

        System.out.println(result);
    }

    private String processCreditCard(CreditCardPayment cc) {
        return "Processed credit card payment for card: " 
                + maskCardNumber(cc.cardNumber());
    }

    private String processPayPal(PayPalPayment pp) {
        return "Processed PayPal payment for email: " + pp.email();
    }

    private String processBankTransfer(BankTransferPayment bt) {
        return "Processed bank transfer for account: " + bt.accountNumber();
    }

    private String maskCardNumber(String cardNumber) {
        return "**** **** **** " + cardNumber.substring(cardNumber.length() - 4);
    }
}

Step 5: Example usage

import java.math.BigDecimal;

public class MainApp {
    public static void main(String[] args) {
        Order order1 = new Order(
                "ORD-1001",
                new BigDecimal("149.99"),
                new CreditCardPayment("4111111111111111", "John Doe", "12", "2028")
        );

        Order order2 = new Order(
                "ORD-1002",
                new BigDecimal("79.99"),
                new PayPalPayment("customer@example.com")
        );

        PaymentProcessor processor = new PaymentProcessor();
        processor.process(order1);
        processor.process(order2);
    }
}

5. Conclusion

Records and Sealed Classes in Java 25 provide a modern, powerful way to design domain models. Records eliminate boilerplate and enforce immutability, while Sealed Classes give developers full control over inheritance structures. Together, they make Java applications safer, easier to maintain, and more expressive. In real-world systems such as e-commerce, these features significantly reduce bugs, improve clarity, and simplify long-term maintenance, making them essential tools for modern Java development.

🤞 Never miss a story from us, get weekly updates to your inbox!

Leave a Reply

Your email address will not be published. Required fields are marked *