34.1 Dynamic Module Layers

Create isolated module graphs at runtime for plugin systems, multi-tenancy, or feature toggles.

Creating a Module Layer

import java.lang.module.*;
import java.nio.file.*;

ModuleFinder finder = ModuleFinder.of(Path.of("plugins"));
ModuleLayer parent = ModuleLayer.boot();
Configuration config = parent.configuration()
    .resolve(finder, ModuleFinder.of(), Set.of("com.example.plugin"));

ClassLoader scl = ClassLoader.getSystemClassLoader();
ModuleLayer layer = parent.defineModulesWithOneLoader(config, scl);

Loading Classes from Layer

Module pluginModule = layer.findModule("com.example.plugin").orElseThrow();
Class<?> pluginClass = pluginModule.getClassLoader().loadClass("com.example.plugin.PluginImpl");
Object instance = pluginClass.getDeclaredConstructor().newInstance();

Isolation

  • Each layer has its own module graph and class loader hierarchy.
  • Layers can share parent modules (e.g., java.base).
  • Use separate loaders for strict isolation:
ModuleLayer layer = parent.defineModulesWithManyLoaders(config, scl);

Service Discovery in Layers

ServiceLoader<MyService> loader = ServiceLoader.load(layer, MyService.class);
MyService svc = loader.findFirst().orElseThrow();

Example: Plugin System

record Plugin(String name, ModuleLayer layer) {}

List<Plugin> plugins = new ArrayList<>();
for (Path pluginDir : Files.list(Path.of("plugins")).toList()) {
  ModuleFinder finder = ModuleFinder.of(pluginDir);
  Configuration cfg = ModuleLayer.boot().configuration()
      .resolve(finder, ModuleFinder.of(), Set.of("plugin.module"));
  ModuleLayer layer = ModuleLayer.boot().defineModulesWithOneLoader(cfg, scl);
  plugins.add(new Plugin(pluginDir.getFileName().toString(), layer));
}

Guidance

  • Use layers for runtime feature loading or multi-tenant isolation.
  • Avoid excessive layers; they add complexity and memory overhead.
  • Coordinate updates to plugins by reloading layers.
  • Combine with ServiceLoader for discovery within layers.