QuickJs4J lets you safely and easily run JavaScript from Java using a sandboxed environment.
QuickJs4J provides a secure and efficient way to execute JavaScript within Java. By running code in a sandbox, it ensures:
- Memory safety – JavaScript runs in isolation, protecting your application from crashes or memory leaks.
- No system access by default – JavaScript cannot access the filesystem, network, or other sensitive resources unless explicitly allowed.
- Portability – Being pure Java bytecode, it runs wherever the JVM does.
- Native-image friendly – Compatible with GraalVM's
native-imagefor fast, lightweight deployments. - Forward compatible - regular Java bytecode is generated, the version of QuickJS4J you’re currently using will remain compatible even when you upgrade to a newer Java runtime.
Whether you're embedding scripting capabilities or isolating untrusted code, QuickJs4J is designed for safe and seamless integration.
There are a few steps to achieve the result:
- compile QuickJS to WebAssembly
- translate the QuickJS payload to pure Java bytecode using Endive Compiler
- run QuickJS directly from Java without using JNI
- ship an extremely small and self contained
jarthat can run wherever the JVM can go!
Add QuickJs4J as a standard Maven dependency:
<dependency>
<groupId>io.roastedroot</groupId>
<artifactId>quickjs4j</artifactId>
</dependency>Then run a simple "Hello World" example:
importio.roastedroot.quickjs4j.core.Runner;
try (varrunner = Runner.builder().build()) {
runner.compileAndExec("console.log(\"Hello QuickJs4J!\");");
System.out.println(runner.stdout());
}Note: You must explicitly print your JavaScript program’s output using System.out.
QuickJs4J runs JavaScript in a secure, sandboxed environment. To simplify communication, it allows you to bind Java methods so they can be called directly from JavaScript.
importio.roastedroot.quickjs4j.core.Engine;
importio.roastedroot.quickjs4j.core.Runner;
importio.roastedroot.quickjs4j.annotations.HostFunction;
importio.roastedroot.quickjs4j.annotations.Builtins;
@Builtins("from_java")
classJavaApi {
@HostFunction("my_java_func")
publicStringadd(intx, inty) {
return"hello " + (x + y);
}
@HostFunction("my_java_check")
publicvoidcheck(Stringvalue) {
assert("hello 42".equals(value));
}
}
varengine =
Engine.builder()
.addBuiltins(JavaApi_Builtins.toBuiltins(newJavaApi()))
.build();
try (varrunner = Runner.builder().withEngine(engine).build()) {
runner.exec("from_java.my_java_check(from_java.my_java_func(40, 2));");
}To invoke functions defined in a JavaScript or TypeScript library, define an interface like this:
importio.roastedroot.quickjs4j.core.Engine;
importio.roastedroot.quickjs4j.core.Runner;
importio.roastedroot.quickjs4j.annotations.GuestFunction;
importio.roastedroot.quickjs4j.annotations.Invokables;
@Invokables("from_js")
interfaceJsApi {
@GuestFunctionStringsub(intx, inty);
}
varengine =
Engine.builder()
.addInvokables(JsApi_Invokables.toInvokables())
.build();
// Inlined for demo; normally loaded from a packaged distribution fileStringjsLibrary = "function sub(x, y) { return \"hello js \" + (x - y); };";
try (varrunner = Runner.builder().withEngine(engine).build()) {
varjsApi = JsApi_Invokables.create(jsLibrary, runner);
System.out.println(jsApi.sub(3, 1));
}Configure the annotation processor in your Maven pom.xml:
<dependencies>
<dependency>
<groupId>io.roastedroot</groupId>
<artifactId>quickjs4j-annotations</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.roastedroot</groupId>
<artifactId>quickjs4j-processor</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>Sometimes, you may want to pass a Java object reference to JavaScript without serializing it (i.e., keeping it only in Java memory). Use HostRefs as shown below:
importio.roastedroot.quickjs4j.annotations.HostRefParam;
importio.roastedroot.quickjs4j.annotations.ReturnsHostRef;
@ReturnsHostRef@HostFunction("my_java_ref")
publicStringmyRef() {
return"a Java string not visible in JS";
}
@HostFunction("my_java_ref_check")
publicvoidmyRefCheck(@HostRefParamStringvalue) {
...
}
try (varmyTestModule = newMyJsTestModule()) {
myTestModule.exec("my_java_ref_check(my_java_ref());");
}An higher level API is exposed for convenience to wrap everything up for the most common use cases.
You can use the ScriptInterface annotation:
importio.roastedroot.quickjs4j.annotations.ScriptInterface;
publicclassCalculatorContext {
publicvoidlog(Stringmessage) {
System.out.println("LOG>> " + message);
}
}
@ScriptInterface(context = CalculatorContext.class)
publicinterfaceCalculator {
intadd(intterm1, intterm2);
intsubtract(intterm1, intterm2);
}
try (varcalculator = newCalculator_Proxy(jsLibrary, newCalculatorContext()) {
calculator.add(1, 2);
calculator.subtract(3, 1);
}NOTE: currently only basic use is supported.
QuickJs4J also provides a JSR-223 (javax.script) compliant scripting engine with Compilable and Invocable support:
importjavax.script.*;
ScriptEngineengine = newScriptEngineManager().getEngineByName("quickjs4j");
engine.eval("1 + 2"); // 3engine.put("name", "World");
engine.eval("'Hello, ' + name"); // "Hello, World"engine.eval("function add(a, b) { return a + b; }");
((Invocable) engine).invokeFunction("add", 3, 4); // 7See the full JSR-223 scripting documentation for details on bindings, Compilable, Invocable (invokeFunction, invokeMethod, getInterface), output redirection, and more.
To build your JavaScript/TypeScript library, refer to this example.
Key points:
Output an ECMAScript module using tools like
esbuild:esbuild your_file.js --format=esm
The library must export the expected functions.
The annotation processor will generate a
.mjsfile at:target/classes/META-INF/quickjs4j/builtin_name.mjsThis file bridges your Java and JS code.
To build this project, you'll need:
- A Rust toolchain
- JDK 11 or newer
- Maven
Steps:
rustup target add wasm32-wasip1 # Only needed oncecd javy-plugin
make build
cd ..
mvn clean installThis project stands on the shoulders of giants: