Practical Use of Java Dynamic Compilation in Projects
This article explains how Java's dynamic compilation can be applied to modularize and simplify code management in large projects, demonstrates a basic implementation with code examples, and shows how to integrate the technique into Spring Boot applications while addressing class‑loader and dependency challenges.
Java Dynamic Compilation in Project Practice
Introduction
Most developers have never used Java's dynamic compilation feature; the author encountered it while trying to manage a heterogeneous project that required frequent code changes across many business modules.
To avoid the pain of locating owners for each module and repeatedly releasing the whole application, the author proposed splitting the codebase into small, independently managed code blocks that could be compiled on demand.
1. What Is Dynamic Compilation
1.1 Related Concepts
JavaFileManager: manages files during compilation.
DiagnosticListener: collects compilation diagnostics such as errors and warnings.
JavaFileObject: represents a Java source file or compiled bytecode.
1.2 Simple Implementation Steps
Create a JavaCompiler instance.
Create a DiagnosticCollector to gather diagnostics.
Create a JavaFileManager (e.g., StandardJavaFileManager ).
Create a JavaFileObject that holds the source code.
Obtain a CompilationTask via JavaCompiler.getTask .
Call CompilationTask.call() to compile.
Process the diagnostics and the compilation result.
Example code:
public class DynamicCompiler {
public static void main(String[] args) throws Exception {
// Create JavaCompiler
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// Collect diagnostics
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
// Manage files
StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
// Source code to compile
String code = "public class HelloWorld { public static void main(String[] args) { System.out.println(\"Hello World!\"); } }";
JavaFileObject source = new JavaSourceFromString("HelloWorld", code);
// Prepare compilation units
Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(source);
CompilationTask task = compiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits);
// Compile
boolean success = task.call();
// Output diagnostics
for (Diagnostic<? extends JavaFileObject> d : diagnostics.getDiagnostics()) {
System.out.println(d.getMessage(null));
}
// Result handling
if (success) {
System.out.println("Compilation was successful.");
} else {
System.out.println("Compilation failed.");
}
fileManager.close();
}
}
class JavaSourceFromString extends SimpleJavaFileObject {
final String code;
JavaSourceFromString(String name, String code) {
super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}Running the program prints:
Hello World!
Compilation was successful.2. Integrating with a Spring Boot Project
While the simple example works, real projects need to handle more complex scenarios such as dependency resolution and class loading.
2.1 Problems Encountered
2.1.1 Overriding the ClassLoader
Dynamic compilation generates classes at runtime, which the default application class loader cannot load. Therefore a custom ClassLoader (or a subclass) must be provided to define and load the generated bytecode.
2.1.2 Dependency Handling
The Java compiler uses JavaFileManager to locate dependent classes. In a Spring Boot fat‑jar, the default manager cannot read nested JARs, so a custom JavaFileManager is required to expose those dependencies.
2.2 Code Example
Utility method to compile a class and load it with a memory‑based class loader:
public static Class compile(String className, String code) {
try (MemoryClassLoader loader = MemoryClassLoader.genInstance()) {
loader.registerJava(className, code);
return MemoryClassLoader.getInstance().loadClass(className);
} catch (Exception e) {
// ignore
}
return null;
}Key components (simplified):
class MemoryClassLoader extends URLClassLoader {
private static final Map<String, byte[]> classBytes = new ConcurrentHashMap<>();
// register source code, compile, store byte[] in classBytes
// override findClass to defineClass from classBytes
// provide factory methods genInstance(), getInstance()
}
class MemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
// stores compiled bytecode in memory
// overrides getJavaFileForOutput to return a MemoryOutputJavaClassObject
// overrides list() to supply classes from nested JARs when running inside a Spring Boot jar
}
class SpringJavaFileManager extends JavacFileManager {
// creates a ClassLoader that can load classes from URLs inside the Spring Boot jar
}These classes together enable on‑the‑fly compilation of user‑provided snippets, proper handling of Spring Boot's embedded dependencies, and loading of the resulting classes without restarting the application.
Conclusion
Dynamic compilation is not a daily requirement, but in specific situations—such as modular business logic that must be updated without a full redeploy—it provides a clean solution. The article offers a practical reference for implementing this technique in Java backend projects, especially those built with Spring Boot.
政采云技术
ZCY Technology Team (Zero), based in Hangzhou, is a growth-oriented team passionate about technology and craftsmanship. With around 500 members, we are building comprehensive engineering, project management, and talent development systems. We are committed to innovation and creating a cloud service ecosystem for government and enterprise procurement. We look forward to your joining us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.