Back to Java
2026-03-207 min read

JS Modules (Java)

Learn JS Modules (Java) step by step with clear examples and exercises.

Why This Matters

Welcome to this full guide on JavaScript Modules in Java! This tutorial is designed to provide practical depth, focusing on real-world scenarios and common pitfalls. We'll cover the core concept of JavaScript modules in Java, a worked example, common mistakes, practice questions, and frequently asked questions. Let's dive in!

JavaScript Modules are essential for organizing and managing large codebases effectively. They help to encapsulate reusable pieces of code, promote modularity, and reduce complexity. Understanding JavaScript modules is crucial for Java developers who want to write cleaner, more maintainable code.

By using JavaScript modules, you can:

  • Encapsulate functionality within individual modules, making it easier to manage and test your code.
  • Reduce the risk of naming conflicts by isolating variables, methods, and classes within their respective modules.
  • Improve performance by only loading necessary modules at runtime.
  • Simplify dependency management using build tools like Maven or Gradle.

Prerequisites

To fully grasp this tutorial, you should have a good understanding of:

  • Basic Java syntax and concepts (variables, methods, classes, etc.)
  • Maven or Gradle build system (for managing dependencies)
  • Familiarity with JavaScript ES6 features (optional but recommended)
  • Module declarations using export and import statements.
  • Default exports (using a single export without a name).
  • Importing multiple modules or renaming imports.

Core Concept

JavaScript Modules in Java are implemented using the java.lang.Module and related classes. To create a module, you need to:

  1. Annotate your class with @Module.
  2. Declare dependencies using requires directive.
  3. Export classes or interfaces using exports directive.
  4. Use the ModuleBuilder class to load and configure modules at runtime.

Here's a simple example of a module that exports a HelloWorld class:

// src/main/java/com/example/myModule/MyModule.java
import java.lang.module.Module;

@Module(name = "myModule")
public class MyModule {
// Declare a dependency on the 'util' module
requires com.example.util;

// Export the HelloWorld class
exports com.example.myModule.hello;
}
// src/main/java/com/example/myModule/hello/HelloWorld.java
public class HelloWorld {
public void sayHello() {
System.out.println("Hello, World!");
}
}

To use the MyModule, you need to load it using ModuleBuilder:

// src/main/java/com/example/Main.java
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleRef;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {
public static void main(String[] args) throws Exception {
// Create a module finder that includes the current project directory
ModuleFinder finder = new ModuleFinder.of(
ModuleFinder.ofSystem(),
ModuleFinder.of(Paths.get("."))
);

// Load the 'myModule' module using the module finder
ModuleRef myModule = ModuleLayer.boot().findModule("com.example.myModule", finder);

// Create a class loader that includes the loaded module and the system classpath
URLClassLoader classLoader = new URLClassLoader(
new URL[] { myModule.getClassLoaderDescriptor().toURL() },
ClassLoader.getSystemClassLoader()
);

// Load the HelloWorld class from the 'myModule' module using the custom class loader
Class<?> helloWorldClass = classLoader.loadClass("com.example.myModule.hello.HelloWorld");

// Create an instance of the HelloWorld class and call its sayHello() method
HelloWorld helloWorld = (HelloWorld) helloWorldClass.getDeclaredConstructor().newInstance();
helloWorld.sayHello();
}
}

Importing Modules

In JavaScript modules, you can import other modules using the import statement. In Java, you need to use the requires directive and load the module at runtime using ModuleBuilder. Here's an example of a module that imports another module:

// src/main/java/com/example/myDependency/MyDependency.java
@Module(name = "myDependency")
public class MyDependency {
// Export a Util class with useful methods
exports com.example.myDependency.util;
}
// src/main/java/com/example/myModule/MyModule.java (modified to import the 'myDependency' module)
@Module(name = "myModule")
public class MyModule {
// Declare a dependency on the 'myDependency' module
requires com.example.myDependency;

// Export the HelloWorld class
exports com.example.myModule.hello;
}

Worked Example

In this example, we'll create a simple application that uses JavaScript modules to manage dependencies between multiple classes. The application will consist of:

  1. A User class with methods for managing user data.
  2. A Database module that provides a database connection and basic CRUD operations.
  3. A Main class that uses the User and Database modules to create, read, update, and delete users.

User Class

// src/main/java/com/example/user/User.java
public class User {
private int id;
private String name;
private String email;

// Constructor, getters, setters, and other methods omitted for brevity
}

Database Module (Expanded)

// src/main/java/com/example/database/Database.java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

@Module(name = "database")
public class Database {
// Declare a dependency on the 'jdbc' module (JDBC driver)
requires java.sql;

// Export the DatabaseConnection and UserRepository interfaces
exports com.example.database.connection;
exports com.example.database.repository;
}

Database Connection Class

// src/main/java/com/example/database/connection/DatabaseConnection.java
public class DatabaseConnection implements AutoCloseable {
private Connection connection;

// Constructor, methods for getting the connection and closing it omitted for brevity
}

User Repository Interface

// src/main/java/com/example/database/repository/UserRepository.java
public interface UserRepository {
void create(User user) throws SQLException;
User read(int id) throws SQLException;
void update(User user) throws SQLException;
void delete(int id) throws SQLException;
}

Database Module (Importing User Class)

To use the User class within the Database module, we need to export it from our main project and import it in the Database module.

// src/main/java/com/example/myModule/MyModule.java (modified to export the User class)
@Module(name = "myModule")
public class MyModule {
// Declare a dependency on the 'database' module
requires com.example.database;

// Export the HelloWorld and User classes
exports com.example.myModule.hello;
exports com.example.user;
}
// src/main/java/com/example/database/Database.java (modified to import the User class)
@Module(name = "database")
public class Database {
// Declare a dependency on the 'myModule' module
requires com.example.myModule;

// Export the DatabaseConnection and UserRepository interfaces
exports com.example.database.connection;
exports com.example.database.repository;
}

Main Class (Using Imported Modules)

Now that we have our modules set up, we can use them in the Main class to create, read, update, and delete users:

// src/main/java/com/example/Main.java (modified to use imported modules)
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleRef;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {
public static void main(String[] args) throws Exception {
// Create a module finder that includes the current project directory
ModuleFinder finder = new ModuleFinder.of(
ModuleFinder.ofSystem(),
ModuleFinder.of(Paths.get("."))
);

// Load the 'myModule' and 'database' modules using the module finder
ModuleRef myModule = ModuleLayer.boot().findModule("com.example.myModule", finder);
ModuleRef database = ModuleLayer.boot().findModule("com.example.database", finder);

// Create a class loader that includes the loaded modules and the system classpath
URLClassLoader classLoader = new URLClassLoader(
new URL[] { myModule.getClassLoaderDescriptor().toURL(),
database.getClassLoaderDescriptor().toURL() },
ClassLoader.getSystemClassLoader()
);

// Load the User, DatabaseConnection, and UserRepository classes from their respective modules using the custom class loader
Class<?> userClass = classLoader.loadClass("com.example.user.User");
Class<?> databaseConnectionClass = classLoader.loadClass("com.example.database.connection.DatabaseConnection");
Class<?> userRepositoryClass = classLoader.loadClass("com.example.database.repository.UserRepository");

// Create instances of the User, DatabaseConnection, and UserRepository classes
User user = new userClass.getDeclaredConstructor().newInstance();
DatabaseConnection connection = (DatabaseConnection) databaseConnectionClass.getDeclaredConstructor().newInstance();
UserRepository repository = (UserRepository) userRepositoryClass.getDeclaredConstructor().newInstance();

// Use the User, DatabaseConnection, and UserRepository objects to create, read, update, and delete users
// ... (implementation omitted for brevity)
}
}

Common Mistakes

  1. Forgetting to annotate your class with @Module.
  2. Not declaring dependencies using the requires directive.
  3. Exporting classes or interfaces without specifying their package (use exports com.example.myModule.hello; instead of exports hello;).
  4. Failing to load modules at runtime using ModuleBuilder.
  5. Not handling exceptions when working with databases.
  6. Not closing database connections after use (using the AutoCloseable interface or manually closing connections).
  7. Forgetting to import necessary classes and interfaces from other modules.
  8. Using conflicting versions of dependencies within different modules.
  9. Creating circular dependencies between modules.
  10. Overcomplicating the modular structure by creating too many small, tightly-coupled modules.

Practice Questions

  1. Create a new module that exports a Shape class with methods for calculating the area and perimeter of different shapes (circle, rectangle, square).
  • Export the Shape class from your main project.
  • Create a Circle, Rectangle, and Square class within the Shape module.
  • Implement methods for calculating the area and perimeter in each shape class.
  • Use the ModuleBuilder to load the Shape module at runtime.
  1. Modify the example application to include multiple users and implement methods for listing all users, searching for users by name or email, and deleting users by ID.
  • Create a UserRepositoryImpl class that implements the UserRepository interface within the Database module.
  • Implement the required CRUD operations in the UserRepositoryImpl class.
  • Modify the Main class to create, read, update, delete, list, search, and delete users using the UserRepositoryImpl class.
  1. Create a new module that provides a simple logging system with log levels (DEBUG, INFO, WARNING, ERROR) and a custom logger interface.
  • Define the Logger interface with methods for setting the log level and logging messages at different levels.
  • Implement the Logger interface in a LoggerImpl class within the Logging module.
  • Use the ModuleBuilder to load the Logging module at runtime.
  • Modify the Main class to use the Logger to log messages at different levels.

FAQ

A: JavaScript modules offer better encapsulation, modularity, and manageability for larger codebases. They also provide a more flexible way to manage dependencies between different parts of an application. Traditional package organization can lead to naming conflicts, circular dependencies, and other issues when dealing with large, complex projects.

  1. Q: Can I use JavaScript modules with
JS Modules (Java) | Java | XQA Learn