Isolated Class Loaders

Use isolated class loaders to prevent dependency conflicts.


Isolated class loaders provide complete dependency isolation, preventing conflicts between different versions of the same library by loading them in separate class loading contexts.

Why Use Isolated Class Loaders?

Isolated class loaders solve several problems:

  • Version conflicts: Load different versions of the same library
  • Namespace pollution: Keep main classpath clean
  • Plugin isolation: Separate plugin dependencies from each other
  • Runtime flexibility: Load and unload dependencies as needed

Creating Isolated Class Loaders

Named Isolated Class Loaders

// Create a named isolated class loader
IsolatedClassLoader isolatedLoader = libraryManager.createNamedIsolatedClassLoader("my-feature");

// Load dependencies into it
List<Dependency> dependencies = List.of(
    Dependency.of("com.google.code.gson", "gson", "2.10.1"),
    Dependency.of("org.yaml", "snakeyaml", "2.0")
);

libraryManager.loadDependencies(isolatedLoader, dependencies);

// Use classes from the isolated loader
Class<?> gsonClass = isolatedLoader.loadClass("com.google.gson.Gson");
Object gson = gsonClass.getDeclaredConstructor().newInstance();

// Don't forget to close when done
isolatedLoader.close();

Global Isolated Class Loader

// Get the shared global isolated class loader
IsolatedClassLoader globalLoader = libraryManager.getGlobalIsolatedClassLoader();

// Load dependencies into global isolated loader
libraryManager.loadDependencies(globalLoader, dependencies);

// Access classes (no need to close global loader)
Class<?> yamlClass = globalLoader.loadClass("org.yaml.snakeyaml.Yaml");

Working with Isolated Classes

Reflection with Isolated Classes

// Load class from isolated loader
Class<?> gsonClass = isolatedLoader.loadClass("com.google.gson.Gson");

// Create instance
Object gson = gsonClass.getDeclaredConstructor().newInstance();

// Call methods via reflection
Method toJson = gsonClass.getMethod("toJson", Object.class);
String json = (String) toJson.invoke(gson, myObject);

Method fromJson = gsonClass.getMethod("fromJson", String.class, Class.class);
Object obj = fromJson.invoke(gson, json, MyClass.class);

Interface-based Integration

When possible, use interfaces to work with isolated classes:

// Define interface in main classpath
public interface JsonProcessor {
    String toJson(Object obj);
    <T> T fromJson(String json, Class<T> type);
}

// Implementation loaded in isolated class loader
public class GsonJsonProcessor implements JsonProcessor {
    private final Gson gson = new Gson();
    
    @Override
    public String toJson(Object obj) {
        return gson.toJson(obj);
    }
    
    @Override
    public <T> T fromJson(String json, Class<T> type) {
        return gson.fromJson(json, type);
    }
}

Combining with Relocation

Isolated + Relocated Dependencies

// Create isolated loader
IsolatedClassLoader isolatedLoader = libraryManager.createNamedIsolatedClassLoader("gson-v2");

// Define relocations
List<Relocation> relocations = List.of(
    Relocation.of("com.google.gson", "com.myplugin.gson.v2")
);

// Load with both isolation and relocation
libraryManager.loadDependencies(isolatedLoader, dependencies, relocations);

// Use relocated classes in isolated environment
Class<?> gsonClass = isolatedLoader.loadClass("com.myplugin.gson.v2.Gson");

Class Loader Management

Checking Class Loader Status

// Check if class loader is closed
if (!isolatedLoader.isClosed()) {
    // Safe to use
    Class<?> clazz = isolatedLoader.loadClass("com.example.MyClass");
}

// Get path count (if supported)
int pathCount = isolatedLoader.getPathCount();
if (pathCount > 0) {
    System.out.println("Class loader has " + pathCount + " paths");
}

Resource Management

// Use try-with-resources for automatic cleanup
try (IsolatedClassLoader loader = libraryManager.createNamedIsolatedClassLoader("temp")) {
    libraryManager.loadDependencies(loader, dependencies);
    
    // Use the loader
    Class<?> clazz = loader.loadClass("com.example.MyClass");
    // ... work with class
    
} // Automatically closed here

Managing Multiple Loaders

// Keep track of multiple isolated loaders
Map<String, IsolatedClassLoader> loaders = new HashMap<>();

// Create specialized loaders
loaders.put("json", libraryManager.createNamedIsolatedClassLoader("json-processing"));
loaders.put("xml", libraryManager.createNamedIsolatedClassLoader("xml-processing"));
loaders.put("database", libraryManager.createNamedIsolatedClassLoader("database-drivers"));

// Load different dependencies into each
libraryManager.loadDependencies(loaders.get("json"), List.of(
    Dependency.of("com.google.code.gson", "gson", "2.10.1")
));

libraryManager.loadDependencies(loaders.get("xml"), List.of(
    Dependency.of("com.fasterxml.jackson.dataformat", "jackson-dataformat-xml", "2.15.2")
));

// Clean up all loaders
loaders.values().forEach(IsolatedClassLoader::close);

Statistics and Monitoring

// Get statistics about isolated class loaders
LibraryManager.LibraryManagerStats stats = libraryManager.getStats();
System.out.println("Active isolated class loaders: " + stats.isolatedClassLoaderCount());

// This count includes both named and global isolated class loaders

Best Practices

When to Use Isolated Class Loaders

  • Different library versions: When you need multiple versions of the same library
  • Plugin systems: To isolate plugin dependencies from each other
  • Optional features: For features that can be enabled/disabled at runtime
  • Temporary operations: For one-time operations that need specific dependencies
Edit on GitHub

Last updated on