Java Modules and Packages
45 minPackages provide a way to organize related classes and interfaces into namespaces, preventing naming conflicts and improving code organization. Package names typically follow reverse domain notation (e.g., `com.example.app`). Classes in the same package can access each other's package-private members. Understanding packages helps you organize code logically and avoid naming collisions in large projects.
Java 9 introduced the Java Platform Module System (JPMS) to address limitations of the package system. Modules provide strong encapsulation (only explicitly exported packages are accessible), explicit dependencies (modules declare what they require), and better dependency management. Modules are declared in `module-info.java` files. Understanding modules helps you build large-scale applications with clear boundaries and dependencies.
Module declarations specify module name, dependencies (`requires`), and exports (`exports`). Modules can also open packages for reflection (`opens`) and provide services (`provides`/`uses`). The module system enables better encapsulation than packages alone—internal packages aren't accessible unless explicitly exported. Understanding module syntax helps you create well-structured modular applications.
Build tools like Maven and Gradle manage dependencies, compile code, run tests, and package applications. They use project object model (POM) files or build scripts to define project structure, dependencies, and build processes. Build tools download dependencies from repositories, manage versions, and handle transitive dependencies. Understanding build tools is essential for modern Java development.
Best practices include using meaningful package names following reverse domain notation, organizing packages by feature or layer, using modules for large applications, managing dependencies with build tools, and keeping package structures flat and logical. Packages and modules should reflect application architecture. Understanding packages and modules enables you to build maintainable, well-organized Java applications.
Key Concepts
- Packages organize classes into namespaces to avoid naming conflicts.
- Java 9 modules provide strong encapsulation and explicit dependencies.
- Modules are declared in module-info.java files.
- Build tools (Maven, Gradle) manage dependencies and project structure.
- Package names typically follow reverse domain notation.
Learning Objectives
Master
- Organizing code using packages
- Understanding Java 9 module system (JPMS)
- Creating module declarations and managing dependencies
- Using build tools for dependency management
Develop
- Code organization and architecture thinking
- Understanding modular application design
- Designing maintainable project structures
Tips
- Use meaningful package names following reverse domain notation.
- Use modules for large applications requiring strong encapsulation.
- Use build tools (Maven/Gradle) for dependency management.
- Keep package structures logical and not too deep.
Common Pitfalls
- Not organizing code into packages, causing naming conflicts.
- Not understanding module system, creating overly complex module structures.
- Not using build tools, manually managing dependencies.
- Creating packages that are too deep or poorly organized.
Summary
- Packages organize classes and prevent naming conflicts.
- Java 9 modules provide strong encapsulation and explicit dependencies.
- Build tools manage dependencies and project structure.
- Understanding packages and modules enables organized applications.
- Proper organization improves code maintainability.
Exercise
Create a modular Java application with proper package structure and module declarations.
// module-info.java
module com.example.app {
requires java.base;
requires java.sql;
exports com.example.app.model;
exports com.example.app.service;
}
// com/example/app/model/User.java
package com.example.app.model;
public class User {
private String name;
private String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
// Getters and setters
public String getName() { return name; }
public String getEmail() { return email; }
}
// com/example/app/service/UserService.java
package com.example.app.service;
import com.example.app.model.User;
import java.util.*;
public class UserService {
private List<User> users = new ArrayList<>();
public void addUser(User user) {
users.add(user);
}
public List<User> getAllUsers() {
return new ArrayList<>(users);
}
public Optional<User> findUserByName(String name) {
return users.stream()
.filter(u -> u.getName().equals(name))
.findFirst();
}
}
// Main class
public class ModularApp {
public static void main(String[] args) {
UserService userService = new UserService();
userService.addUser(new User("Alice", "alice@example.com"));
userService.addUser(new User("Bob", "bob@example.com"));
System.out.println("All users:");
userService.getAllUsers().forEach(user ->
System.out.println(user.getName() + " - " + user.getEmail())
);
}
}