Back to Sealed Classes & Interfaces
Q4Question 4 of 12

What happens if a permitted subclass has no `final`/`sealed`/`non-sealed` modifier?

Key Concepts
  • A permitted subclass must declare `final`, `sealed`, or `non-sealed`; omitting all three is a compile error.
  • The rule exists to prevent sealing from leaking: without an explicit extensibility declaration, unknown classes could slip in.
  • `final` ends the branch; `sealed` continues the closure; `non-sealed` deliberately reopens the hierarchy.
  • Records and enums are implicitly `final`, so they satisfy the rule automatically without an extra modifier.
  • A permitted subtype that is an interface must be `sealed` or `non-sealed` (interfaces cannot be `final`).
  • The requirement forces an explicit, reviewed architectural decision about extensibility at every subtype.

Definition

A permitted subclass must declare `final`, `sealed`, or `non-sealed`; omitting all three is a compile error because sealing requires an explicit extensibility decision at every subtype.
  • Mandatory modifier – every direct permitted subclass must carry exactly one of final, sealed, or non-sealed.
  • Compile error – omitting all three produces "sealed, non-sealed or final modifiers expected."
  • final – ends the hierarchy at that subtype; no further extension.
  • sealed – continues the closure with its own permits list.
  • non-sealed – deliberately reopens that branch for unrestricted extension.
  • Records and enums – implicitly final, so they satisfy the rule without an extra modifier.
  • Interfaces – a permitted sub-interface must be sealed or non-sealed because interfaces cannot be final.

Imagine National Bank of India approves a product to be a kind of Account. Head office insists that each approved product must stamp its own policy on further variations: either "no further variants allowed" (final), "only my own named variants allowed" (sealed), or "anyone may add variants freely" (non-sealed). A product that forgets to stamp any policy is sent back: the form is incomplete, and head office refuses to file it.

Understanding the Concepts

What error do you get without the modifier?

The compiler emits "sealed, non-sealed or final modifiers expected" when a permitted subclass omits all three modifiers.

JAVA
package com.bank.model;

import java.math.BigDecimal;

public sealed interface Account permits SavingsAccount {
    BigDecimal balance();
}

// COMPILE ERROR: sealed, non-sealed or final modifiers expected
// class SavingsAccount implements Account {
//     public BigDecimal balance() { return new BigDecimal("500000"); }
// }

// CORRECT: add final
final class SavingsAccount implements Account {
    @Override
    public BigDecimal balance() {
        return new BigDecimal("500000");
    }
}

Why does Java require this modifier?

Sealing must not leak. If a permitted subtype could be extended freely without saying so, the hierarchy would no longer be closed because unknown classes could slip in beneath it.

JAVA
package com.bank.model;

import java.math.BigDecimal;

// Without the modifier rule, this would leak the sealed hierarchy:
// sealed interface Account permits SavingsAccount { ... }
// class SavingsAccount implements Account { ... }  // no modifier
// class RogueSavings extends SavingsAccount { }     // anyone could sneak in!

// With the rule: SavingsAccount must declare final, sealed, or non-sealed
sealed interface Account permits SavingsAccount, LoanAccount {
    BigDecimal balance();
}

// final: no one can extend this
final class SavingsAccount implements Account {
    @Override
    public BigDecimal balance() { return new BigDecimal("500000"); }
}

// sealed: continues the restriction
sealed class LoanAccount implements Account permits HomeLoan {
    @Override
    public BigDecimal balance() { return new BigDecimal("1000000"); }
}
final class HomeLoan extends LoanAccount {}

What does each modifier mean?

| Modifier | Meaning | Extension Rule |
|---|---|---|
| final | Branch ends here | No class may extend this |
| sealed | Branch continues closed | Only its own permitted subtypes |
| non-sealed | Branch reopens | Any class may extend freely |

JAVA
package com.bank.model;

import java.math.BigDecimal;

public sealed interface Account
        permits SavingsAccount, LoanAccount, PartnerProduct {
    BigDecimal balance();
}

final class SavingsAccount implements Account {
    @Override
    public BigDecimal balance() { return new BigDecimal("500000"); }
}

sealed class LoanAccount implements Account permits HomeLoan, CarLoan {
    @Override
    public BigDecimal balance() { return new BigDecimal("1000000"); }
}
final class HomeLoan extends LoanAccount {}
final class CarLoan extends LoanAccount {}

non-sealed class PartnerProduct implements Account {
    @Override
    public BigDecimal balance() { return BigDecimal.ZERO; }
}
// Anyone can extend PartnerProduct
class WalletProduct extends PartnerProduct {}

How do records and enums satisfy the rule?

Records are implicitly final, so a record implementing a sealed interface satisfies the rule automatically. Enums are effectively final too.

JAVA
package com.bank.model;

import java.math.BigDecimal;

public sealed interface PaymentResult {
    // Records are implicitly final: no extra modifier needed
    record Success(BigDecimal amount) implements PaymentResult {}
    record Failure(String reason) implements PaymentResult {}
    record Pending(String expectedDate) implements PaymentResult {}
}
JAVA
package com.bank.service;

import com.bank.model.PaymentResult;
import com.bank.model.PaymentResult.*;
import java.math.BigDecimal;

public class PaymentDemo {
    public static void main(String[] args) {
        PaymentResult result = new Success(new BigDecimal("75000"));
        String msg = switch (result) {
            case Success s -> "Paid " + s.amount() + " INR";
            case Failure f -> "Failed: " + f.reason();
            case Pending p -> "Pending until " + p.expectedDate();
        };
        System.out.println(msg);
    }
}
TEXT
Paid 75000 INR

What about interfaces as permitted subtypes?

A permitted subtype that is itself an interface must be sealed or non-sealed. Interfaces cannot be final in Java.

JAVA
package com.bank.model;

import java.math.BigDecimal;

public sealed interface Financial
        permits Account, Investment {
    BigDecimal value();
}

// Sub-interface: must be sealed or non-sealed (not final)
sealed interface Account extends Financial
        permits SavingsAcc {
    @Override BigDecimal value();
}
final class SavingsAcc implements Account {
    @Override public BigDecimal value() { return new BigDecimal("500000"); }
}

non-sealed interface Investment extends Financial {
    @Override BigDecimal value();
}

How does this affect exhaustiveness checking?

Because every subtype declares its extensibility, the compiler can compute the complete leaf set for exhaustiveness checking. A non-sealed branch is the one place where the set becomes open, and the compiler requires a default or wildcard when switching if the open branch is reachable.

JAVA
package com.bank.service;

import com.bank.model.*;
import java.math.BigDecimal;

public class ExhaustiveDemo {
    public String classify(Account account) {
        return switch (account) {
            case SavingsAccount s -> "Savings";
            case LoanAccount l -> "Loan";
            case PartnerProduct p -> "Partner"; // covers all partner sub-types
        };
    }
}

What are common mistakes?

Forgetting the modifier is the most frequent first-time error. The fix is to decide and declare it.

JAVA
// WRONG: no modifier
// class SavingsAccount implements Account { ... }
// Error: sealed, non-sealed or final modifiers expected

// FIX: decide what kind of subtype this is
final class SavingsAccount implements Account {
    @Override
    public BigDecimal balance() { return new BigDecimal("500000"); }
}

What should a senior engineer know?

This requirement makes the closed-set guarantee hold transitively. The modifier forces an architectural decision at each product type about whether future variants are allowed, which is exactly the kind of deliberate design a regulated banking system benefits from. In code reviews at National Bank of India, Pankaj ensures every permitted subtype has an explicit extensibility stamp.

Visual Overview

DIAGRAM
┌─────────────────────────────────────────────┐
│  Permitted Subtype Modifier Requirement     │
├─────────────────────────────────────────────┤
│                                             │
│  sealed interface Account permits A, B, C   │
│       │                                     │
│       ├── A: final                          │
│       │   └── Branch ends. No extension.    │
│       │                                     │
│       ├── B: sealed permits D, E            │
│       │   ├── D: final                      │
│       │   └── E: final                      │
│       │   └── Branch continues closed.      │
│       │                                     │
│       └── C: non-sealed                     │
│           ├── F extends C (anyone)          │
│           └── G extends C (anyone)          │
│           └── Branch reopened.              │
│                                             │
│  Missing modifier?                          │
│  ┌──────────────────────────────┐           │
│  │ COMPILE ERROR:               │           │
│  │ "sealed, non-sealed or       │           │
│  │  final modifiers expected"   │           │
│  └──────────────────────────────┘           │
│                                             │
│  Records / Enums?                           │
│  └── Implicitly final → rule satisfied ✓   │
└─────────────────────────────────────────────┘

Key Takeaway

If a permitted subclass has no final, sealed, or non-sealed modifier, it is a compile error. The language requires every class or interface that directly extends a sealed type to declare its own extensibility, and leaving all three off produces "sealed, non-sealed or final modifiers expected."

The reason is that sealing must not leak. A sealed type closes its hierarchy at the first level by naming permitted subtypes, but if one of those subtypes could be extended freely without saying so, unknown classes could slip in and the hierarchy would no longer be closed. So each permitted subtype states its policy: final ends the branch, sealed continues the closure, and non-sealed deliberately reopens it.

Records and enums are implicitly final, so they satisfy the rule automatically. In a banking project at National Bank of India, Pankaj uses records for payment results: Success, Failure, and Pending all implement a sealed PaymentResult interface with no extra modifier needed. For ordinary classes and interfaces, the modifier must be written explicitly.

The value of this rule in production is that it turns extensibility into an explicit, reviewed architectural decision at each product type rather than an accident. The modifier forces the team to think about whether a subtype should be a closed leaf, a further-restricted branch, or an open extension point, which is exactly the deliberate design a regulated system needs.

Remember one sentence: every permitted subclass must be final, sealed, or non-sealed; omitting all three is a compile error that forces an explicit extensibility decision.

---

Share
Use ← → arrow keys to navigate