|
1 | | -# What is dynamic-java-compiler? |
2 | | - |
| 1 | +# Dynamic Java Compiler |
3 | 2 |
|
4 | | -_Dynamic-java-compiler_ is a library that allows users to dynamically compile and execute any java source code. Writing dynamically executed Java applications require some boilerplate code: working with classloaders, compilation error handling, etc. The idea behind this library is to free you from this development and let you focus on your business logic. |
| 3 | +[](https://github.com/raulgomis/dynamic-java-compiler/actions/workflows/master.yml) |
5 | 4 |
|
6 | | -# How does it work? |
| 5 | +Compile Java source held in a string and load the resulting class directly into |
| 6 | +the running JVM. |
7 | 7 |
|
8 | | -The dynamic ompilation task is very simple with this library. Imagine we want to compile this source code introduced dynamically as a text string by the user: |
| 8 | +Dynamic Java Compiler wraps the standard Java compiler, an in-memory file |
| 9 | +manager, and a dedicated class loader behind a small API. Source and generated |
| 10 | +bytecode stay in memory, so callers do not need to manage temporary `.java` or |
| 11 | +`.class` files. |
| 12 | + |
| 13 | +## Features |
| 14 | + |
| 15 | +- Compiles Java source at runtime using the JDK compiler |
| 16 | +- Loads generated classes without writing bytecode to disk |
| 17 | +- Supports packages, imports, and nested classes |
| 18 | +- Returns structured compiler diagnostics on failure |
| 19 | +- Has no runtime dependencies outside the JDK |
| 20 | + |
| 21 | +## Requirements |
| 22 | + |
| 23 | +- Java 17 or later |
| 24 | +- A full JDK that includes the system Java compiler |
| 25 | + |
| 26 | +The project is compiled with `maven.compiler.release=17`, so its bytecode |
| 27 | +remains compatible with Java 17 while builds can run on newer JDKs. A minimal |
| 28 | +runtime image without the `jdk.compiler` module cannot perform dynamic |
| 29 | +compilation. |
| 30 | + |
| 31 | +## Quick start |
| 32 | + |
| 33 | +Create the source code to compile: |
9 | 34 |
|
10 | 35 | ```java |
11 | | -public class Test01 implements Runnable { |
12 | | - public void run() { |
13 | | - System.out.println("Hello World!"); |
14 | | - } |
15 | | -} |
| 36 | +String source = """ |
| 37 | + public class GreetingTask implements Runnable { |
| 38 | + @Override |
| 39 | + public void run() { |
| 40 | + System.out.println("Hello from dynamically compiled code!"); |
| 41 | + } |
| 42 | + } |
| 43 | + """; |
16 | 44 | ``` |
17 | 45 |
|
18 | | -So simple, we just need to instantiate the compiler and compile the code: |
| 46 | +Compile, instantiate, and execute it: |
19 | 47 |
|
20 | 48 | ```java |
| 49 | +import com.raulgomis.djc.DynamicCompiler; |
| 50 | + |
| 51 | +DynamicCompiler<Runnable> compiler = new DynamicCompiler<>(); |
| 52 | +Class<Runnable> taskClass = compiler.compile( |
| 53 | + null, |
| 54 | + "GreetingTask", |
| 55 | + source |
| 56 | +); |
| 57 | + |
| 58 | +Runnable task = taskClass.getDeclaredConstructor().newInstance(); |
| 59 | +task.run(); |
| 60 | +``` |
| 61 | + |
| 62 | +The first argument to `compile` is the package name. Pass `null` or an empty |
| 63 | +string for the default package. The second argument is the simple class name |
| 64 | +and must agree with the class declared by the source. |
| 65 | + |
| 66 | +## Compiling a packaged class |
| 67 | + |
| 68 | +The package declared in the source must match the package passed to |
| 69 | +`compile`: |
| 70 | + |
| 71 | +```java |
| 72 | +String source = """ |
| 73 | + package example.tasks; |
| 74 | +
|
| 75 | + public class GreetingTask implements Runnable { |
| 76 | + @Override |
| 77 | + public void run() { |
| 78 | + System.out.println("Hello!"); |
| 79 | + } |
| 80 | + } |
| 81 | + """; |
| 82 | + |
21 | 83 | DynamicCompiler<Runnable> compiler = new DynamicCompiler<>(); |
22 | | -// Read source code as String |
23 | | -Class<Runnable> clazz = compiler.compile(null, "Test01", source); |
24 | | -final Runnable r; |
| 84 | +Class<Runnable> taskClass = compiler.compile( |
| 85 | + "example.tasks", |
| 86 | + "GreetingTask", |
| 87 | + source |
| 88 | +); |
| 89 | +``` |
| 90 | + |
| 91 | +The returned class has the binary name `example.tasks.GreetingTask`. |
| 92 | + |
| 93 | +## Handling compilation errors |
| 94 | + |
| 95 | +Compilation failures throw `DynamicCompilerException`. Use |
| 96 | +`getDiagnostics()` for structured JDK diagnostics or |
| 97 | +`getDiagnosticsError()` for a readable summary: |
| 98 | + |
| 99 | +```java |
| 100 | +import com.raulgomis.djc.DynamicCompilerException; |
| 101 | + |
25 | 102 | try { |
26 | | - r = clazz.newInstance(); |
27 | | - r.run(); |
28 | | -} catch (InstantiationException | IllegalAccessException e) { |
29 | | - e.printStackTrace(); |
| 103 | + DynamicCompiler<Runnable> compiler = new DynamicCompiler<>(); |
| 104 | + compiler.compile(null, "BrokenTask", brokenSource); |
| 105 | +} catch (DynamicCompilerException exception) { |
| 106 | + System.err.print(exception.getDiagnosticsError()); |
| 107 | + |
| 108 | + exception.getDiagnostics().forEach(diagnostic -> { |
| 109 | + System.err.printf( |
| 110 | + "line %d, column %d: %s%n", |
| 111 | + diagnostic.getLineNumber(), |
| 112 | + diagnostic.getColumnNumber(), |
| 113 | + diagnostic.getMessage(null) |
| 114 | + ); |
| 115 | + }); |
30 | 116 | } |
31 | 117 | ``` |
32 | 118 |
|
33 | | -The final result will be: |
| 119 | +Diagnostic wording is produced by the active JDK and may differ between JDK |
| 120 | +versions. |
| 121 | + |
| 122 | +## Security |
| 123 | + |
| 124 | +This library compiles code; it does not sandbox it. Once loaded and invoked, |
| 125 | +dynamically compiled code runs with the same permissions as the host |
| 126 | +application. Do not compile or execute untrusted source without a separate, |
| 127 | +appropriately isolated execution environment. |
| 128 | + |
| 129 | +## Building and testing |
| 130 | + |
| 131 | +Run the full verification build: |
| 132 | + |
| 133 | +```shell |
| 134 | +mvn verify |
34 | 135 | ``` |
35 | | -Hello World! |
| 136 | + |
| 137 | +This runs the JUnit 5 test suite and generates a JaCoCo coverage report at |
| 138 | +`target/site/jacoco/index.html`. |
| 139 | + |
| 140 | +For a clean build: |
| 141 | + |
| 142 | +```shell |
| 143 | +mvn clean verify |
36 | 144 | ``` |
37 | 145 |
|
38 | | - |
39 | | -## Contribution |
| 146 | +## Contributing |
| 147 | + |
| 148 | +Pull requests are welcome. Please include focused tests for behavioral changes |
| 149 | +and run `mvn verify` before submitting. |
| 150 | + |
| 151 | +Use the [issue tracker](https://github.com/raulgomis/dynamic-java-compiler/issues) |
| 152 | +to report bugs or propose features. |
40 | 153 |
|
41 | | -You are welcome to contribute to the project using pull requests on GitHub. |
| 154 | +## License |
42 | 155 |
|
43 | | -If you find a bug or want to request a feature, please use the [issue tracker](https://github.com/raulgomis/dynamic-java-compiler/issues) of Github. |
| 156 | +This project is available under the [MIT License](LICENSE). |
0 commit comments