Skip to content

Commit 55a255f

Browse files
committed
[GR-74336] Add a new guide: Working with the Web Image API.
PullRequest: graal/23644
2 parents 012b3ab + adf9b58 commit 55a255f

File tree

2 files changed

+336
-2
lines changed

2 files changed

+336
-2
lines changed

web-image/docs/get-started.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ layout: docs
33
title: Web Image
44
link_title: Web Image
55
permalink: /reference-manual/web-image/
6-
toc_group: native-image
6+
toc_group: web-image
77
---
88

99
# Getting Started with Web Image
@@ -13,6 +13,8 @@ Web Image uses the standard GraalVM Native Image toolchain and is enabled by pas
1313

1414
> Note: Web Image is an experimental technology and under active development. APIs, tooling, and capabilities may change.
1515
16+
For guidance on JavaScript interop, value conversion, and working with `JSObject`, see [Web Image API Guide](web-image-api.md).
17+
1618
## Prerequisites
1719

1820
To build Web Image applications, you need:
@@ -132,6 +134,7 @@ Browsers restrict loading Wasm modules from the local filesystem, so opening via
132134

133135
## Related Documentation
134136

137+
* For working with JavaScript in Web Image, see the [Web Image API Guide](web-image-api.md).
135138
* To learn more, see how to [compile javac into a Wasm module](https://github.com/graalvm/graalvm-demos/tree/master/native-image/wasm-javac).
136139
* Review a demo [showing builds of JVM applications (such as Spring Shell) running in JavaScript-hosted Wasm environments](https://github.com/graalvm/graalvm-demos/tree/master/native-image/wasm-spring-shell).
137-
* See the [GraalVM Web Image API](https://www.graalvm.org/sdk/javadoc/org/graalvm/webimage/api/package-summary.html).
140+
* [Web Image API](https://www.graalvm.org/sdk/javadoc/org/graalvm/webimage/api/package-summary.html).

web-image/docs/web-image-api.md

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
---
2+
layout: docs
3+
title: Web Image API Guide
4+
link_title: Web Image API Guide
5+
permalink: /reference-manual/web-image/api-guide/
6+
toc_group: web-image
7+
---
8+
9+
# Working with the Web Image API
10+
11+
This document describes the recommended approach for using the Web Image API to enable JavaScript interoperability in Java applications that are compiled into WebAssembly modules using [GraalVM Native Image Web Image backend](get-started.md), and can run in Node.js or browser environments.
12+
13+
The [Web Image API](https://www.graalvm.org/sdk/javadoc/org/graalvm/webimage/api/package-summary.html) provides a Java-to-JavaScript interoperability layer for applications compiled to WebAssembly.
14+
15+
> Note: Web Image is experimental and under active development. APIs, tooling, and capabilities may change.
16+
17+
## Table of Contents
18+
19+
* [Embedding JavaScript Code with `@JS`](#embedding-javascript-code-with-js)
20+
* [Working with Primitives](#working-with-primitives)
21+
* [Working with Objects](#working-with-objects)
22+
* [Creating JavaScript Objects in Java](#creating-javascript-objects-in-java)
23+
* [Creating Instances of JavaScript Classes](#creating-instances-of-javascript-classes)
24+
* [Functions Are Objects](#functions-are-objects)
25+
* [Exporting Java Code to JavaScript](#exporting-java-code-to-javascript)
26+
* [Passing Arguments](#passing-arguments)
27+
* [What Counts as a Callable Value?](#what-counts-as-a-callable-value)
28+
* [Error Handling](#error-handling)
29+
30+
## Embedding JavaScript Code with `@JS`
31+
32+
The central entry point is the `@JS` annotation, which allows a Java method to execute a JavaScript snippet instead of a Java method body.
33+
For example:
34+
```java
35+
import org.graalvm.webimage.api.JS;
36+
37+
public class Example {
38+
@JS("console.log(message);")
39+
static native void log(String message);
40+
}
41+
```
42+
43+
The method is typically declared `native`, because its implementation is supplied by the JavaScript snippet rather than a Java method body.
44+
45+
## Working with Primitives
46+
47+
Primitive JavaScript values are represented by wrapper classes:
48+
49+
* `JSBoolean`
50+
* `JSNumber`
51+
* `JSBigInt`
52+
* `JSString`
53+
* `JSSymbol`
54+
* `JSUndefined`
55+
56+
All of them extend `JSValue`.
57+
58+
For exchanging primitive values, there are two practical styles.
59+
60+
* The first style is to work with the explicit JavaScript wrapper types directly. In that mode, method signatures make it obvious that the values crossing the boundary are JavaScript values rather than ordinary Java values:
61+
62+
```java
63+
import org.graalvm.webimage.api.JS;
64+
import org.graalvm.webimage.api.JSNumber;
65+
66+
public class Adder {
67+
@JS("return a + b;")
68+
static native JSNumber add(JSNumber a, JSNumber b);
69+
}
70+
```
71+
72+
This style is explicit and predictable when you want to stay close to JavaScript semantics, or when you are debugging conversion behavior.
73+
74+
* The second style is to use `@JS.Coerce`, which asks Web Image to convert between Java types and JavaScript types automatically:
75+
76+
```java
77+
import org.graalvm.webimage.api.JS;
78+
79+
public class Adder {
80+
@JS.Coerce
81+
@JS("return a + b;")
82+
static native int add(int a, int b);
83+
}
84+
```
85+
86+
This is often the most convenient approach for `boolean`, the Java numeric types, `String`, and `BigInteger`.
87+
It produces signatures that look natural from Java, which makes small helper methods easier to read.
88+
If the conversion behavior is unclear, start with explicit wrapper types and add coercion later.
89+
90+
To conclude:
91+
92+
- use plain Java types together with `@JS.Coerce` for simple methods;
93+
- use `JSNumber`, `JSString`, `JSBoolean`, and the other wrapper types when you want to be explicit about JavaScript values;
94+
- use `JSValue` when the runtime type may vary and you need to inspect or convert it manually.
95+
96+
For example, if a value may be different kinds of JavaScript values depending on runtime behavior, you can accept `JSValue` and convert it explicitly:
97+
98+
```java
99+
import org.graalvm.webimage.api.JS;
100+
import org.graalvm.webimage.api.JSValue;
101+
102+
public class Example {
103+
@JS("return value;")
104+
static native JSValue identity(JSValue value);
105+
106+
static int use(JSValue value) {
107+
return value.asInt();
108+
}
109+
}
110+
```
111+
112+
If JavaScript returns a value that does not match the Java type you requested, Web Image throws `ClassCastException`.
113+
114+
The recommendation is also to use typed `get(...)` whenever possible:
115+
```java
116+
int price = request.get("price", Integer.class);
117+
```
118+
119+
Instead of `Object raw = request.get("price");`.
120+
Typed `get(...)` is clearer and gives you earlier failures if the data does not match what Java expects.
121+
122+
## Working with Objects
123+
124+
At present, **`JSObject`** is the primary way to work with JavaScript objects from Java.
125+
The current limitations are:
126+
127+
* `@JS.Import` is not implemented yet
128+
* `@JS.Export` is not implemented yet
129+
* subclasses of `JSObject` are not supported yet
130+
131+
The recommended object model today is intentionally simple: accept and return `JSObject`, manipulate properties through `get(...)` and `set(...)`, and use typed reads such as `get("name", String.class)` whenever possible.
132+
For example, this pattern reads a "flat" object together with a nested object:
133+
134+
```java
135+
import org.graalvm.webimage.api.JSObject;
136+
137+
String operation = request.get("operation", String.class);
138+
int price = request.get("price", Integer.class);
139+
140+
JSObject user = request.get("user", JSObject.class);
141+
boolean premium = user.get("premium", Boolean.class);
142+
```
143+
144+
Writing object properties is slightly different: when you want JavaScript primitive values on the JavaScript side, pass explicit `JSValue` wrappers such as `JSNumber`, `JSBoolean`, or `JSString` to `set(...)`.
145+
Start with an empty object and populate it field by field:
146+
147+
```java
148+
JSObject response = JSObject.create();
149+
response.set("finalPrice", JSNumber.of(96));
150+
response.set("discountApplied", JSNumber.of(24));
151+
response.set("premium", JSBoolean.of(true));
152+
```
153+
154+
This ensures that the stored properties are JavaScript values instead of proxied Java objects.
155+
When returned back to JavaScript, this becomes a normal JavaScript object.
156+
157+
Nested objects are handled in the same way: read the nested property as another `JSObject`, then continue reading from it.
158+
159+
```java
160+
JSObject user = request.get("user", JSObject.class);
161+
String tier = user.get("tier", String.class);
162+
```
163+
164+
## Creating JavaScript Objects in Java
165+
166+
The simplest way to create a plain JavaScript object is:
167+
168+
```java
169+
JSObject obj = JSObject.create();
170+
```
171+
172+
There are also additional `JSObject.create(...)` overloads and related helper methods such as:
173+
174+
* `JSObject.create()`
175+
* `JSObject.create(proto)`
176+
* `JSObject.create(proto, properties)`
177+
* `JSObject.defineProperty(...)`
178+
* `JSObject.defineProperties(...)`
179+
180+
These methods are useful when you want to construct objects in Java without writing a JavaScript helper.
181+
182+
Use these helpers for descriptor objects, plain record-like objects, temporary request or response objects.
183+
184+
## Creating Instances of JavaScript Classes
185+
186+
There is an important distinction between creating a plain JavaScript object and creating an instance of a specific JavaScript class.
187+
Plain object creation works well with `JSObject.create(...)`.
188+
However, if you need an actual instance of a JavaScript class, such as a browser object, or a custom constructor type, the most practical approach currently is to define a helper method:
189+
190+
```java
191+
@JS("return new Foo(arg1, arg2);")
192+
@JS.Coerce
193+
static native JSObject newFoo(int arg1, String arg2);
194+
```
195+
196+
In practice, create the instance in JavaScript and then handle it in Java as a `JSObject`.
197+
The same guidance applies to browser and framework objects.
198+
If you need something like a JavaScript `Date`, a DOM-like helper object, or an application-specific JavaScript instance, construct it in a small `@JS` helper and return `JSObject`.
199+
200+
## Functions Are Objects
201+
202+
A JavaScript function can be represented as a `JSObject`.
203+
You can call it from Java using:
204+
205+
* `invoke(args...)`
206+
* `call(thisArg, args...)`
207+
208+
For example:
209+
```java
210+
@JS("return (a, b) => a + b;")
211+
static native JSObject createFunction();
212+
213+
JSObject fn = createFunction();
214+
Object result = fn.invoke(1, 2);
215+
```
216+
217+
This is useful when JavaScript returns callbacks or factories.
218+
This is also useful when an API returns a function-valued property instead of a plain object.
219+
220+
## Exporting Java Code to JavaScript
221+
222+
Another common use of `@JS` is exposing Java code to JavaScript.
223+
The current practical pattern is to export a Java function through a small bootstrap helper written with `@JS`.
224+
225+
Example pattern:
226+
```java
227+
@JS(args = {"adder"}, value = "globalThis.adder = adder;")
228+
private static native void export(java.util.function.BiFunction<JSNumber, JSNumber, JSNumber> adder);
229+
```
230+
231+
Then in `main()`:
232+
```java
233+
export((a, b) -> JSNumber.of(a.asInt() + b.asInt()));
234+
```
235+
236+
Here, `@JS` is used to install a Java-provided function onto the JavaScript side so that browser or Node.js code can call it later.
237+
238+
This is different from the simpler `@JS` example shown earlier, where `@JS` is used to run JavaScript from Java:
239+
```java
240+
public class Example {
241+
@JS("console.log(message);")
242+
static native void log(String message);
243+
}
244+
```
245+
246+
This is also different from `@JS.Export`.
247+
Because `@JS.Export` is not implemented yet, this guide recommends the helper-based pattern instead.
248+
249+
## Passing Arguments
250+
251+
Arguments are visible inside the JavaScript body by parameter name.
252+
Parameter names are only available if they are recorded in bytecode, for example by compiling with `javac -parameters`.
253+
254+
If parameter names are not available in bytecode, or if you want to make the Java-to-JavaScript binding explicit, provide them explicitly:
255+
256+
```java
257+
@JS.Coerce
258+
@JS(args = {"x", "y"}, value = "return x + y;")
259+
static native int add(int a, int b);
260+
```
261+
262+
## What Counts as a Callable Value?
263+
264+
JavaScript needs a callable value, and the practical Java-side model is a functional interface.
265+
That callable can be a lambda, a method reference, or an implementation of a functional interface.
266+
Common choices include:
267+
268+
* `Runnable` for no arguments and no return value
269+
* `Consumer<T>` for one argument and no return value
270+
* `Function<T, R>` for one argument and a return value
271+
* `BiFunction<T, U, R>` for two arguments and a return value
272+
273+
For example, these are all reasonable callable values to export:
274+
275+
```java
276+
@JS(args = {"handler"}, value = "globalThis.pricingService = handler;")
277+
private static native void export(Function<JSObject, JSObject> handler);
278+
279+
static JSObject handleRequest(JSObject request) {
280+
JSObject response = JSObject.create();
281+
response.set("ok", JSBoolean.of(true));
282+
return response;
283+
}
284+
285+
static final class PricingHandler implements Function<JSObject, JSObject> {
286+
@Override
287+
public JSObject apply(JSObject request) {
288+
return handleRequest(request);
289+
}
290+
}
291+
292+
export(request -> handleRequest(request));
293+
export(Example::handleRequest);
294+
export(new PricingHandler());
295+
```
296+
297+
For structured request/response exchange, `Function<JSObject, JSObject>` is often the most convenient shape: JavaScript passes a plain object, Java reads it with typed `get(...)` calls, and returns a fresh `JSObject`.
298+
299+
## Error Handling
300+
301+
When JavaScript throws a non-Java exception value, it may be surfaced as `ThrownFromJavaScript`.
302+
That gives Java code access to the thrown object:
303+
304+
```java
305+
try {
306+
...
307+
} catch (ThrownFromJavaScript ex) {
308+
Object thrown = ex.getThrownObject();
309+
}
310+
```
311+
312+
This is useful for APIs called directly from browser JavaScript.
313+
A returned error object is often easier to inspect in the console or UI than a Java-side exception crossing the boundary.
314+
315+
### Summary
316+
317+
For the current state of the Web Image API, prefer the following style:
318+
319+
* use `@JS` for small JavaScript glue snippets
320+
* add `@JS.Coerce` when you want a Java-friendly signature
321+
* use `JSObject` for all structured objects exchange
322+
* use `get(...)` and `set(...)` instead of `JSObject` subclasses
323+
* use helper methods that construct JavaScript class instances explicitly with `new`
324+
325+
Until `@JS.Import`, `@JS.Export`, and typed `JSObject` subclass support are fully implemented, the safest approach is to treat JavaScript objects as `JSObject` values and manipulate them through `get(...)`, `set(...)`, and small helper methods.
326+
327+
### Related Documentation
328+
329+
* [Web Image: Export Java Method with JavaScript Objects](https://github.com/graalvm/graalvm-demos/tree/master/web-image/js-java-object-exchange)
330+
* [Web Image: Export Java Method Example](https://github.com/graalvm/graalvm-demos/tree/master/web-image/export-java-function)
331+
* [Web Image API](https://www.graalvm.org/sdk/javadoc/org/graalvm/webimage/api/package-summary.html)

0 commit comments

Comments
 (0)