Skip to content

Repository files navigation

QuickJs4J

QuickJs4J lets you safely and easily run JavaScript from Java using a sandboxed environment.

Why Use QuickJs4J?

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-image for 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.

How it works

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 jar that can run wherever the JVM can go!

Quick Start

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));");
}

Calling JavaScript from Java

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));
}

Enabling Annotation Processing

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>

Passing Java Object References

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());");
}

High Level API

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.

JSR-223 Scripting Engine

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); // 7

See the full JSR-223 scripting documentation for details on bindings, Compilable, Invocable (invokeFunction, invokeMethod, getInterface), output redirection, and more.

Building a JS/TS Library

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 .mjs file at:

    target/classes/META-INF/quickjs4j/builtin_name.mjs
    

    This file bridges your Java and JS code.

Building the Project

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 install

Acknowledgements

This project stands on the shoulders of giants:

  • QuickJS – a small, embeddable JavaScript engine
  • Javy – a toolchain for compiling JavaScript to WebAssembly
  • Endive – a native JVM WebAssembly runtime

About

Run JavaScript from Java in a safe sandbox.

Resources

Stars

80 stars

Watchers

3 watching

Forks

Releases

Used by

Contributors

Languages