Skip to content

andreaTP/go4j

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

46 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Go4J

Go4J lets you run TinyGo‑compiled WebAssembly and interoperate with Java on the JVM. It uses the Chicory runtime and provides both a low‑level API and an annotation‑driven, high‑level, type‑safe binding layer.

Experimental: This project is experimental and we are looking for feedback on the design and implementation. Try it out and let us know what you think!

Why Go4J?

  • Interop made easy: pass primitives and opaque Java object references between Go and Java.
  • Pure Java: no native deps; works anywhere the JVM runs.
  • WASI support: run TinyGo WASI modules with configurable stdio/args/fs/env.

Project setup (Maven)

Add dependencies and enable annotation processing:

<dependencies>
  <dependency>
    <groupId>io.roastedroot</groupId>
    <artifactId>go4j-core</artifactId>
  </dependency>
  <dependency>
    <groupId>io.roastedroot</groupId>
    <artifactId>go4j-annotations</artifactId>
    <scope>provided</scope>
  </dependency>
</dependencies>

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <configuration>
        <annotationProcessorPaths>
          <path>
            <groupId>io.roastedroot</groupId>
            <artifactId>go4j-processor</artifactId>
          </path>
        </annotationProcessorPaths>
      </configuration>
    </plugin>
  </plugins>
</build>

Quick start

Run a TinyGo WASI module and capture stdout:

import com.dylibso.chicory.wasm.Parser;
import com.dylibso.chicory.wasi.WasiOptions;
import io.roastedroot.go4j.Go;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;

var wasm = MyApp.class.getResourceAsStream("main.wasm");
var module = Parser.parse(wasm);
var stdout = new ByteArrayOutputStream();
var wasi = WasiOptions.builder().withStdout(stdout).build();

var go = Go.builder(module).withWasi(wasi).build();
int exitCode = go.run();
assert exitCode == 0;
System.out.println(stdout.toString(StandardCharsets.UTF_8)); // Hello world!\n

Corresponding TinyGo program:

package main

import "fmt"

func main() {
    fmt.Println("Hello world!")
}

Calling Go from Java

Define an interface for Go exports and call it from Java:

import com.dylibso.chicory.wasm.Parser;
import io.roastedroot.go4j.Go;
import io.roastedroot.go4j.annotations.*;
import java.nio.file.Path;

@Invokables
interface MathApi {
  @GuestFunction("add")
  int add(int a, int b);
}

var module = Parser.parse(Path.of("add.wasm"));
var go = Go.builder(module).withWasi().build();
var math = MathApi_Invokables.create(go);

int result = math.add(3, 11);
System.out.println(result); // 14

Corresponding TinyGo program:

package main

//export add
func add(a int32, b int32) int32 { return a + b }

func main() {}

Calling Java from Go

Expose Java methods to Go via annotations, then execute Go code that calls them:

import com.dylibso.chicory.wasm.Parser;
import io.roastedroot.go4j.Go;
import io.roastedroot.go4j.annotations.*;
import java.nio.file.Path;

@Builtins("from_java")
class JavaApi {
  @ReturnsHostRef
  @HostFunction("my_java_func")
  public String add(int x, int y) { return "hello " + (x + y); }

  @HostFunction("my_java_check")
  public void check(@HostRefParam String value) { assert ("hello 42".equals(value)); }
}

@Invokables
interface GoApi {
  @GuestFunction void test();
}

var module = Parser.parse(Path.of("hello-java.wasm"));
var go = Go.builder(module)
    .withWasi()
    .withAdditionalImport(JavaApi_Builtins.toAdditionalImports(new JavaApi()))
    .build();
var goApi = GoApi_Invokables.create(go);

goApi.test();

Corresponding TinyGo snippet (imports Java host functions and calls them):

package main

//go:wasmimport from_java my_java_func
func my_java_func(x int32, y int32) int32

//go:wasmimport from_java my_java_check
func my_java_check(ref int32)

//export test
func test() {
    ref := my_java_func(40, 2)
    my_java_check(ref)
}

func main() {}

Notes:

  • @Invokables generates a type-safe client to call Go exports.
  • @Builtins + @HostFunction generate WASM imports mapped to Java methods.
  • @HostRefParam / @ReturnsHostRef pass opaque Java objects by reference (no serialization) across the boundary.

Working with JavaRefs in Go

Use the Go dependency for convenient JavaRef handling:

go get github.com/roastedroot/go4j

Example Go code using the JavaRef API:

package main

import "github.com/roastedroot/go4j"

//export processString
func processString(strRef go4j.JavaRef) bool {
    // Convert JavaRef to Go string
    str := strRef.AsString()

    // Create new JavaRef with Go string
    newRef := go4j.Alloc().Set().String(str)

    // Clean up
    newRef.Free()

    // Return boolean result
    return len(str) > 0
}

func main() {}

Compile Go

Compile your Go code with TinyGo targeting WASI (examples under core/src/test/resources/wasm):

tinygo build -o your.wasm -target=wasip1 ./path/to/main.go

Or with the standard Go compiler:

GOOS=wasip1 GOARCH=wasm go build -o your.wasm ./path/to/main.go

Basic support is also available for wasm-unknown-unknown:

tinygo build -o your.wasm -target=wasm-unknown ./path/to/main.go

Ensure your Go module exports functions matching your @GuestFunction signatures and imports host functions under the module name used in @Builtins.

Acknowledgements

  • TinyGo – Go compiler for tiny places
  • Chicory – Java WebAssembly runtime

About

POC high level bindings to execute Go in pure Java

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •