Core Java

Invoke a GoLang Function from Java

Go, often referred to as GoLang, is a statically typed, compiled programming language designed by Google. Known for its simplicity, concurrency support, and performance, Go is widely used in backend systems, cloud-native applications, and microservices. With its robust standard library and features like goroutines and channels, Go excels in writing scalable, efficient programs. Many developers use it to complement other languages like Java in polyglot systems. In this article we will discuss how to invoke a GoLang function from Java.

1. Dependencies

To invoke a Go function from Java, you’ll need the following dependencies:

  • Java Development Kit (JDK) installed (version 11 or higher is recommended).
  • Go compiler installed and configured on your system.
  • Java Native Interface (JNI): For integrating native libraries with Java.
  • cgo: A Go tool for creating C-compatible binaries.
  • javac and java: Java compiler and runtime environment.

2. Invoking Go Function from Java

The process involves writing a Go function, compiling it into a shared library, and using JNI to invoke it from Java.

2.1 Writing the Go Function

Here’s a Go function that takes two integers as input and returns their sum:

package main

import "C"

//export AddNumbers
func AddNumbers(a, b int) int {
    // This function receives two integers as input,
    // computes their sum, and returns the result.
    return a + b
}

// The main function is required for building the shared library,
// even though it does nothing in this case.
func main() {}

2.1.1 Code Explanation

The package main declaration defines the entry point for the Go program. This is mandatory for any Go program that needs to be compiled as an executable or shared library. In this context, it is used to compile the shared library that contains the exported function.

The import "C" statement enables interoperability between Go and C. It allows the Go program to use C syntax and conventions, which is critical for creating functions that can be exported as C-compatible symbols.

The //export AddNumbers directive tells the Go compiler to make the AddNumbers function available for external use as a C-compatible function. This is necessary for the function to be callable from Java or other languages that can interface with shared libraries.

The AddNumbers function itself takes two integer parameters, a and b, computes their sum, and returns the result. This is a simple example of a computational function that showcases how parameters can be passed and values returned.

The func main() function is required for building the shared library, even though it does not perform any operations in this case. Without a main function, the Go compiler will not create an executable or shared library. Here, it acts as a placeholder to fulfill this requirement.

2.2 Compiling Go Code into a Shared Library

Compile the Go code into a shared library using the following command:

go build -o libadd.so -buildmode=c-shared main.go

The -buildmode=c-shared flag tells the Go compiler to create a shared library (.so file) that can be loaded by other programs. The output file, libadd.so, contains the compiled Go code.

2.3 Writing the Java Code

Create a Java program to load the shared library and invoke the Go function:

public class GoInvoker {
    static {
        // The System.loadLibrary method loads the shared library created by Go.
        // Ensure the library is in your system's library path or specify its full path.
        System.loadLibrary("add"); // Load the shared library
    }

    // Declare a native method corresponding to the Go function.
    // The method signature must match the Go function's parameters and return type.
    public native int AddNumbers(int a, int b);

    public static void main(String[] args) {
        GoInvoker invoker = new GoInvoker();
        // Call the native method and pass two integers.
        int result = invoker.AddNumbers(10, 20);
        // Print the result received from the Go function.
        System.out.println("Result from Go Function: " + result);
    }
}

2.3.1 Code Explanation

The class GoInvoker contains the logic to load the shared library and interact with the Go function. The static block (static { ... }) is executed when the class is first loaded by the JVM. Inside this block, the System.loadLibrary method is called to load the shared library named add. This library is generated from the Go code and must be available in the system’s library path, or its full path must be provided.

The native keyword is used to declare a method that is implemented in native code (in this case, Go). The method AddNumbers is defined to accept two integers as parameters and return their sum as an integer. The method signature must exactly match the Go function’s parameter types and return type for successful integration.

In the main method, an instance of the GoInvoker class is created. The AddNumbers method is then called on this instance, passing the integers 10 and 20 as arguments. This call bridges the gap between Java and Go, invoking the Go function exported in the shared library.

The result returned by the Go function is stored in the variable result. Finally, the program prints the result to the console using System.out.println. This demonstrates the successful communication between the Java program and the Go function via the shared library.

2.4 Compiling and Running the Java code

Compile the Java program using the following command:

javac GoInvoker.java

Run the Java program using:

java GoInvoker

Ensure that the shared library is in the library path (e.g., the current directory or a directory specified in LD_LIBRARY_PATH on Linux or PATH on Windows). The output of the program will be:

Result from Go Function: 30

3. Handling Complex Data Types

To handle more complex data types, such as structs, you can use C-style structures in Go and map them using JNI in Java. This approach requires serialization and deserialization between Go and Java. Let’s define a Person struct, serialize it into JSON format, and expose a function that returns this JSON string.

3.1 Exporting Structs as JSON

package main

import (
    "C"
    "encoding/json"
    "fmt"
)

// Define a struct in Go
type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

// Export a function to convert the struct to a JSON string
//export GetPersonJSON
func GetPersonJSON() *C.char {
    person := Person{Name: "John Doe", Age: 30}
    jsonData, err := json.Marshal(person)
    if err != nil {
        fmt.Println("Error marshaling data:", err)
        return nil
    }
    return C.CString(string(jsonData))
}

// The main function is required for building the shared library
func main() {}

In this Go code:

  • We define a Person struct with Name and Age fields.
  • The GetPersonJSON function is exported to return the struct serialized as a JSON string using Go’s json.Marshal function.
  • The main function is empty but required for compiling the Go program into a shared library.

The exported Go function GetPersonJSON will be called from Java to get the JSON representation of the Person struct.

3.2 Handling JSON and Invoking Go Function

In Java, we use JNI to load the shared library created from the Go code and invoke the function that returns the JSON string. Once we receive the JSON string in Java, we can parse it using a JSON library like org.json.

public class GoInvoker {
    static {
        // Load the Go shared library
        System.loadLibrary("add"); // Make sure to adjust the library name as needed
    }

    // Native method to call the Go function and receive JSON string
    public native String GetPersonJSON();

    public static void main(String[] args) {
        GoInvoker invoker = new GoInvoker();
        // Call the Go function that returns a JSON string
        String jsonResult = invoker.GetPersonJSON();
        
        // Parse the JSON string in Java
        try {
            // Use the org.json library or another JSON parsing library
            org.json.JSONObject personObject = new org.json.JSONObject(jsonResult);
            String name = personObject.getString("name");
            int age = personObject.getInt("age");

            // Print the parsed result
            System.out.println("Name: " + name);
            System.out.println("Age: " + age);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

In this Java code:

  • The System.loadLibrary method loads the shared library produced by the Go code.
  • The GetPersonJSON method is declared as a native method to call the Go function.
  • The main method calls GetPersonJSON, receives the JSON string, and parses it using the org.json library.

The parsed data is then printed to the console, showcasing how complex data types can be passed between Go and Java.

3.3 Compiling and Running the code

To compile and run this example, follow these steps:

  • First, compile the Go code into a shared library using the following command:
    go build -o libadd.so -buildmode=c-shared main.go
  • Next, compile the Java code:
    javac GoInvoker.java
  • Finally, run the Java program:
    java GoInvoker

After running the Java program, the output of the program will be:

Name: John Doe
Age: 30

This confirms that the complex data (the Go struct) was correctly serialized to JSON in Go, passed to Java, and parsed successfully in Java.

4. Conclusion

Integrating Go and Java through shared libraries allows developers to harness the strengths of both languages. Go’s performance and simplicity combined with Java’s versatility provide a robust solution for modern applications.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button