Skip to content
 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

74 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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.

Whether you're embedding scripting capabilities or isolating untrusted code, QuickJs4J is designed for safe and seamless integration.

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

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 once

cd 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
  • Chicory – a native JVM WebAssembly runtime

About

Run JavaScript from Java in a safe sandbox.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages