Java Modules vs Packages: Key Differences, Examples, and Access Control

0

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.

Post a Comment

0Comments
Post a Comment (0)