LearnFrenzy
Sign in Take a free test

Abstraction and Modern Types · Lesson 28 of 42

Records, Enums and Sealed Classes

5 min read Updated Nov 7, 2026 Saurabh Samir

Modern Java added types that remove boilerplate and make intent obvious. Records are one-line immutable data classes, enums model a fixed set of values, and sealed classes let you list exactly which subclasses may exist. Together with pattern matching, they make Java 21 code shorter and safer – and they are now common in Spring Boot projects.

In this lesson

  • Records: data classes in one line
  • Enums with fields, methods and switch
  • Sealed interfaces and classes
  • Putting them together with pattern matching

Records

A record declares its data once; Java generates the constructor, accessors, equals(), hashCode() and toString(). All fields are final.

import java.util.HashSet;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        Product p = new Product("Headphones", 1499.0, 2);
        System.out.println(p);
        System.out.println(p.name() + " total ₹" + p.total());     // accessor: name(), not getName()
        System.out.println(p.equals(new Product("Headphones", 1499.0, 2)));
        Set<Product> set = new HashSet<>(Set.of(p));
        System.out.println(set.contains(new Product("Headphones", 1499.0, 2)));
        try {
            new Product("Pen", -5, 1);
        } catch (IllegalArgumentException e) {
            System.out.println("Rejected: " + e.getMessage());
        }
    }
}

record Product(String name, double price, int qty) {
    Product {                                        // compact constructor: validation
        if (price < 0) throw new IllegalArgumentException("negative price");
    }

    double total() { return price * qty; }           // records can have methods
}

Output

Product[name=Headphones, price=1499.0, qty=2]
Headphones total ₹2998.0
true
true
Rejected: negative price

When to use a record

For data carriers: API request/response objects (DTOs), map keys, results that return several values, events. A record can't extend another class and its fields can't be changed, so it isn't suitable for entities that change over time, such as a JPA @Entity.

Enums

An enum is a type with a fixed set of named constants. It's far safer than using strings or ints for statuses – the compiler rejects typos.

public class Main {
    public static void main(String[] args) {
        OrderStatus s = OrderStatus.SHIPPED;
        System.out.println(s + " " + s.ordinal() + " " + s.label());
        System.out.println(OrderStatus.valueOf("DELIVERED").isFinal());
        for (OrderStatus st : OrderStatus.values()) System.out.print(st.name() + " ");
        System.out.println();
        String msg = switch (s) {
            case PLACED -> "We have your order";
            case SHIPPED -> "On the way";
            case DELIVERED -> "Enjoy!";
            case CANCELLED -> "Refund started";
        };
        System.out.println(msg);
    }
}

enum OrderStatus {
    PLACED("Order placed"), SHIPPED("Shipped"), DELIVERED("Delivered"), CANCELLED("Cancelled");

    private final String label;                     // enums can have fields…

    OrderStatus(String label) { this.label = label; }   // …constructors (always private)

    String label() { return label; }                // …and methods

    boolean isFinal() { return this == DELIVERED || this == CANCELLED; }
}

Output

SHIPPED 1 Shipped
true
PLACED SHIPPED DELIVERED CANCELLED
On the way
  • Enum constants are singletons, so comparing with == is correct and fast.
  • A switch expression over an enum needs no default if every constant is handled – and the compiler will warn you if a new constant is added later and not handled.
  • EnumMap and EnumSet are efficient collections keyed by enums.

Sealed classes and interfaces

A sealed type lists the only classes allowed to extend or implement it. That tells readers – and the compiler – the complete set of possibilities.

public class Main {
    static double area(Shape s) {
        return switch (s) {                       // no default needed: the compiler knows all cases
            case Circle c -> Math.PI * c.r() * c.r();
            case Rect(double w, double h) -> w * h;      // record pattern: unpack fields
            case Square sq -> sq.side() * sq.side();
        };
    }

    public static void main(String[] args) {
        Shape[] shapes = {new Circle(1), new Rect(4, 3), new Square(2)};
        for (Shape s : shapes) System.out.printf("%s → %.2f%n", s, area(s));
    }
}

sealed interface Shape permits Circle, Rect, Square {}
record Circle(double r) implements Shape {}
record Rect(double w, double h) implements Shape {}
record Square(double side) implements Shape {}

Output

Circle[r=1.0] → 3.14
Rect[w=4.0, h=3.0] → 12.00
Square[side=2.0] → 4.00

sealed interface Shape

Shape permits exactly…

  • Circlerecord (implicitly final)
  • Rectrecord
  • Squarerecord

Permitted subclasses must be final, sealed or non-sealed. Records are implicitly final, which makes "sealed interface + records" a neat way to model results, events and API responses.

Practice

Try it yourself

  1. Create an enum Planet-style Coin with values PAISE_50(0.5), RUPEE_1(1), RUPEE_2(2) and sum one of each.

    Show solution
    public class Main {
        public static void main(String[] args) {
            double total = 0;
            for (Coin c : Coin.values()) total += c.value;
            System.out.println(total);
        }
    }
    
    enum Coin {
        PAISE_50(0.5), RUPEE_1(1), RUPEE_2(2);
        final double value;
        Coin(double value) { this.value = value; }
    }

    Output

    3.5
  2. Write a record Range(int from, int to) that rejects from > to and has a length() method.

    Show solution
    public class Main {
        public static void main(String[] args) {
            Range r = new Range(3, 10);
            System.out.println(r + " length " + r.length());
        }
    }
    
    record Range(int from, int to) {
        Range {
            if (from > to) throw new IllegalArgumentException("from > to");
        }
        int length() { return to - from; }
    }

    Output

    Range[from=3, to=10] length 7

Quick check

What does a record generate automatically?

A canonical constructor, accessor methods, equals(), hashCode() and toString().

Can you compare enum values with ==?

Yes – each constant is a single instance.

Why does a switch over a sealed interface need no default?

The compiler knows every permitted subtype, so it can check that all cases are covered.

Summary

  • Records: immutable data classes with generated equals/hashCode/toString; validate in a compact constructor.
  • Enums: fixed sets of values with fields, methods and exhaustive switches.
  • Sealed types list their subclasses; with record patterns, switches become complete and concise.

Interview practice: Java 8 to 21 features.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Search LearnFrenzy

Esc

Quick links