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 withName
andAge
fields. - The
GetPersonJSON
function is exported to return the struct serialized as a JSON string using Go’sjson.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 anative
method to call the Go function. - The
main
method callsGetPersonJSON
, receives the JSON string, and parses it using theorg.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.