Two classes both declare the package com.acme.util, but they are defined by different class loaders. Can one access the other's package-private members? Explain what the JVM treats as 'the same package' at run time.
answer
- run-time package = (package name, defining loader)
- mismatch → IllegalAccessError at resolution, not compile time
- prevents package-name spoofing into library internals
- java.* definition forbidden; jar sealing is complementary
- modules: no split packages, explicit exports on top
basics
~20 sNo. The JVM's run-time package is the pair (package name, defining class loader), so same-named packages in different loaders are different packages. Package-private access across them fails with IllegalAccessError, even though the source-level package names match.
solid answer
~50 sAccess control for package-private (default-access) members is decided against the **run-time package**, which the JVM defines as *(package name, defining class loader)* — the same name-plus-loader rule that governs type identity. So two classes both declaring `package com.acme.util;` but defined by different loaders are in **different run-time packages**. Package-private fields, methods and classes are not accessible between them. The compiler happily allows the access, because at compile time only the source package name is visible; the failure appears at run time as `IllegalAccessError` when the reference is resolved. This is a deliberate security property. Without it, an attacker could inject a class declaring a JDK-internal package name and gain package-private access to platform internals simply by naming the package correctly. Tying the package to the defining loader makes that impossible; `java.*` package definition is additionally forbidden outright. Java 9's module system tightens the same area further: named modules must not split a package across modules, and a package's exports are declared explicitly.
code
text · 8 linesloaderA defines com.acme.util.Helper // has a package-private method reset()
loaderB defines com.acme.util.Caller // calls Helper.reset()
=> compiles fine (source packages match)
=> at resolution:
java.lang.IllegalAccessError: class com.acme.util.Caller tried to access
method com.acme.util.Helper.reset()V (com.acme.util.Caller and
com.acme.util.Helper are in unnamed module of loader B / loader A)go deeper
Know the rule: same package name plus same class loader, otherwise it is a different package and package-private access is denied.
Explain that the check happens at resolution and surfaces as IllegalAccessError even though the code compiled, and connect it to the same name-plus-loader identity rule used for types.
Give the security rationale (package-name spoofing), and identify where it bites in practice: generated classes, mocking libraries, and jars split across loaders in containers.
Position it within layered access control — module readability and exports, then run-time package, then access flags — and set the design rule that anything relying on package-private access must control the defining loader, not just the naming.
## Two notions of "package" At the **source** level, a package is just the name in the `package` declaration; it determines default-access visibility as the language spec describes. At **run time**, the JVM works with a **run-time package**: a package name *together with the defining class loader of the classes in it*. Two classes are in the same run-time package only if they have the same package name **and** the same defining loader. This is the direct analogue of type identity being *(binary name, defining loader)*. Package membership inherits the same rule because it is derived from the same loading event. ## What that means for access Default (package-private) access, and `protected` access in its package-local aspect, are checked at **resolution time** against run-time packages. If the accessing class and the accessed member's class have the same package name but different defining loaders, resolution fails and the JVM throws `IllegalAccessError`. The compile-time/run-time asymmetry is what makes this surprising: `javac` sees only source package names, so the code compiles. The error surfaces the first time the offending symbolic reference is resolved, which may be well into execution. A related consequence: `Class.getPackage()` / `getPackageName()` return the name, but two `Package` objects obtained from classes with different defining loaders are distinct — again, the name alone is not the identity. ## Why the JVM does it this way Security. Package-private access is an intentional trust boundary: classes that ship together in a package may see each other's internals. If the boundary were keyed on the *name* alone, anyone could write a class declaring an existing package name, get it loaded, and read or call package-private members of that package's classes — a trivial escalation against library and platform internals. Binding the run-time package to the defining loader closes that: your injected class is only in the same run-time package if it was defined by the *same loader* as the classes it wants to reach — that is, if it was already trusted enough to be loaded from the same place. As a second, absolute line of defense the JVM refuses to let user-defined loaders define classes in `java.*` packages at all. Sealing (a jar manifest attribute) provides a complementary, packaging-level guarantee: all classes of a sealed package must come from the same jar. ## Interaction with the module system Since Java 9, named modules add a further layer on top: - A package must not be **split** across two named modules; the module system rejects such configurations, which removes a whole class of ambiguity that existed on the class path. - Access to a package's types from outside its module requires an explicit `exports` (or `opens` for deep reflection), independent of the run-time-package rule. So modern access control is layered: module readability and exports first, then run-time package and the class-level access flags. The run-time package rule did not go away; the module system sits above it. ## Where you actually meet this - **Instrumentation and bytecode generation.** Generated classes are commonly placed in the same package as the class they augment so they can touch package-private members. That only works if they are defined by *the same loader* — which is why generation APIs offer to define classes as a "nestmate" or into the target class's loader/lookup context rather than into an ad-hoc loader. - **Test frameworks** that generate helpers in the package under test have the same requirement. - **Container/plugin setups** where a jar's classes end up split across a shared loader and an application loader: half the package can no longer see the other half's internals, producing `IllegalAccessError` at run time with no compile-time warning. - **Mocking and proxying libraries** hitting package-private methods must arrange loader-compatible definition, not merely matching names. ## The rule to carry "Same package" is never a statement about a string. At run time it means *same package name and same defining class loader* — and inside a module system, additionally *same module with the right exports*. Any tool or design that relies on package-private access must control **which loader defines the classes**, not just what they are named.
- Why does the JVM define the run-time package this way instead of using the package name alone?To stop package-name spoofing. If the name alone granted package-private access, any injected class declaring an existing package name could reach that package's internals. Requiring the same defining loader means only code loaded from the same trusted source shares the boundary, and defining classes in java.* is prohibited outright as a further safeguard.
- How do bytecode-generation libraries legitimately access package-private members of a target class?By ensuring the generated class is defined into the target's own loader and package context — for example via a lookup object obtained from the target class, or by defining it as a nestmate — rather than into an ad-hoc loader. Matching the package name is necessary but not sufficient; the defining loader must match too.
saying these in an interview costs you the question
- Believing matching package names alone grant package-private access at run time
- Expecting the compiler to catch this — the failure is an IllegalAccessError at resolution
- Thinking a generated class in the right-named package can always reach package-private members
- Confusing jar sealing with the run-time package rule
- Assuming the module system replaced the run-time-package check rather than layering on top of it