Experience with sealed classes & the "same package" rule

Dan Smith daniel.smith at oracle.com
Wed Jun 9 21:42:16 UTC 2021

Here's some late feedback on the sealed classes preview. Probably actionable before 17 GA, if that's what people want to do, or potentially a change that can come in a future release. (Or you can just tell me that this inconvenience isn't a big deal, live with it.)

The spec for sealed classes contains this rule:


If a sealed class C belongs to a named module, then every class named in the permits clause of the declaration of C must belong to the same module as C; otherwise a compile-time error occurs.

If a sealed class C belongs to an unnamed module, then every class named in the permits clause of the declaration of C must belong to the same package as C; otherwise a compile-time error occurs.

> Sealed class hierarchies are not intended to be declared across different maintenance domains. Modules cannot depend on each other in a circular fashion, yet a sealed class and its direct subclasses need to refer to each other in a circular fashion (in permits and extends clauses, respectively). Necessarily, therefore, a sealed class and its direct subclasses must co-exist in the same module. In an unnamed module, a sealed class and its direct subclasses must belong to the same package.


A unique property of this rule is that it is guarded on whether code lives in a (named) module or not. If you're in a module, you get language semantics X; if not, you get language semantics Y. In particular, there exist programs that will compile when packaged in a module, but will not when left to the unnamed module. (This is different than the canonical use of modules, which is to restrict the set of packages that are observable, depending on the configuration of the current module.)

In practice, I had this experience:

1) Trying to deploy some Java code with a tool that requires a single jar.
2) Need to include the experimental bytecode API, which is packaged as a module.
3) Copy the needed API sources into my own source tree.
4) Compilation fails: lots of "cannot extend a sealed class in a different package" errors.

Unfortunately, the bytecode API was making use of sealed classes, and those hierarchies crossed package boundaries. (In a typical public API vs. private implementation packaging strategy.)

(The workaround was to declare my own module-info.java and package my program as a single modular jar.)

Is this behavior consistent with the design of modules? I lean towards "no": Java sources don't opt in to modules. They belong to a module, or not, depending on the context—whether there's a module-info.java somewhere, which javac parameters are used. The sources themselves are meant to be portable. But this rule means any sealed class hierarchy that crosses package boundaries is asserting *in the program source* that the classes must belong to a module.

In practice, I think it's probably a fairly routine experience to take some sources, originally written in a module context, and recompile them in a module-free context. I did it because of idiosyncrasies of my deployment scenario. Others might do it because, say, they're releasing the same API for different audiences.

Proposed solution: just drop the whole rule I quoted above. Sealed classes and their children already must be able to "see" each other, so it's impossible to consistently compile the hierarchy in separate maintenance domains. There are ways you could bootstrap your way into it with separate compilation in the unnamed module, but... if you're trying that hard, you probably know what you're doing. It's hard for me to envision *accidentally* creating a sealed hierarchy across maintenance domains.

(Or, if we like explicitly stating the same-module rule, even though it's redundant, that would be okay. Just drop the same-package rule in this scenario, and apply the same standard to named and unnamed modules.)

More information about the amber-spec-experts mailing list