Asynchronous polyglot programming with Java and JavaScript on GraalVM

Daniele Bonetta
graalvm
Published in
5 min readJun 24, 2020

One of the many cool features of GraalVM is its support for polyglot applications. The ability to mix and match programming styles and paradigms makes it easy to combine different programming models allowing developers to take advantage of different languages in a single application. Since GraalVM was open sourced, many platforms and frameworks have started embedding GraalVM language runtimes to provide scripting capabilities through dynamic languages such as JavaScript or Python, for example Vert.x Es4x, as well as some experimental builds of Oracle Database.

Mixing and combining different programming languages often means mixing and combining different programming styles and paradigms. This is evident in applications that mix programming languages with dissimilar models of concurrency and parallel execution. In this blog post we focus on the combination of Java and JavaScript in the context of asynchronous applications. Both languages have built-in support for asynchronous execution, but subtle differences in the way non-blocking execution can be orchestrated make it challenging to write applications that combine the two languages.

Hello JavaScript, this is Java

Asynchronous code execution is a first-class citizen in JavaScript. Built-in language constructs such as async/awaitas well as Promise objects make it natural for JavaScript developers to deal with asynchronous control flow. When it comes to polyglot programming, GraalVM allows JavaScript developers to interact with Java objects using the same, familiar, built-in language support.

Any Java object can be exposed to GraalVM JavaScript applications by implementing a method called then, with the following signature:

In JavaScript, Java objects implementing the then method can be used as executor function for JavaScript Promise objects. In this way, any JavaScript application can use Java to resolve (or reject) an associated JavaScript promise. As an example, consider the following JavaScript code:

where interopPromise is a Java object instance of the following class:

At runtime, GraalVM will delegate to the Java object the resolution of the JavaScript promise. In this example, this means that the JavaScript promise will either resolve or reject the interopPromise object depending on the result of the Java computeSomething() method call.

Promise objects created using a Java executor function are effectively JavaScript object instances. Hence, they can be used like any other Promise object. A nice consequence of this property is that they can be used from an async function:

Hello Java, this is JavaScript!

All JavaScript objects can be used from Java applications using the GraalVM polyglot API, including Promise object instances. Therefore, it is possible for Java applications to interact with such objects by means of the Promise API. As an example, the following code snippet demonstrates how Java lambda functions can be used as JavaScript promise reactions:

When executed, the code in the example will call the asyncFunction JavaScript function, which will then resolve a Promise object with resolution value 42. As a result of the promise resolution mechanism, a Java Consumer lambda function will be invoked and will print "JavaScript resolves: ..." to the standard output.

Combining Asynchronous and Parallel Execution in a Polyglot HTTP Web Service: As we have discussed in a previous blog post, GraalVM JavaScript relies on a relaxed share-nothing parallel programming model. According to the model, JavaScript objects cannot be used by two threads at the same time, but parallel threads using a dedicated polyglot Context are allowed. Plus, Java objects can be shared across contexts, and a single context can be used from different threads when proper synchronization is used. When it comes to asynchronous programming the same rules apply making it possible to implement asynchronous Java-to-JavaScript applications that benefit from multi-threaded execution.

Let’s look at an example: a polyglot Helidon Web application. The following figure provides an high-level overview of the main polyglot interactions in the example application we’ll use for this demo:

Diagram of interactions between Java and JavaScript in the demo application
Diagram of interactions between Java and JavaScript in the demo application

In a nutshell, the application performs the following operations, combining Java and JavaScript execution:
#1. Requests handling (Java): an HTTP Helidon Web server handles an incoming request. It parses the request URL and extracts any useful information such as a request ID. At this point, Helidon might already be using multiple parallel threads to handle concurrent requests.
#2. Request validation (JS): a JavaScript async function is used to validate the input request ID. If the value is accepted, the JavaScript function suspends its execution (using await), delegating to another Java thread some heavy computation.
#3.Request processing (Java): In another thread, a Java method performs some CPU-intensive computation. Once done, it resumes the async JavaScript function of #2 by resolving a JavaScript Promise.
#4. Response creation (JS): Once resumed, the JavaScript async function can use JSON.stringify() to create a response object that contains data computed in Java.
#5. Client response (Java): finally, the JavaScriptasync function returns. The execution flow goes back to Java, where a CompletableFuture generates the client response using the JSON data created in JavaScript.

To see how each step is implemented, you can take a look at the full example which can be found on our GraalVM examples GitHub repository. The following are two (simplified) code snippets from the example, showing how JavaScript promises are mapped to Java CompletableFuture:

This simple demo application demonstrates a simple (yet effective) way to use multi-threading and asynchronous programming combining Java and JavaScript; and illustrates how powerful polyglot programming can be. A request handled from Java can be seamlessly delegated to JavaScript, which itself can offload computations to another Java thread, while using familiar concepts such as async/ await to wait for asynchronous computations being performed in parallel.

As we discussed throughout this blog post, GraalVM’s polyglot capabilities enable asynchronous programming across multiple languages and paradigms. One possible domain where this can be useful is web applications, where there are many great frameworks written in different languages. Of course, many other domains might well benefit from polyglot programming, and we can’t wait to see how many cool polyglot applications will integrate GraalVM JavaScript in the future!

You can try the example application yourself: download GraalVM and get the demo application from GitHub.

--

--

Daniele Bonetta
graalvm
Writer for

Researcher at Oracle Labs in the VM research group and member of the GraalVM project.