{"id":51142781,"url":"https://github.com/faceless2/quickjs-java","last_synced_at":"2026-06-26T00:30:56.706Z","repository":{"id":367066339,"uuid":"1277997345","full_name":"faceless2/quickjs-java","owner":"faceless2","description":"An experimental rebuild of quickjs-wasm-java","archived":false,"fork":false,"pushed_at":"2026-06-24T11:06:24.000Z","size":71,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-24T13:08:12.643Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/faceless2.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-23T11:20:30.000Z","updated_at":"2026-06-24T11:06:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/faceless2/quickjs-java","commit_stats":null,"previous_names":["faceless2/quickjs-java"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/faceless2/quickjs-java","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faceless2%2Fquickjs-java","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faceless2%2Fquickjs-java/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faceless2%2Fquickjs-java/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faceless2%2Fquickjs-java/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/faceless2","download_url":"https://codeload.github.com/faceless2/quickjs-java/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faceless2%2Fquickjs-java/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34798182,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-25T02:00:05.521Z","response_time":101,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-26T00:30:56.096Z","updated_at":"2026-06-26T00:30:56.697Z","avatar_url":"https://github.com/faceless2.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# quickjs for Java\n\n**QuickJS Java** is a repackaging of the [quickjs-wasm-java](https://github.com/StefanRichterHuber/quickjs-wasm-java) package\nby Stefan Richter-Huber, so I can take it in a slightly different direction.\n\n## Why?\nThe use case is running small scripts from untrusted sources that are completely sandboxed. Graal isn't an option for two main\nreasons:\n1. it's designed to integrated tightly with Java code - resource limits and isolation have been added on later, with Isolated Contexts.\n2. no control over event loop: the JavaScript will pause until the script completes, effectively hanging the thread.\n   With 1000 tasks that's 1000 hung threads, and Graal's isolated contexts don't (currently; 2026) work with virtual threads.\n\nThis project takes a different approach. The [QuickJS engine](https://bellard.org/quickjs/) (in C) is linked with [rquickjs](https://github.com/DelSkayn/rquickjs) (in Rust)\nand compiled to Web-Assembly, which is run in the JVM using [Chicory](https://chicory.dev/), a pure-Java WebAssembly runtime without native dependencies.\nChicory converts the Web-Assembly directly into Java class files which can be called from Java.\n\nThere are a few projects taking this approach\n* [quickjs-wasm-java](https://github.com/StefanRichterHuber/quickjs-wasm-java), which is the basis for this API. Almost all of the Rust source in this repository was taken directly from that project.\n* [quickjs4j](https://github.com/roastedroot/quickjs4j), which uses [Javy](https://github.com/bytecodealliance/javy)\n\nThe advantage is that rather than one-thread-per task, a single thread can be used to interleave operations on multiple contexts - much like `select(2)` in C, in fact.\n\n## Building\n* Requirements: Java 21+ and Rust\n* There is `pom.xml` for Maven, which is lifted entirely from Stefan Richter-Huber's `quickjs-wasm-java`\n* Alternatively if you find Maven slow, verbose, opaque and you want to manage your dependency supply-chain instead of outsourcing the process to the internet? There are some simple shell scripts to build the components:\n  * `download-libs.sh` to download the Jars\n  * `build-wasm.sh` to compile the rust to WebAssembly, and compile the WebAssembly to Java classes with Chicory\n  * `build-jars.sh` to compile the Java code and bundle it into `target/quickjs-${VERSION}.jar`\n  * `test.sh` to compille the Java tests, bundle them into `target/quickjs-test-${VERSION}.jar` and run them.\n\n## Examples\n### Getting Started\n\nSimple things are simple. Use a _runtime_ to create one or more _contexts_.\nSimple objects (numbers, strings, booleans) map to Java types. Maps become\n`com.github.quickjs.JSObject` (which extends Map)`, Arrays become\n`com.github.quickjs.JSArray` (which extends List)`.\n```java\nimport com.bfo.quickjs.*;\n\n// Simple examples\nJSRuntime js = new JSRuntime();\nJSContext ctx = js.newContext();\nObject o = ctx.eval(\"var x = 1 + 2; x\");    // o = 3;\nJSObject m = (JSObject)ctx.eval(\"var x = {\\\"foo\\\":1 };  // implements Map\nm.put(\"bar\", 3);\no = ctx.eval(\"x.bar\");    // o = 3;\no = ctx.eval(\"throw new Error('fail')\");  // o is a JSException\n```\n\n### Dynamic Properties\nDynamic properties can be created with the `JSComputedValue` interface.\n* Implement `Object get(JSObject owner, String key)` to implement the required getter\n* Override `void set(JSObject owner, String key, Object value)` to implement the optional setter.\n* Optionally, override `boolean isHidden()` and `boolean isDeletable()` to determine if the property is listed in the object keyset, or if it can be deleted\n\n`key` is the property name and `owner` is the `JSObject` the property is set on (***note*** don't rely on this value yet, see https://github.com/faceless2/quickjs-java/issues/2)\n```java\nJSRuntime runtime = new JSRuntime();\nJSContext ctx = runtime.newContext();\nfinal JSObject map = ctx.newObject();\nmap.put(\"name\", new JSComputedValue() {\n  public Object get(JSObject owner, String key) {\n    return map.get(\"first\") + \" \" + map.get(\"last\");\n  }\n  public void set(JSObject owner, String key, Object value) {\n    String name = (String)value;\n    int ix = name.indexOf(\" \");\n    if (ix \u003e 0) {\n        map.put(\"first\", name.substring(0, ix));\n        map.put(\"last\", name.substring(ix + 1));\n    }\n  }\n});\nctx.put(\"person\", map);\nObject o = ctx.eval(\"person.first = 'John'; person.last = 'Smith'; person.name\");   // = \"John Smith\"\no = ctx.eval(\"person.name = 'Jim Jones'; person.first\");                            //= \"Jim\"\n```\n### Annotations for simpler exporting to JavaScript\nThe `@JSExport` annotation can be set on a class, which marks it as containing\nfields and methods (also annotated) to be exported via a proxy `JSObject`.\n* Fields annotated with `@JSExport` must be public and may be final.\n* Methods annotated with `@JSExport(field=\"name\")` become getters or setters for a property of that name, depending on the method signature\n* Methods annotated with a simple `@JSExport` become JS functions. Conversion for simple types is automatic, and _varargs_ can be used for variable length methods.\n\n```java\n@JSExport\npublic static class Person\n{\n  @JSExport public String first;\n  @JSExport public String last;\n  @JSExport(hidden=true) public final int secret; // won't be enumerated, can't be changed.\n\n\n  @JSExport(field=\"name\")\n  public String getName() {   // a getter returns non-void and takes no arguments\n    return first + \" \" + last;\n  }\n\n  @JSExport(field=\"name\")\n  public void setName(String name) {  // a setter returns void and takes one argument\n    int ix = name.indexOf(\" \");\n    if (ix \u003e 0) {\n      first = name.substring(0, ix);\n      last = name.substring(ix + 1);\n    }\n  }\n\n  @JSExport\n  public void greet(String intro, int age) {\n    System.out.println(intro + \", \" + getName() + \" you are \" + age);\n  }\n\n  @JSExport\n  public Object min(int... values) {\n    Arrays.sort(values);\n    return values[0];\n  }\n}\n\nJSRuntime runtime = new JSRuntime();\nJSContext ctx = runtime.newContext();\nctx.put(\"person\", new Person());\nctx.eval(\"person.first = 'John'; person.last = 'Smith'; person.name\");  // = \"John Smith\"\nctx.eval(\"person.name = 'Jim Jones'; person.first\");                    // = \"Jim\"\nctx.eval(\"person.greet(\\\"Hello\\\", 12)\");                                // prints \"Hello, Jim Jones you are 12\"\nctx.eval(\"person.min(5,4,9,1,3)\");                                      // = 1\n```\n\n### Async\n\nAsynchronous code is supported. Each JS promise is mirrored by a Java [CompleteableFuture](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/CompletableFuture.html), and completing one complete the other.\nPromises can be returned directly, or expressions can be wrapped in a promise by calling `evalAsync()`\n\nJavaScript is single threaded so the `poll()` method must be repeatedly called on the primary thread to check for queued events. Java promises may be completed on any thread, and their state will be reflected in JavaScript on the next call to `poll()`\n\n```java\nJSRuntime js = new JSRuntime();\nJSContext ctx = js.newContext();\n\n// Create a function \"log\" which prints output. There's no console object!\nctx.put(\"log\", new Consumer\u003cObject\u003e() {\n  @Override public void accept(Object o) {\n    System.out.println(o); \n  }\n});\n\n// Create a function \"delay\" which eventually resolves on a background thread.\ncontext.put(\"delay\", new Supplier\u003cObject\u003e() {\n  public Object get() {\n    final CompletableFuture\u003cObject\u003e future = new CompletableFuture\u003c\u003e();\n    new Thread() {\n      public void run() {\n        try { Thread.sleep(500); } catch (Exception e) {}\n        future.complete(\"done\");\n      }\n    }.start();\n    return future;\n  }\n});\n\nCompletableFuture\u003c?\u003e future;\nfuture = (CompletableFuture\u003c?\u003e)ctx.eval(\"delay().then(x =\u003e 'all ' + x).then(x =\u003e log(x))\");\nwhile (!future.isDone()) {\n  Thread.sleep(200);\n  ctx.poll();\n}\n// Loop completes after 500ms with \"all done\" printed to System.out\n\nfuture = ctx.evalAsync(\"await new Promise((resolve) =\u003e { delay().then(x =\u003e 'all ' + x).then(resolve) })\");\nwhile (!future.isDone()) {\n  Thread.sleep(200);\n  ctx.poll();\n}\nSystem.out.println(future.get());   // Same result as above.\n\n// Errors propagate up\nfuture = (CompletableFuture\u003c?\u003e)ctx.eval(\"delay().then(x =\u003e 'all ' + x).then(() =\u003e { throw new Error('fail') })\");\nwhile (!future.isDone()) {\n  Thread.sleep(200);\n  ctx.poll();\n}\nSystem.out.println(future.get());   // Exception is thrown \"Promise Rejected\"\n\nfuture = ctx.evalAsync(\"await new Promise((resolve) =\u003e { delay().then(x =\u003e 'all ' + x).then(() =\u003e { throw new Error('fail'); }) })\");\nwhile (!future.isDone()) {\n  ctx.poll();\n  Thread.sleep(200);\n}\nSystem.out.println(future.get());  // Exception is thrown \"fail\"\n```\n\n### Plumbing\n\nBy default the logging is done using `System.Logger(\"com.bfo.quickjs\")`\nbut you can change this easily by passing a `JSRuntime.Logger` to `setLogger()`.\nThis is a trivial two-method interface for your logging process of choice:\n* `boolean isLoggable(int level)`\n* `void log(int level, String message, Object... args)`\n\nwhere\n* `level` is ERROR=1, WARN=2, INFO=3, DEBUG=4, TRACE=5\n* \"{}\" in `message` will be substituted with the value from `args`. A Throwable may be added as the final arg.\n\nThere is no console object for JavaScript to read/write to, but the Web-Assembly\nlayer may still log to stderr. Streams for these can be set with `setStdin(InputStream)`,\n`setStdout(OutputStream)` and `setStdErr(OutputStream)`\n\nA runtime limit can be set with `setRuntimeLimit(long ms)` and a memory limit with \n`setMemoryLimit(int bytes)`.\n\n```java\nJSRuntime js = new JSRuntime();\njs.setStdout(System.out).setStderr(System.err);\njs.setRuntimeLimit(1000).setMemoryLimit(65535);\njs.setLogger(JSRuntime.Logger.toStream(JSRuntime.Logger.INFO, System.out));\nJSContext ctx = js.newContext();\n/// etc\n```\n\n### Gotchas\n\n```java\nJSRuntime js = new JSRuntime();\nJSContext ctx = js.newContext();\nctx.eval(\"let x = {'foo: 1}\");\nJSObject o1 = (JSObject)ctx.get(\"x\");\nassert o1 != null;  // No! X is set in the lexical context, but not on this.\nctx.eval(\"globalThis.x = {'foo: 1}\");   // But either of these will work\nctx.eval(\"var x = {'foo: 1}\");\n\nJSObject o2 = (JSObject)ctx.eval(\"var x = {'foo: 1}; x\");\nJSObject o3 = (JSObject)ctx.eval(\"x\");\nassert o1 == o2;  // No! Different object each time. See https://github.com/faceless2/quickjs-java/issues/2\n\nctx.put(\"myobject\", o3);\nasssert ctx.get(\"myobject\") == o3;  // Still no! Same problem.\n\n// JavaScript is complicated! This seems to be by design. Be careful.\ncctx.eval(\"var x = 1; globalThis.y = 2; let z = 3\");\nassert ctx.containsKey(\"x\");\nassert ctx.containsKey(\"y\");\nassert !ctx.containsKey(\"z\");\nctx.remove(\"x\");\nctx.remove(\"y\");\nassert !ctx.containsKey(\"x\");\nassert !ctx.containsKey(\"y\");\nassert \"number\".equals(ctx.eval(\"typeof x\"));\nassert \"undefined\".equals(ctx.eval(\"typeof y\"));\nassert \"number\".equals(ctx.eval(\"typeof z\"));\n```\nFixes here require changes at the Rust layer which are beyond my abililty - assistance welcome.\n\nMy advise is that if an object is created in Java, **manage it in Java**: keep a reference to it and use that\nreference to interact with it. Exporting it to JavaScript will export a proxy for the same object, and\nreimporting back into Java will give you a proxy for the proxy.\n\n\n### Credits\nVery heavily based on `quickjs-wasm-java`, as mentioned. That project makes use of FunctionalInterfaces, which I found made simple things easier and harder things (eg methods with variable parameter lists) impossible. This difference and the original's dependency on `log4j` was the original reason for the rewrite and repackaging, however the Rust layer is largely unchanged.\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaceless2%2Fquickjs-java","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffaceless2%2Fquickjs-java","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaceless2%2Fquickjs-java/lists"}