Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/feature-branches.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Feature Branch Tests

on:
push:
branches:
- 'feature/**'
pull_request:
branches:
- master
types: [opened, synchronize, reopened, ready_for_review]

permissions:
contents: read

jobs:
test:
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ubuntu-latest
strategy:
matrix:
java: ['17', '21', '25', '26']
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ matrix.java }}
cache: maven

- name: Run tests and generate coverage report
run: mvn -B verify --file pom.xml

- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: jacoco-coverage-report-java-${{ matrix.java }}
path: target/site/jacoco/
if-no-files-found: error
32 changes: 25 additions & 7 deletions .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,32 @@ on:
pull_request:
branches: [master]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
java: ['17', '21', '25', '26']
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Maven
run: mvn -B package --file pom.xml
- name: Checkout
uses: actions/checkout@v4

- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ matrix.java }}
cache: maven

- name: Run tests and generate coverage report
run: mvn -B verify --file pom.xml

- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: jacoco-coverage-report-java-${{ matrix.java }}
path: target/site/jacoco/
if-no-files-found: error
67 changes: 67 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# AGENTS.md

## Project overview

This repository contains a small Java library for compiling and loading Java
source code at runtime. Production code is under
`src/main/java/com/raulgomis/djc`, and tests are under the matching package in
`src/test/java`.

The project targets Java 17 bytecode and uses Maven, JUnit 5, and JaCoCo.

## Build and verification

Run commands from the repository root.

- `mvn test` runs the unit tests.
- `mvn verify` runs the tests and generates the JaCoCo report.
- `mvn clean verify` performs a clean, full verification when stale build
output may matter.
- The HTML coverage report is written to
`target/site/jacoco/index.html`.

Before handing off a code change, run `mvn verify`. Also run
`git diff --check` to catch whitespace errors.

## Implementation guidelines

- Keep the public API small and compatible unless the task explicitly calls
for an API change.
- Compile with Java 17-compatible language and library features.
- Follow the existing four-space indentation and brace style.
- Prefer standard-library solutions; avoid adding dependencies for behavior
that can be implemented clearly without them.
- Keep compiler diagnostics associated with the individual compilation that
produced them.
- Preserve the in-memory design: generated source and bytecode should not
require temporary source or class files.
- Treat package names, binary class names, source file names, and resource
paths carefully. A source package and the qualified name used for loading
must agree.
- Preserve exception causes when wrapping unexpected compiler, file-manager,
or class-loading failures.

## Testing guidelines

- Use JUnit Jupiter.
- Name tests after observable behavior, such as
`compilesAClassInANamedPackage`.
- Add regression tests for bug fixes and focused tests for new branches.
- Test both successful compilation and relevant failure behavior, including
diagnostics and wrapped causes.
- Tests for package-private implementation classes should remain in
`com.raulgomis.djc` rather than widening production visibility.
- Prefer small inline source strings for a single scenario. Use
`src/test/resources` when a source fixture is substantial or reused.
- Dynamically compiled test code must be deterministic and must not perform
network access, modify user files, or depend on machine-specific state.
- Do not weaken assertions merely to increase the coverage percentage.

## Change hygiene

- The worktree may already contain user changes. Inspect `git status` and
preserve unrelated modifications.
- Do not edit generated files under `target`.
- Keep documentation and CI configuration consistent with the Java version
and Maven commands in `pom.xml`.
- Avoid broad formatting or refactoring unrelated to the requested change.
161 changes: 137 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,156 @@
# What is dynamic-java-compiler?
![Build](https://github.com/raulgomis/dynamic-java-compiler/workflows/Build/badge.svg)
# Dynamic Java Compiler

_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.
[![Build](https://github.com/raulgomis/dynamic-java-compiler/actions/workflows/master.yml/badge.svg)](https://github.com/raulgomis/dynamic-java-compiler/actions/workflows/master.yml)

# How does it work?
Compile Java source held in a string and load the resulting class directly into
the running JVM.

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:
Dynamic Java Compiler wraps the standard Java compiler, an in-memory file
manager, and a dedicated class loader behind a small API. Source and generated
bytecode stay in memory, so callers do not need to manage temporary `.java` or
`.class` files.

## Features

- Compiles Java source at runtime using the JDK compiler
- Loads generated classes without writing bytecode to disk
- Supports packages, imports, and nested classes
- Returns structured compiler diagnostics on failure
- Has no runtime dependencies outside the JDK

## Requirements

- Java 17 or later
- A full JDK that includes the system Java compiler

The project is compiled with `maven.compiler.release=17`, so its bytecode
remains compatible with Java 17 while builds can run on newer JDKs. A minimal
runtime image without the `jdk.compiler` module cannot perform dynamic
compilation.

## Quick start

Create the source code to compile:

```java
public class Test01 implements Runnable {
public void run() {
System.out.println("Hello World!");
}
}
String source = """
public class GreetingTask implements Runnable {
@Override
public void run() {
System.out.println("Hello from dynamically compiled code!");
}
}
""";
```

So simple, we just need to instantiate the compiler and compile the code:
Compile, instantiate, and execute it:

```java
import com.raulgomis.djc.DynamicCompiler;

DynamicCompiler<Runnable> compiler = new DynamicCompiler<>();
Class<Runnable> taskClass = compiler.compile(
null,
"GreetingTask",
source
);

Runnable task = taskClass.getDeclaredConstructor().newInstance();
task.run();
```

The first argument to `compile` is the package name. Pass `null` or an empty
string for the default package. The second argument is the simple class name
and must agree with the class declared by the source.

## Compiling a packaged class

The package declared in the source must match the package passed to
`compile`:

```java
String source = """
package example.tasks;

public class GreetingTask implements Runnable {
@Override
public void run() {
System.out.println("Hello!");
}
}
""";

DynamicCompiler<Runnable> compiler = new DynamicCompiler<>();
// Read source code as String
Class<Runnable> clazz = compiler.compile(null, "Test01", source);
final Runnable r;
Class<Runnable> taskClass = compiler.compile(
"example.tasks",
"GreetingTask",
source
);
```

The returned class has the binary name `example.tasks.GreetingTask`.

## Handling compilation errors

Compilation failures throw `DynamicCompilerException`. Use
`getDiagnostics()` for structured JDK diagnostics or
`getDiagnosticsError()` for a readable summary:

```java
import com.raulgomis.djc.DynamicCompilerException;

try {
r = clazz.newInstance();
r.run();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
DynamicCompiler<Runnable> compiler = new DynamicCompiler<>();
compiler.compile(null, "BrokenTask", brokenSource);
} catch (DynamicCompilerException exception) {
System.err.print(exception.getDiagnosticsError());

exception.getDiagnostics().forEach(diagnostic -> {
System.err.printf(
"line %d, column %d: %s%n",
diagnostic.getLineNumber(),
diagnostic.getColumnNumber(),
diagnostic.getMessage(null)
);
});
}
```

The final result will be:
Diagnostic wording is produced by the active JDK and may differ between JDK
versions.

## Security

This library compiles code; it does not sandbox it. Once loaded and invoked,
dynamically compiled code runs with the same permissions as the host
application. Do not compile or execute untrusted source without a separate,
appropriately isolated execution environment.

## Building and testing

Run the full verification build:

```shell
mvn verify
```
Hello World!

This runs the JUnit 5 test suite and generates a JaCoCo coverage report at
`target/site/jacoco/index.html`.

For a clean build:

```shell
mvn clean verify
```


## Contribution
## Contributing

Pull requests are welcome. Please include focused tests for behavioral changes
and run `mvn verify` before submitting.

Use the [issue tracker](https://github.com/raulgomis/dynamic-java-compiler/issues)
to report bugs or propose features.

You are welcome to contribute to the project using pull requests on GitHub.
## License

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.
This project is available under the [MIT License](LICENSE).
Loading
Loading