# Modern Java Features: Sealed Classes

Sealing a class or interface restricts the list of subclasses or implementations to a predefined set. For example, consider defining a Shape and restricting subclasses to be only triangles and squares.

```java
public class SealedClasses {
    sealed class Shape permits Triangle, Square {
    }
}
```

```java
public final class Triangle extends Shape {
}
```

```java
public non-sealed class Square extends Shape {
}
```

**Did you notice the use of** `final` **and** `non-sealed` **keywords here?**  
Subclasses of a sealed class **must specify** whether they are `final`, `non-sealed`, or `sealed`. This is because the compiler needs full visibility of the class hierarchy to enforce the restriction.

Sealed types were introduced in **Java 15** as a preview feature, became standard in **Java 17**, and were further enhanced in **Java 21** with pattern-matching support in `switch` expressions — making them even more ergonomic to use.

## 💳 Modeling Business Constraints with Sealed Classes

**Why** would you want to seal a class or interface — and **does it violate the Open-Closed Principle**?

**Short answer: Not necessarily — but they *do* limit extension intentionally.**

The **Open-Closed Principle** says:

> *"Software entities should be open for extension, but closed for modification."*

With sealed classes, you're *closing* the inheritance hierarchy by design. That might look like it contradicts OCP, but in fact:

* You’re **explicitly modeling a closed set of variants**.
    
* You’re still open to extension — just within a **bounded and intentional scope**.
    

For example, think of a system that has 3 well-defined payment methods. You want to model the business domain closely and prevent maintainers from adding a new payment method inadvertently. A good way to do this is by sealing the PaymentMethod class and having the compiler error when an invalid subclass is created:

```java
// File: PaymentMethod.java
public sealed interface PaymentMethod
    permits CreditCard, PayPal, BankTransfer {
    
    String processPayment(double amount);
}
```

```java
// File: CreditCard.java
public final class CreditCard implements PaymentMethod {
    @Override
    public String processPayment(double amount) {
        return "Processed $" + amount + " with Credit Card.";
    }
}
```

```java
// File: PayPal.java
public final class PayPal implements PaymentMethod {
    @Override
    public String processPayment(double amount) {
        return "Processed $" + amount + " with PayPal.";
    }
}
```

```java
// File: BankTransfer.java
public final class BankTransfer implements PaymentMethod {
    @Override
    public String processPayment(double amount) {
        return "Processed $" + amount + " with Bank Transfer.";
    }
}
```

```java
//Doesn't compile: The type CryptoPayment should be a permitted subtype of PaymentMethod
final public class CryptoPayment implements PaymentMethod {

  @Override
  public String processPayment(double amount) {
    // TODO Auto-generated method stub
    return null;
  }
}
```

## 💡 Leverage Pattern Matching with Sealed Classes

When you know all the possible subclasses ahead of time (thanks to `sealed`), the Java compiler can **enforce exhaustive handling** in `switch` expressions — a big win for correctness and readability.

```java
public class BankInterestExample {
    public static void main(String[] args) {
        BankAccount savings = new SavingsAccount(10000);
        BankAccount fixed = new FixedDepositAccount(15000);
        BankAccount checking = new CheckingAccount(5000);

        System.out.println(calculateInterest(savings));   // 300.0
        System.out.println(calculateInterest(fixed));     // 750.0
        System.out.println(calculateInterest(checking));  // 0.0
    }

    sealed interface BankAccount permits SavingsAccount, FixedDepositAccount, CheckingAccount {
        double balance();
    }

    //note: defined as record to provide a simple and short example
    record SavingsAccount(double balance) implements BankAccount {}
    record FixedDepositAccount(double balance) implements BankAccount {}
    record CheckingAccount(double balance) implements BankAccount {}

    static double calculateInterest(BankAccount account) {
        return switch (account) {
            case SavingsAccount sa       -> sa.balance() * 0.03;
            case FixedDepositAccount fda -> fda.balance() * 0.05;
            case CheckingAccount ca      -> 0.0;
        };
    }
}
```

## 📌 Compiler rules to keep in mind:

* You can define permitted subclasses in the same file as the sealed class. If you do so, then you can omit the `permits` clause.
    
* The entire hierarchy must be accessible by the sealed class at compile time. Meaning, subclasses and subclasses of subclasses if they are sealed as well.
    
* Subclasses must directly extend the sealed class.
    
* Subclasses must have exactly one of the following modifiers to describe how it continues the sealing initiated by its superclass: final, non-sealed, sealed.
    
* You can name a record class in the `permits` clause but given record classes are final you don’t need to declare it as final explicitly.
    
* Subclasses must be in the same module as the sealed class (if the sealed class is in a named module) or in the same package (if the sealed class is in the unnamed module).
    

## 🧪 APIs Related to Sealed Classes and Interfaces

Java 17+ introduces two helpful APIs in `java.lang.Class`:

* `boolean isSealed()` — returns `true` if the class or interface is sealed.
    
* `ClassDesc[] permittedSubclasses()` — returns the permitted subclass descriptors (or empty if not sealed).
    

## Summary

Sealed classes give you **control over inheritance**, helping you model your domain safely and predictably.  
Use them when:

* You want to **model finite sets** of domain types (e.g., payment types).
    
* You want to leverage **exhaustiveness in pattern matching**.
    
* You want to **prevent unauthorized subclassing**.
    

**💬 What Do You Think?**

Have you started using sealed classes? Do you think they are a great idea? Let me know in the comments.

**📚 Want to Learn More?**

I found the following resources helpful:

* [JAVA 21 Doc on Sealed Classes and Interfaces](https://docs.oracle.com/en/java/javase/21/language/sealed-classes-and-interfaces.html)
    
* [Blog Posts on the Discussion of Open-Closed-Principle and Sealed Classes](https://medium.com/@kedarpatil141/do-java-sealed-classes-break-the-open-closed-principle-ocp-3a6fb9286434)
