skip to content

Migration Challenges

Split packages are forbidden, internal sun and com.sun APIs are encapsulated, and --add-exports and --add-opens are the escape hatches. Interviewers ask this because it explains most of the Java 8 to 11 upgrade pain.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

What is a split package in JPMS, why is it forbidden, and how do you fix one during migration?

level: middleimportance: must knowfreq 62%

answer

  1. Same package in two modules = forbidden
  2. JPMS needs exactly one owner per package (reliable configuration)
  3. Classpath/unnamed module has NO split check
  4. Fix: merge / exclude / relocate
  5. --patch-module is a transition crutch

basics

~20 s

A split package is when the same package name lives in two different modules. The module system forbids it because each package must belong to exactly one module. You fix it by merging the package into one module or moving the classes so the package exists in only one place.

solid answer

~40 s

A split package is when the same package (e.g. javax.annotation) is supplied by two or more modules on the module path. JPMS requires every package to be owned by exactly one module so the runtime can unambiguously resolve which module exports a given type; a split breaks resolution and the module graph fails to build. Typical causes are overlapping legacy jars (old JSR artifacts) or a package straddling a boundary you are drawing. Fixes: merge the conflicting jars into one module, pick one canonical artifact and exclude the duplicate, relocate classes so the package is whole in a single module, or keep the offenders on the classpath (the unnamed module has no split-package check). The escape hatch --patch-module can splice extra classes into a module, but the clean fix is consolidating ownership.

go deeper

for a junior

Can state that two modules cannot contain the same package and that the build fails if they do.

for a middle

Explains the reliable-configuration rationale, identifies common causes (legacy javax jars), and applies merge/exclude/relocate fixes.

for a senior

Weighs trade-offs between classpath migration, dependency exclusion, and --patch-module; recognizes split packages in transitive dependency graphs and resolves them at the build-tool level.

for a principal

Designs module boundaries to avoid splits across an org's library set, defines policy for legacy javax artifacts, and guides large-scale migrations so package ownership is unambiguous before modules are introduced.

## Background: modules and packages A **package** in Java is a namespace for classes, like `com.acme.util`. The **Java Platform Module System (JPMS)**, added in Java 9 (Project Jigsaw), introduces a layer above packages: a **module** is a named, self-describing unit declared by a `module-info.java` file that states what it `requires` (depends on) and `exports` (makes visible). Modules placed on the **module path** form a **module graph** that the runtime resolves at startup. ## What a split package is A **split package** exists when the *exact same package name* is contributed by **two or more modules** at the same time. Example: both `module-a` and `module-b` contain classes in package `javax.annotation`. ## Why JPMS forbids it JPMS guarantees **reliable configuration**: for any package, there is exactly one module that owns and can export it, so the runtime knows unambiguously where a type comes from. If two modules both claimed `javax.annotation`, the resolver could not decide which one a `requires` should read, and **strong encapsulation** (the rule that you can only see exported packages of modules you require) would be undefined. So the module system rejects the configuration at resolution time with an error such as `module X reads package javax.annotation from both Y and Z` or refuses to define two modules owning the same package. Note the contrast with the **classpath / unnamed module**: the classpath has *no* split-package check (it just uses first-wins class loading), which is one reason classpath apps tolerated overlapping jars for years. ## How splits arise during migration 1. **Legacy JSR jars** — many old API jars (annotations, JAXB, JAX-WS) re-declare `javax.*` packages that also exist elsewhere. 2. **Two versions of the same library** transitively pulled in. 3. **Drawing a module boundary** through a package — putting half of `com.acme.util` in one module and half in another. ## How to fix it - **Consolidate ownership** — merge the conflicting jars/classes so the package lives in exactly one module. - **Exclude the duplicate** — choose one canonical artifact (build-tool dependency exclusion) and drop the other. - **Relocate** — move classes so each package is whole in a single module (sometimes renaming the package). - **Stay on the classpath** — leaving the offending jars on the classpath (not the module path) sidesteps the check entirely during incremental migration. - **`--patch-module <module>=<jar/dir>`** — an escape hatch that injects extra classes into a named module at compile/run time, effectively healing a split for a known module; useful as a transition crutch, not a permanent design. ## Key takeaways Split packages are a *configuration* error, not a runtime crash deep in your code: the JVM refuses to start (or javac refuses to compile) when the module path has them. The durable fix is making each package owned by one module; the classpath and `--patch-module` are tactical workarounds during migration.

  • Why does the classpath tolerate split packages but the module path does not?
    The classpath loads everything into the single unnamed module with first-wins class resolution and performs no split-package validation. The module path enforces reliable configuration, requiring each package to be owned by exactly one module, and fails resolution if two modules contribute the same package.
  • Can two modules export different packages that share a common prefix, like com.acme.a and com.acme.b?
    Yes. The restriction is on identical full package names, not prefixes. com.acme.a and com.acme.b are distinct packages and may live in different modules without conflict.

Two houses claiming the same street address: the postal service (resolver) can't deliver mail because it cannot tell which house owns the address.

saying these in an interview costs you the question

  • Thinking a split package is a runtime ClassNotFoundException deep in code — it actually fails resolution at startup/compile
  • Believing two modules can share a package if versions differ
  • Assuming --add-exports fixes a split (it controls visibility, not ownership)
  • Confusing 'split package' with a circular dependency

context

open as a page

What happened to internal JDK APIs like sun.* and com.sun.* in the module system, and how do you cope when your code depends on them?

level: seniorimportance: must knowfreq 58%

basics

~20 s

In Java 9+, internal JDK packages (like sun.misc.Unsafe) are encapsulated by their modules and no longer exported, so code that used them fails to compile or warns/breaks at runtime. The right fix is to switch to a supported public API; as a temporary workaround you can use --add-exports or --add-opens.

open as a page

Why does JPMS forbid cyclic module dependencies, and what do you do when two modules seem to need each other?

level: middleimportance: should knowfreq 41%

basics

~20 s

JPMS does not allow module A to require module B while B also requires A (directly or through a chain). Cycles are banned so the module graph can be resolved in a clear order. You break a cycle by extracting the shared code into a third module, or by inverting a dependency with an interface.

open as a page

Contrast bottom-up and top-down JPMS migration strategies and explain how automatic modules and the unnamed module enable incremental migration.

level: seniorimportance: should knowfreq 47%

basics

~20 s

Bottom-up means modularizing your dependencies first, then your own code; top-down means modularizing your own application first while leaving libraries as automatic modules. Automatic modules (plain jars on the module path) and the classpath unnamed module let you mix modular and non-modular code so you can migrate gradually.

open as a page

Explain the JPMS escape-hatch flags --add-exports, --add-opens, --add-modules, and --add-reads. When is each needed during migration?

level: principalimportance: should knowfreq 44%

basics

~20 s

These command-line flags relax module rules without editing module-info. --add-exports lets you use a package that isn't exported; --add-opens grants deep reflection into a package; --add-modules forces a module into the graph even if nothing requires it; --add-reads lets one module read another it didn't declare. They are temporary bridges during migration.

open as a page