A package (declared with package com.example.myapp; at the top of a file) groups related classes and enforces the public/package-private visibility rules, but offers no way to hide a public class from outside code entirely — anything public on the classpath was accessible from anywhere. The Java Platform Module System (JPMS), introduced in Java 9, adds a module-info.java descriptor declaring which packages a module exports; packages not explicitly exported are genuinely inaccessible to other modules, even if their classes are public.
Packages: namespace and basic visibility
A class with no access modifier at all (no public, private, or protected) has package-private visibility — accessible only within its own package, one of Java's four real access levels alongside public, protected, and private.
package com.example.myapp;
public class Calculator { // public: visible anywhere on the classpath
int internalState; // package-private: visible only within com.example.myapp
}module-info.java: declaring exports
Only com.example.myapp.api is exported; com.example.myapp.internal, even with fully public classes inside it, is invisible to any other module — real, enforced encapsulation that plain packages could never provide.
module com.example.myapp {
requires java.sql; // this module depends on java.sql
exports com.example.myapp.api; // this package IS visible to other modules
// com.example.myapp.internal is NOT exported — genuinely inaccessible outside this module
}Why JPMS matters: real encapsulation
Before modules, a library's "internal" packages were a purely social convention — nothing stopped external code from importing and using them, and libraries broke constantly when users depended on classes never meant to be public API. JPMS gives the compiler and runtime a way to enforce that boundary genuinely, which is also what made it possible to modularize the JDK itself starting in Java 9, letting applications ship smaller custom runtime images containing only the modules they actually need.