You are currently viewing Sealed Classes and Interfaces In Java

Sealed Classes and Interfaces In Java

1. Introduction

Sealed classes are a special Java class type, restricting which other classes can extend them. They were introduced by the JDK Enhancement Proposal(JEP) 409 as part of the JDK 17. In this article, you will learn more about Sealed classes and how to use them in your Code effectively.

2. Prerequisites

Sealed classes are available from Java 17 onwards. In this tutorial, we will use Java 17.
Hence, to complete the tutorial, you will need :

  • The Java SE Development Kit 17 (JDK 17): If you don’t have it already refer to this tutorial for the installation procedure.

3. Motivation and Goals

Let’s assume you are working on an e-commerce application. The application will handle both Credit card and PayPal payments. Your class hierarchy may look like this:

abstract class Payment {
    void process() {
        System.out.println("Processing payment...");
    }
}

class CreditCardPayment extends Payment {
    @Override
    void process() {
        System.out.println("Processing credit card payment...");
    }
}

class PayPalPayment extends Payment {
    @Override
    void process() {
        System.out.println("Processing PayPal payment...");
    }
}

This class hierarchy consists of two payment classes: CreditCardPayment, and PayPalPayment. However, this hierarchy does not prevent a developer from creating a new payment class, CryptoPayment even if this payment type is not supported by the application. This could lead to unexpected bugs in the application.
Now, let’s consider the same example using the sealed concept:

abstract sealed class Payment permits CreditCardPayment, PayPalPayment {
    void process() {
        System.out.println("Processing payment...");
    }
}

final class CreditCardPayment extends Payment {
    @Override
    void process() {
        System.out.println("Processing credit card payment...");
    }
}

final class PayPalPayment extends Payment {
    @Override
    void process() {
        System.out.println("Processing PayPal payment...");
    }
}

We have the same class hierarchy as previously: One abstract class Payment, and two subclasses CreditCardPayment and PayPalPayment. However, this time the hierarchy is enforced with the use of the sealed keyword. No other class can inherit from the Payment class.

4. Sealed Classes

To seal a class, add the sealed modifier to the class declaration. Then, after the extends keyword, add permits and list the allowed child classes.

abstract sealed class Payment permits CreditCardPayment, PayPalPayment {
    void process() {
        System.out.println("Processing payment...");
    }
}

5. Sealed Interfaces

Just like for classes, add the sealed keyword to the declaration; and add permits after implements to seal an interface. However, unlike classes, sealed interfaces allow both classes and interfaces after the permits keyword. Typically, this means that a sealed interface restricts which other interface can extend it, and also which class can implement it.

sealed interface Payment permits CreditCardPayment, PayPalPayment {
    void process();
}

6. Restrictions on Allowed Subclasses

Each allowed child class of a sealed class must follow these rules:

  • The class MUST be accessible from the sealed class at compile time
  • The class MUST directly extend the sealed class
  • The class MUST have one of the following three modifiers: final, sealed, non-sealed.
  • The class MUST be in the same module as the sealed class (if in a named module), or in the same package (if in an unnamed module).

7. Use Cases

You should think about Sealed classes and interfaces whenever there is a need to enforce the class hierarchy in your application. Below are some of the use cases.

7.1. Exhaustive Switch Statements

Whenever you implement a switch statement or expression, you should ensure to cover all the possible cases. Using sealed classes is a great way to enforce that. Note that the code below uses Pattern Matching, a new feature of Java 21.

sealed interface MathOperation permits Add, Subtract, Multiply {}
record Add(int x, int y) implements MathOperation {}
record Subtract(int x, int y) implements MathOperation {}
record Multiply(int x, int y) implements MathOperation {}

public class SealedClassesAndInterfaces {
    public static void main(String[] args){
        MathOperation operation = new Add(1,2);
        executeOperation(operation);
    }

    static void executeOperation(MathOperation operation) {
        switch (operation) {
            case Add add -> System.out.println(add.x() + add.y());
            case Subtract subtract -> System.out.println(subtract.x() - subtract.y());
            case Multiply multiply -> System.out.println(multiply.x() * multiply.y());
        }
    }
}

7.2. Encapsulating Business Logic

If you were to implement a Business Requirement for which the objects are restricted to a specific subset of values, the sealed concept would come in very handy. The payment system we saw earlier is a good example of this use case.

8. Conclusion

In this tutorial, you learned how to use Sealed classes and interfaces in your Java applications. This post was a quick overview of the Sealed classes feature, visit the JEP 409 for more insights.

Noel Kamphoa

Experienced software engineer with expertise in Telecom, Payroll, and Banking. Now Senior Software Engineer at Societe Generale Paris.