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.