Quickjs4j
Run JavaScript from Java in a safe sandbox.
Install / Use
/learn @roastedroot/Quickjs4jREADME
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-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.
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 Chicory Compiler
- run QuickJS directly from Java without using JNI
- ship an extremely small and self contained
jarthat 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:
import io.roastedroot.quickjs4j.core.Runner;
try (var runner = 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.
import io.roastedroot.quickjs4j.core.Engine;
import io.roastedroot.quickjs4j.core.Runner;
import io.roastedroot.quickjs4j.annotations.HostFunction;
import io.roastedroot.quickjs4j.annotations.Builtins;
@Builtins("from_java")
class JavaApi {
@HostFunction("my_java_func")
public String add(int x, int y) {
return "hello " + (x + y);
}
@HostFunction("my_java_check")
public void check(String value) {
assert("hello 42".equals(value));
}
}
var engine =
Engine.builder()
.addBuiltins(JavaApi_Builtins.toBuiltins(new JavaApi()))
.build();
try (var runner = 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:
import io.roastedroot.quickjs4j.core.Engine;
import io.roastedroot.quickjs4j.core.Runner;
import io.roastedroot.quickjs4j.annotations.GuestFunction;
import io.roastedroot.quickjs4j.annotations.Invokables;
@Invokables("from_js")
interface JsApi {
@GuestFunction
String sub(int x, int y);
}
var engine =
Engine.builder()
.addInvokables(JsApi_Invokables.toInvokables())
.build();
// Inlined for demo; normally loaded from a packaged distribution file
String jsLibrary = "function sub(x, y) { return \"hello js \" + (x - y); };";
try (var runner = Runner.builder().withEngine(engine).build()) {
var jsApi = 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:
import io.roastedroot.quickjs4j.annotations.HostRefParam;
import io.roastedroot.quickjs4j.annotations.ReturnsHostRef;
@ReturnsHostRef
@HostFunction("my_java_ref")
public String myRef() {
return "a Java string not visible in JS";
}
@HostFunction("my_java_ref_check")
public void myRefCheck(@HostRefParam String value) {
...
}
try (var myTestModule = new MyJsTestModule()) {
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:
import io.roastedroot.quickjs4j.annotations.ScriptInterface;
public class CalculatorContext {
public void log(String message) {
System.out.println("LOG>> " + message);
}
}
@ScriptInterface(context = CalculatorContext.class)
public interface Calculator {
int add(int term1, int term2);
int subtract(int term1, int term2);
}
try (var calculator = new Calculator_Proxy(jsLibrary, new CalculatorContext()) {
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:
import javax.script.*;
ScriptEngine engine = new ScriptEngineManager().getEngineByName("quickjs4j");
engine.eval("1 + 2"); // 3
engine.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
.mjsfile at:target/classes/META-INF/quickjs4j/builtin_name.mjsThis 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 once
cd javy-plugin
make build
cd ..
mvn clean install
Acknowledgements
This project stands on the shoulders of giants:
Related Skills
node-connect
345.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
104.6kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
345.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
345.4kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
