Package Relocation
Use package relocation to avoid namespace conflicts between libraries.
Package relocation renames packages in library JARs to prevent namespace conflicts, especially important in plugin environments where multiple plugins might bundle different versions of the same library.
Why Package Relocation?
When multiple plugins use the same library, conflicts can occur if they're loaded into the same classpath:
- Problem: Plugin A loads Gson 2.8.9, Plugin B loads Gson 2.10.1 → version conflicts
- Solution: Plugin A uses
com.google.gson, Plugin B usescom.pluginb.libs.gson
Creating Relocations
Simple Relocation
// Create a simple relocation rule
Relocation relocation = Relocation.of("com.google.gson", "com.myplugin.libs.gson");
// Check if relocation would actually change anything
if (relocation.isEffective()) {
System.out.println("Relocation: " + relocation.getDescription());
}Using the Builder Pattern
Relocation relocation = Relocation.builder()
.from("com.google.gson")
.to("com.myplugin.libs.gson")
.build();
// Get human-readable description
String description = relocation.getDescription(); // "com.google.gson -> com.myplugin.libs.gson"Loading Dependencies with Relocation
Basic Relocation Loading
// Create dependencies and relocations
List<Dependency> dependencies = List.of(
Dependency.of("com.google.code.gson", "gson", "2.10.1")
);
List<Relocation> relocations = List.of(
Relocation.of("com.google.gson", "com.myplugin.libs.gson")
);
// Load with relocation applied to main classpath
libraryManager.loadDependencies(dependencies, relocations);Multiple Package Relocation
// Relocate multiple related packages
List<Relocation> relocations = List.of(
Relocation.of("com.google.gson", "com.myplugin.libs.gson"),
Relocation.of("com.google.gson.reflect", "com.myplugin.libs.gson.reflect"),
Relocation.of("com.google.gson.stream", "com.myplugin.libs.gson.stream")
);
libraryManager.loadDependencies(dependencies, relocations);Isolated Class Loaders with Relocation
// Create isolated class loader
IsolatedClassLoader isolatedLoader = libraryManager.createNamedIsolatedClassLoader("gson-loader");
List<Dependency> gsonDeps = List.of(
Dependency.of("com.google.code.gson", "gson", "2.10.1")
);
List<Relocation> gsonRelocations = List.of(
Relocation.of("com.google.gson", "com.myplugin.libs.gson")
);
// Load into isolated class loader with relocation
libraryManager.loadDependencies(isolatedLoader, gsonDeps, gsonRelocations);
// Use the relocated classes
Class<?> gsonClass = isolatedLoader.loadClass("com.myplugin.libs.gson.Gson");Working with Relocated Classes
Loading Relocated Classes
// After relocation, use the new package name
try {
// Original: com.google.gson.Gson
// Relocated: com.myplugin.libs.gson.Gson
Class<?> gsonClass = Class.forName("com.myplugin.libs.gson.Gson");
Object gson = gsonClass.getDeclaredConstructor().newInstance();
// Use reflection to work with relocated classes
Method toJson = gsonClass.getMethod("toJson", Object.class);
String json = (String) toJson.invoke(gson, myObject);
} catch (Exception e) {
logger.error("Failed to use relocated Gson", e);
}Relocation Implementation Details
Automatic Caching
Quark automatically caches relocated JARs to avoid repeated processing:
// First call - JAR is relocated and cached
libraryManager.loadDependencies(dependencies, relocations);
// Second call - Uses cached relocated JAR (much faster)
libraryManager.loadDependencies(dependencies, relocations);Relocation Dependencies
Relocation requires additional dependencies that are loaded automatically:
- ASM 9.7: For bytecode manipulation
- jar-relocator 1.7: For performing the actual relocation
These are loaded into an isolated class loader to avoid conflicts.
Best Practices
Naming Conventions
// Good: Use your plugin/organization namespace
Relocation.of("com.google.gson", "com.myplugin.libs.gson");
Relocation.of("org.yaml", "com.myplugin.libs.yaml");
// Good: Version-specific names for multiple versions
Relocation.of("com.google.gson", "com.myplugin.libs.gson.v2_10");
// Avoid: Generic suffixes that might conflict
// Relocation.of("com.google.gson", "com.google.gson.relocated");Performance Considerations
// Reuse relocations for multiple dependency loads
List<Relocation> commonRelocations = List.of(
Relocation.of("com.google.gson", "com.myplugin.libs.gson")
);
// Apply to multiple dependency sets
libraryManager.loadDependencies(configDeps, commonRelocations);
libraryManager.loadDependencies(utilityDeps, commonRelocations);Troubleshooting
Common Issues
- ClassNotFoundException: Ensure you're using the correct relocated package name
- NoSuchMethodError: Relocated classes might have different method signatures
- Serialization issues: Relocated classes might not be compatible with existing serialized data
- Reflection errors: Update reflection code to use relocated class names
Debug Relocation
// Enable debug logging to see relocation process
libraryManager.setLogLevel(LogLevel.DEBUG);
// Check what dependencies were loaded and where
Map<Dependency, Path> loaded = libraryManager.getLoadedDependencies();
for (Map.Entry<Dependency, Path> entry : loaded.entrySet()) {
Path jarPath = entry.getValue();
if (jarPath.toString().contains("relocated")) {
System.out.println("Relocated JAR: " + entry.getKey().getCoordinates() + " -> " + jarPath);
}
}
// Verify relocation effectiveness
Relocation relocation = Relocation.of("com.google.gson", "com.myplugin.libs.gson");
if (relocation.isEffective()) {
System.out.println("Relocation is effective: " + relocation.getDescription());
} else {
System.out.println("Relocation would have no effect");
}Error Handling
try {
libraryManager.loadDependencies(dependencies, relocations);
} catch (RelocationHandler.RelocationException e) {
logger.error("Relocation failed: " + e.getMessage(), e);
// Fallback: load without relocation
libraryManager.loadDependencies(dependencies);
}Edit on GitHub
Last updated on