Packages and modules both help organize Java applications, but they solve different problems. A package groups related classes and interfaces, while a module groups packages and adds a stronger boundary for dependencies and encapsulation.
This distinction becomes especially important when working with large Java applications. A package helps you organize code inside an application; a module helps you define how larger parts of the application interact with one another.
Why Compare Modules and Packages?
At first glance, modules can seem like a larger version of packages. There is some truth to that because a module can contain multiple packages, but the module system provides capabilities that packages alone do not provide.
For example, a package can use access modifiers such as public, private, protected, and default access. A module adds another architectural boundary that controls which packages are exposed and which other modules are required.
Basic Difference
| Feature | Package | Module |
|---|---|---|
| Groups | Classes, interfaces, and related types | Packages |
| Main purpose | Organization and namespace management | Dependency management and strong encapsulation |
| Introduced | Core Java feature | Java 9 |
| Descriptor | No module descriptor required | Uses module-info.java |
| Dependencies | No package-level dependency declaration | Declared with requires |
| Visibility | Controlled through access modifiers | Also controlled through exported packages |
| Scope | Smaller organizational unit | Larger architectural unit |
What Is a Package?
A package is a namespace used to organize related Java types. It helps prevent naming conflicts and makes source code easier to navigate.
package com.example.payment;
public class PaymentService {
}
Here, PaymentService belongs to the com.example.payment package.
A package might contain several related classes:
com.example.payment ├── PaymentService ├── Payment ├── PaymentValidator └── PaymentRepository
The package groups these types logically, but it does not explicitly declare dependencies on other packages.
What Is a Module?
A module is a named collection of packages with an explicit declaration of dependencies and package boundaries.
module com.example.payment {
exports com.example.payment.api;
}
The module can contain several packages, while the descriptor decides which packages are exposed to other modules.
com.example.payment ├── com.example.payment.api ├── com.example.payment.service ├── com.example.payment.repository └── com.example.payment.internal
The module can export only the package intended to form its public API.
Hierarchy Relationship
A useful way to remember the relationship is:
Class ↓ Package ↓ Module
A class belongs to a package, and a module can contain multiple packages.
Remember: A package organizes related types. A module organizes packages and defines how those packages interact with other modules.
Package Access
Packages participate in Java's traditional access-control system. A class or member can use access modifiers to control visibility.
| Modifier | Basic Visibility |
|---|---|
| private | Within the declaring class |
| default | Within the same package |
| protected | Within the package and through permitted inheritance access |
| public | Broad Java-level accessibility, subject to module boundaries |
Packages therefore provide an important level of encapsulation, but package access alone does not define a complete application-level dependency boundary.
Module Access
Modules add another level of control. A module can decide which packages are exported.
module com.example.library {
exports com.example.library.api;
}
Suppose the module contains:
com.example.library.api com.example.library.internal
Only the API package is exported. The internal package remains hidden from normal access by other modules.
Important: Module encapsulation is an additional boundary. A public class inside a package that is not exported is not automatically available for normal access from another module.
Dependency Management
A major difference between packages and modules is explicit dependency declaration.
Packages do not have a directive equivalent to requires.
Modules can explicitly declare dependencies:
module com.example.app {
requires com.example.library;
}
This creates a formal relationship between the two modules.
com.example.app
|
| requires
v
com.example.library
This explicit dependency graph becomes particularly valuable in large applications where understanding component relationships is essential.
Public Does Not Mean the Same Thing
One of the most important differences to understand is that the public modifier and module exports operate at different levels.
Consider:
package com.example.internal;
public class InternalService {
}
The class is public at the Java language level. However, if its package belongs to a named module and the package is not exported, another module cannot normally access it as part of ordinary module-based access.
This gives module authors stronger control over their public API.
Real-World Analogy
Think of a company building.
A package is like a department. It groups employees who work on related responsibilities. A module is more like a secured division containing several departments.
Employees inside a department may collaborate according to internal rules. The division, however, can decide which departments have public reception areas and which remain restricted to internal staff.
In this analogy, packages organize code while modules establish larger architectural boundaries.
Modules and Packages Work Together
Modules do not replace packages. They build on top of them.
module com.example.shop {
exports com.example.shop.api;
}
The module still contains packages, and those packages still contain Java classes. The module system adds another layer of organization and access control around them.
When Should You Think About Packages?
Packages are useful whenever you need to organize related classes and create logical namespaces.
For example:
com.example.customer com.example.order com.example.payment
Even a relatively small Java application can benefit from meaningful package organization.
When Should You Think About Modules?
Modules become especially valuable when an application or library has multiple significant components and needs stronger control over dependencies and public boundaries.
A modular application might look like:
com.example.app com.example.authentication com.example.payment com.example.reporting
Each module can have its own dependencies and exported API.
Modules vs Packages in Practice
Imagine an e-commerce application containing customer, order, inventory, and payment functionality.
A package-oriented design could look like:
com.example.customer com.example.order com.example.inventory com.example.payment
A modular design could group these packages into larger application boundaries:
module com.example.customer {
}
module com.example.order {
}
module com.example.payment {
}
Each module can then explicitly declare what it needs and what it exposes.
Common Beginner Mistakes
- Thinking packages and modules are exactly the same concept.
- Assuming modules replace packages.
- Assuming every public class in a module is automatically accessible from another module.
- Forgetting that modules contain packages rather than individual classes directly.
- Confusing package-private access with module-level encapsulation.
- Using modules without understanding the dependency relationships they introduce.
Best Practices
- Use packages to organize related classes and maintain clear namespaces.
- Use modules when stronger dependency management and encapsulation are valuable.
- Keep module APIs intentionally small by exporting only required packages.
- Avoid creating unnecessary modules for very small applications.
- Design package and module boundaries around clear responsibilities.
Interview Insight
A common interview question is, "Can a module contain multiple packages?" Yes. In fact, grouping multiple related packages is one of the fundamental purposes of a module.
Another common question is, "Does Java module replace the package concept?" No. Packages continue to organize Java types. Modules provide a higher-level boundary around packages.
Interview Tip: A concise comparison is: Package = organization and namespace; Module = packages plus explicit dependencies and stronger encapsulation.
Final Comparison
| Aspect | Package | Module |
|---|---|---|
| Groups | Classes and interfaces | Packages |
| Purpose | Organization and namespace | Architecture, dependencies, and encapsulation |
| Descriptor | None required | module-info.java |
| Dependency declaration | Not available | requires |
| Package exposure | Access modifiers | exports plus access modifiers |
| Reflective access | No module-level rule | opens can control reflective access |
| Scope | Type-level organization | Application or library architectural boundary |
Packages and modules are not competing features; they operate at different levels and work together. Packages give Java developers a clean way to organize classes and define local access boundaries, while modules provide a stronger architectural layer for grouping packages, declaring dependencies, and controlling what an application or library exposes. Understanding both concepts gives you a much clearer picture of how modern Java applications are structured.
