Definition
permitslists subtypes – the clause names the direct subtypes allowed to extend the sealed type, and a type cannot extend itself.- Compile error –
sealed class Account permits Account {}fails because the compiler detects a cyclic or invalid permitted subtype. - Concrete sealed class – a non-abstract sealed class can itself be instantiated, but that is being a usable leaf, not permitting itself.
- Recursive data – a node holding references to its own type is legal composition, entirely different from a type in its own
permitsclause. - DAG invariant – Java inheritance forms a directed acyclic graph; any cycle, including a one-node self-cycle, is forbidden.
Imagine National Bank of India's head office writes the closed list of products that may be "a kind of Account." Putting "Account" itself on that list makes no sense, because the list is meant to name the specific products that are types of Account, not the concept of Account repeating itself. A category cannot be a member of its own membership list.
Understanding the Concepts
Can a sealed class list itself in permits?
No. The permits clause declares the set of direct subtypes, and by definition a subtype is a different type that inherits from the sealed type. A class cannot extend itself. Writing sealed class Account permits Account {} produces a compile error.
package com.bank.model;
// COMPILE ERROR: cyclic or invalid permitted subtype
// public sealed class Account permits Account {}
// Correct: list different subtypes
public sealed class Account permits SavingsAccount, CurrentAccount {
String id;
}
final class SavingsAccount extends Account {}
final class CurrentAccount extends Account {}
Is a concrete sealed class the same as self-permitting?
No. A non-abstract sealed class can be instantiated directly, so it is itself a usable leaf value in addition to having permitted subclasses. But it did not permit itself: it permitted other types, and it is usable on its own because it is non-abstract.
package com.bank.model;
import java.math.BigDecimal;
// Concrete sealed class: usable as a value AND has subtypes
public sealed class LedgerNode permits SummaryNode {
private final BigDecimal amount;
public LedgerNode(BigDecimal amount) {
this.amount = amount;
}
public BigDecimal amount() { return amount; }
}
final class SummaryNode extends LedgerNode {
public SummaryNode(BigDecimal amount) {
super(amount);
}
}
package com.bank.service;
import com.bank.model.LedgerNode;
import com.bank.model.SummaryNode;
import java.math.BigDecimal;
public class LedgerDemo {
public static void main(String[] args) {
// LedgerNode is concrete: can instantiate directly
LedgerNode node = new LedgerNode(new BigDecimal("50000"));
SummaryNode summary = new SummaryNode(new BigDecimal("150000"));
System.out.println("Node amount: " + node.amount());
System.out.println("Summary amount: " + summary.amount());
}
}
Node amount: 50000
Summary amount: 150000
How is a recursive data structure different?
A recursive data structure where a node contains references to other nodes of the same sealed type is completely legal. Recursion in the data (a node holding child nodes) is composition, not an inheritance cycle.
package com.bank.model;
import java.math.BigDecimal;
import java.util.List;
public sealed interface TransactionNode
permits LeafTransaction, GroupTransaction {
BigDecimal total();
}
record LeafTransaction(String id, BigDecimal amount)
implements TransactionNode {
@Override
public BigDecimal total() { return amount; }
}
// Recursive: holds a list of TransactionNode children
record GroupTransaction(String label, List<TransactionNode> children)
implements TransactionNode {
@Override
public BigDecimal total() {
return children.stream()
.map(TransactionNode::total)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
package com.bank.service;
import com.bank.model.*;
import java.math.BigDecimal;
import java.util.List;
public class RecursiveDemo {
public static void main(String[] args) {
TransactionNode tree = new GroupTransaction("Batch", List.of(
new LeafTransaction("T1", new BigDecimal("25000")),
new LeafTransaction("T2", new BigDecimal("75000")),
new GroupTransaction("Sub-batch", List.of(
new LeafTransaction("T3", new BigDecimal("50000"))
))
));
System.out.println("Batch total: " + tree.total() + " INR");
}
}
Batch total: 150000 INR
Why does Java forbid inheritance cycles?
Java's inheritance forms a directed acyclic graph rooted at Object. Any cycle, including a one-node self-cycle, is forbidden throughout Java, not just for sealed types. The permits clause inherits this fundamental constraint.
// None of these compile:
// class A extends A { } // self-cycle
// class A extends B { } class B extends A { } // two-node cycle
// sealed class A permits A { } // self-permit = self-cycle
How is the rule recorded in the class file?
The permitted set goes into the PermittedSubclasses class-file attribute, listing the direct subclasses. The sealed class's own entry is never in that list because the attribute describes who may extend it, not the class itself. The JVM validates at load time.
package com.bank.service;
import java.util.Arrays;
public class PermittedCheck {
sealed interface Account permits Savings, Current {}
final record Savings() implements Account {}
final record Current() implements Account {}
public static void main(String[] args) {
Class<?>[] permitted = Account.class.getPermittedSubclasses();
boolean containsSelf = Arrays.asList(permitted).contains(Account.class);
System.out.println("Account in its own permits: " + containsSelf);
}
}
Account in its own permits: false
What are the design alternatives?
If you want the sealed type to be usable as a value and also closed, make it a non-abstract sealed class with permitted subtypes. If you want recursive data, use a field of the sealed type. Neither involves self-permitting.
package com.bank.model;
import java.math.BigDecimal;
// Design alternative 1: concrete sealed base
public sealed class FeeRule permits DiscountedFeeRule {
protected BigDecimal rate;
public FeeRule(BigDecimal rate) {
this.rate = rate;
}
public BigDecimal calculate(BigDecimal principal) {
return principal.multiply(rate);
}
}
final class DiscountedFeeRule extends FeeRule {
private final BigDecimal discount;
DiscountedFeeRule(BigDecimal rate, BigDecimal discount) {
super(rate);
this.discount = discount;
}
@Override
public BigDecimal calculate(BigDecimal principal) {
return super.calculate(principal).subtract(discount);
}
}
What are common mistakes with sealed class self-permitting?
The most common mistake is assuming that a concrete sealed class with no permits clause is extensible. It is not: a sealed class with no permits implicitly permits only classes in the same compilation unit, and if none exist, it is effectively final. Another error is confusing a sealed class permitting itself (which Java forbids) with a sealed class that permits its own nested classes, which is the standard and correct approach.
package com.bank.model;
public class SealedSelfPermitMistakes {
// WRONG assumption: concrete sealed class is extensible
// sealed class Account { } // No permits, no subclasses in file -> effectively final
// CORRECT: sealed class permits nested subclasses
sealed static class Account permits Account.Savings, Account.Current {
static final class Savings extends Account { }
static final class Current extends Account { }
}
public static void main(String[] args) {
System.out.println("Sealed with no permits + no subclasses = effectively final");
System.out.println("Sealed permits Nested.Savings, Nested.Current = standard pattern");
System.out.println("Sealed permits itself = compile error (inheritance cycle)");
}
}
Expected output:
Sealed with no permits + no subclasses = effectively final
Sealed permits Nested.Savings, Nested.Current = standard pattern
Sealed permits itself = compile error (inheritance cycle)
What should a senior engineer know?
Recognizing the distinction between "a concrete sealed base" and "a recursive field" prevents incorrect designs. If you find yourself wanting a type to permit itself, step back and ask: do I want the base to be a usable value (make it concrete), or do I want a tree structure (add a field of the sealed type)? Both are clean solutions that do not require self-permitting.
Visual Overview
┌─────────────────────────────────────────────┐
│ Self-Permitting: Why It Fails │
├─────────────────────────────────────────────┤
│ │
│ sealed class Account permits Account {} │
│ │ │
│ └── Account extends Account? │
│ └── CYCLE DETECTED │
│ └── COMPILE ERROR │
│ │
├─────────────────────────────────────────────┤
│ Concrete Sealed Base (legal alternative) │
│ │
│ sealed class Node permits SummaryNode │
│ │ │
│ ├── new Node(...) → OK │
│ └── new SummaryNode(...) → OK │
│ │
├─────────────────────────────────────────────┤
│ Recursive Data (legal alternative) │
│ │
│ sealed interface TxnNode │
│ permits Leaf, Group │
│ │ │
│ ├── Leaf(amount) │
│ └── Group(List<TxnNode>) ← recursive │
│ field OK │
└─────────────────────────────────────────────┘
Key Takeaway
A sealed class cannot permit itself. The permits clause names the direct subtypes allowed to extend the sealed type, and a type cannot extend itself. Writing sealed class Account permits Account is a compile error because Java inheritance forms a directed acyclic graph and any cycle is forbidden.
People sometimes confuse self-permitting with two different but legal concepts. The first is a concrete sealed base: a non-abstract sealed class can be instantiated directly, so it is itself a usable leaf value in addition to having permitted subclasses. In a banking project, a LedgerNode is concrete and permits a SummaryNode subtype. The base is usable on its own, but it never permitted itself.
The second confusion is a recursive data structure. A sealed TransactionNode that holds a list of TransactionNode children is perfectly legal. This is recursion in the data (composition), not an inheritance cycle. In a batch processing system, a group transaction sums its children recursively, and each child is either a leaf or another group.
The underlying principle is that Java inheritance must be acyclic. Any cycle, including a one-node self-cycle, is forbidden. The PermittedSubclasses class-file attribute never contains the sealed class itself because it describes who may extend the class, not the class itself. If you find yourself wanting self-permitting, you actually want either a concrete sealed base or a recursive field.
Remember one sentence: a sealed class cannot permit itself because permits lists subtypes and nothing can be its own subtype; use a concrete base or a recursive field instead.
---