Call Java code from Ballerina

Ballerina offers a straightforward way to call the existing Java code from Ballerina.

Although Ballerina is not designed to be a JVM language, the current implementation, which targets the JVM, aka jBallerina, provides Java interoperability by adhering to the Ballerina language semantics.

Ballerina bindings to Java code

You can write Ballerina code (Ballerina bindings) that lets you call the corresponding Java API as illustrated in the diagram below.

Ballerina bindings to Java code

Ballerina FFI

The Ballerina FFI explains how to write the Ballerina bindings manually. Learning to write them manually helps you to understand the inner workings of calling Java from Ballerina and how to customize the bindings generated by the Bindgen tool.

The Bindgen tool

Alternatively, you can use the Bindgen tool to generate these bindings automatically while eliminating the need for understanding the Ballerina FFI layer. It is a CLI tool that eases the process of generating Ballerina bindings for given Java APIs. For more details on how the Bindgen tool works, see The Bindgen tool. It also explains how to customize the bindings generated by the Bindgen tool.

Write Ballerina bindings

The Package layout explains how to package Java libraries (JAR files) with Ballerina programs to produce self-contained executable programs. When you generate bindings for a Java library using the Bindgen tool, this part is already handled.

Use the SnakeYAML Java library in Ballerina

The below sections explain how to use the Bindgen tool to generate Ballerina bindings for Java classes and how to use them.

SnakeYAML is a YAML parser for Java. This guide describes how to use this library to parse a YAML document using the Bindgen tool. You develop a Ballerina program that parses the given YAML file and writes the content to the standard output.

Step 1 - Write the Java code

It is recommended to always start by writing the Java code. It gives you an idea of the set of Java classes required to implement your logic. Then, you can use the Bindgen tool to generate Ballerina bindings for those classes.

The Java code below uses the SnakeYAML API to parse the given YAML file.

Note: This is not the most idiomatic way of writing the Java code for this scenario.

Copy
import org.yaml.snakeyaml.Yaml;

import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Map;

public class SnakeYamlSample {

    public static void main(String... a) {
	    String filename = a[0];
        try (InputStream inputStream = new FileInputStream(filename)) {
            Yaml yaml = new Yaml();
            Map<String, Object> obj = yaml.load(inputStream);
            System.out.println(obj);
        } catch (Exception e) {
            System.err.println("The file '" + filename + "' cannot be loaded. Reason: " + e.getMessage());;
        }
    }
}

Here, you have used four Java classes:

  • org.yaml.snakeyaml.Yaml
  • java.io.FileInputStream
  • java.io.InputStream
  • java.util.Map

You can see them in the imported class list.

Tip: You are encouraged to generate Ballerina bindings for these four classes as a start.

Now, you create an environment for your Ballerina program.

Step 2 - Set up the Ballerina package

Info: This section assumes that you have already read Package layout.

Create a Ballerina package

Copy
$ bal new yaml_package
Created new Ballerina package 'yaml_package' at yaml_package.

Verify the package

Copy
$ cd yaml_package
$ bal build
Compiling source
	sameera/yaml_package:0.1.0

Generating executable
	target/bin/yaml_package.jar
Copy
$ bal run target/bin/yaml_package.jar
Hello World!

Great! You are all set for the next step.

Add a sample YAML file

Copy the content below to a file named invoice.yml in the package root directory.

Copy
invoice: 34843
date   : 2001-01-23
bill-to: &id001
   given  : Chris
   family : Dumars
   address:
       lines: |
           458 Walkman Dr.
           Suite #292
       city    : Royal Oak
       state   : MI
       postal  : 48046
ship-to: *id001
product:
   - sku         : BL394D
     quantity    : 4
     description : Basketball
     price       : 450.00
   - sku         : BL4438H
     quantity    :
     description : Super Hoop
     price       : 2392.00
tax  : 251.42
total: 4443.52
comments: >
   Late afternoon is best.
   Backup contact is Nancy
   Billsmer @ 338-4338.\

Step 3 - Generate the Ballerina bindings

In this step, you will use the Bindgen tool to generate Ballerina bindings for the four classes that were listed in Step 1. If you want more information about the tool, see Bindgen tool.

Copy
$ bal bindgen -mvn org.yaml:snakeyaml:1.25 org.yaml.snakeyaml.Yaml java.io.FileInputStream java.io.InputStream java.util.Map

Ballerina package detected at: /Users/sameera/yaml_package

Resolving maven dependencies...
snakeyaml-1.25.jar 100% [===============================================] 297/297 KB (0:00:01 / 0:00:00)

Updated the `Ballerina.toml` file with the new platform libraries.

The following JARs were added to the classpath:
	snakeyaml-1.25.jar

Generating bindings for:
	java.util.Map
	java.io.FileInputStream
	org.yaml.snakeyaml.Yaml
	java.io.InputStream

Generating dependency bindings for:
	org.yaml.snakeyaml.introspector.BeanAccess
	java.util.function.BiFunction
	org.yaml.snakeyaml.DumperOptions$FlowStyle
	...
	...
  • The -mvn option specifies the Maven dependency of the Java library required to generate bindings.
  • The argument list specifies the Java class names.

The Bindgen tool generates bindings for:

  • the specified Java classes
  • the Java classes exposed in the public APIs of all the specified classes

Before you move on to the next step, verify the generated code.

Copy
$ bal build
... 
...

Generating executable
	target/bin/yaml_package.jar

$ bal run target/bin/yaml_package.jar
Hello World!

Step 4 - Write the Ballerina code

Now, you will use the generated bindings and write the Ballerina code, which uses the SnakeYAML library. You can develop the Ballerina code corresponding to the Java code below step by step.

Copy
public class SnakeYamlSample {

    public static void main(String... a) {
        String filename = a[0];
        try (InputStream inputStream = new FileInputStream(filename)) {
            Yaml yaml = new Yaml();
            Map<String, Object> obj = yaml.load(inputStream);
            System.out.println(obj);
        } catch (Exception e) {
            System.err.println("The file '" + filename + "' cannot be loaded. Reason: " + e.getMessage());;
        }
    }
}

Create the FileInputStream

The goal here is to create a new java.io.FileInputStream instance from the filename. In Step 3, you generated bindings for the required Java classes. The following is the code snippet that does the job.

Copy
javaio:FileInputStream | javaio:FileNotFoundException fileInputStream = javaio:newFileInputStream3(filename);

Here, FileInputStream is the Ballerina class generated for the java.io.FileInputStream class.

  • Ballerina bindings for each Java package are mapped onto a separate Ballerina module by default. Therefore, you need to import them when using them inside other modules. Here, the java.io Ballerina module (mapping the corresponding Java package) is imported as javaio. However, if you wish to generate all the bindings inside a single directory, you can do so by using the [(-o|--output) <output-path>] command option.
  • You can find functions that start with newFileInputStream in the generated code. Each such function creates a new java.io.FileInputStream instance. Ballerina does not support function overloading. Therefore, the Bindgen tool generates a separate Ballerina function for each overloaded method or constructor. Function names of the generated bindings will be improved in a future release.
  • All the public instance methods in the java.io.FileInputStream class are mapped to methods in the generated Ballerina class. For more details on how other Java class members are mapped into Ballerina bindings, see Bindgen tool.

Next, you’ll handle the error using a type guard.

Copy
if fileInputStream is javaio:FileNotFoundException {
    // The type of fileInputStream is FileNotFoundException within this block.
    io:println("The file '" + filename + "' cannot be loaded. Reason: " + fileInputStream.message());
} else {
    // The type of fileInputStream is FileInputStream within this block.
}

Create the SnakeYAML entry point

The org.yaml.snakeyaml.Yaml class is the entry point to the SnakeYAML API. The generated corresponding Ballerina class is Yaml. The newYaml1() function is mapped to the default constructor of the Java class. Import the org.yaml.snakeyaml Ballerina module as snakeyaml.

Copy
snakeyaml:Yaml yaml = snakeyaml:newYaml1();

Load the YAML document

Use the org.yaml.snakeyaml.Yaml.load(InputStream is) method to get a java.util.Map Java instance from the given java.io.InputStream. Since the Object Ballerina class (the mapping of java.lang.Object class) resides inside the java.lang module, import it as javalang.

Note: Even though you didn't explicitly generate the java.lang.Object class, it has been generated automatically since it is exposed through the public APIs of generated classes.

Copy
javalang:Object mapObj = yaml.load(fileInputStream);

The org.yaml.snakeyaml.Yaml.load(InputStream is) is a generic method. The Bindgen tool does not support Java generics at the moment. That is why the corresponding Ballerina method returns a java.lang.Object.

You can print the content of the java.util.Map instance in the standard out as follows.

Copy
io:println(mapObj);

Complete the code

Below is the complete code. You can replace the contents in main.bal with the following code.

Copy
import ballerina/io;
import yaml_package.java.io as javaio;
import yaml_package.java.lang as javalang;
import yaml_package.org.yaml.snakeyaml as snakeyaml;
 
public function main(string... args) returns error? {
    string filename = args[0];
    javaio:FileInputStream | javaio:FileNotFoundException fileInputStream = javaio:newFileInputStream3(filename);
    if fileInputStream is javaio:FileNotFoundException {
        io:println("The file '" + filename + "' cannot be loaded. Reason: " + fileInputStream.message());
    } else {
        snakeyaml:Yaml yaml = snakeyaml:newYaml1();
        javalang:Object mapObj = yaml.load(fileInputStream);
        io:println(mapObj);
   }
}

Build and run this code.

Copy
$ bal build
Compiling source
	sameera/yaml_package:0.1.0

Generating executable
	target/bin/yaml_package.jar

Now, you need to pass the YAML file name as the first argument.

Copy
$ bal run target/bin/yaml_package.jar invoice.yml
{invoice=34843, date=Mon Jan 22 16:00:00 PST 2001, bill-to={given=Chris, family=Dumars, address={lines=458 Walkman Dr.
Suite #292
, city=Royal Oak, state=MI, postal=48046}}, ship-to={given=Chris, family=Dumars, address={lines=458 Walkman Dr.
Suite #292
, city=Royal Oak, state=MI, postal=48046}}, product=[{sku=BL394D, quantity=4, description=Basketball, price=450.0}, {sku=BL4438H, quantity=null, description=Super Hoop, price=2392.0}], tax=251.42, total=4443.52, comments=Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338.\}