loading...

Ballerina Interop and all you need to know

lankavitharana profile image Rajith ・5 min read

First of all, I'll give a brief introduction about Ballerina as this is my first post about ballerina in dev.to

Ballerina is a brand new statically typed programming language which was developed specifically targeting the integration space. There are some previous posts I have written in Medium about ballerina language

Let's come to the topic at hand, very often people were asking me about the interrelationship between java and ballerina and how to use existing Java libraries in Ballerina. I am not going to talk about the interrelationship in this post as that itself is a separate topic. This post is about how to properly use interop functionality to interact with java in Ballerina.

Earlier we used a different mechanism called native binding to interact with java, however since the introduction of interop, we strongly recommend the usage of interop instead of the old mechanism, in fact, it is bit hard to do the old style binding anyway now(even though we have both of the mechanisms still there and most of the standard libraries are still using the old style)

If anyone is familiar with old-style binding, a major difference between two are how we keep binding details, wherein old-style we kept them at java side as an annotation and in interop, those details are kept at ballerina side as annotations.

First I'll explain the concept, say you need to invoke a java method inside ballerina, what you have to do is define a ballerina method which matches the java signature and specify the fully qualified Java class which has the java method in the annotation.

So let's dig into howto with a simple example

here we are going to invoke below java method from ballerina side as an interop function.

package org.wso2.ballerina.math;

public class AddInt {

    public static long addInt(long a, long b) {
        return a + b;
    }
}

so, first of all, you'll have to write above java code and get that compiled into a jar. As you can see, above is a simple static java method which accepts two long parameters(I am using long type here because ballerina int is a long in java side which makes the sample simple one)

Next step is to create the ballerina function definition and provide necessary linking information.

Below is the example ballerina function definition

public function addInt(int a, int b) returns int = @java:Method {
    class:"org/wso2/ballerina/math/AddInt"
} external;

That's simply it, you can use addInit as a normal function in ballerina side. Still, as I have said earlier, for the ballerina module to build properly, you'll have to provide the jar file which contains the above AddInit java class. That you can specify in the Ballerina.toml file itself. Below is how it's done.

[platform]
target = "java8"

    [[platform.libraries]]
    artifactId = "ballerina-math"
    version = "@project.version@"
    path = "../native-math/target/ballerina-math-1.0.0.jar"
    groupId = "org.wso2.ballerinalang"
    modules = ["math"]

You can find the complete example in the GitHub repo ballerina-math (check the tag relevant to the ballerina version)

The concept is pretty simple, even though we are invoking a java method, there should always be a ballerina side definition, so from ballerina perspective, it's very easy to do all the type checkings and validations. The only other validation we are doing with interop is validating the ballerina definition against the actual java implementation.

When it comes to invoking java, there comes the problem of how to map ballerina types to java types vice versa. And also what to do about actual java types that cannot be represented in ballerina side. For that, we have introduced a new type called handle. Handle is an opaque type as far as ballerina is concerned, so you can keep any kind of java value under handle type. Other than that, we tried to map all the possible types with java, because with handles we cannot properly do type checking. So for example, int in ballerina side is a long value in java.

Ok now since we have the basics, let's dig into a bit complex example.

So how do we handle java method overloads?

For them, we can use annotations(java:Method annotation) to provide more details. Since ballerina doesn't allow overloading, you'll have to define separate ballerina functions with distinct names, however using annotations, you can specify the actual java method name you need to invoke. (Basically, you can use annotation to override the default behavior).

So how do we invoke instance methods and handle java constructors?
Fo constructor, you can use java:Constructor annotation. As you may have guessed, you can override parameter mapping in the annotation as well. And you can keep the created object reference in ballerina side as a handle then when invoking an instance method, you will have to pass that handle as the first argument of the method invocation. Ok let's take an example for more clarity

package org.wso2.ballerina.math;

public class Substract {
    private long initialVal;

    public Substract(long initial) {
    this.initialVal = initial;
    }

    public long substractInt(long a) {
    return initialVal - a;
    }
}

Below is the ballerina side constructor mapping

public function newSubstract(int initial) returns handle = @java:Constructor {
    class:"org/wso2/ballerina/math/Substract"
} external;

as you can see we have used java:Constructor annotation here, and we are returning a handle type value, which is the object instance. Here the name of the ballerina method doesn't matter(no need to match it with java constructor name or anything), we use parameters to distinguish between overloaded constructors.

Below is the ballerina method which matches to the instance method.

public function substract(handle receiver, int b) returns int = @java:Method{
    class:"org/wso2/ballerina/math/Substract",
    name:"substractInt"
} external;

Note that I have used name field to specify the actual java method name(when the ballerina method name is different from actual java method name).

So how do we use these constructors and instance methods?
Below is a sample code which creates and instance of java Substract class and invoke it's substractInt instance method.

    // Creating instance
    handle sub = newSubstract(10);

    // Invoking a instance method
    int subRes = substract(sub, 5);
    io:println(subRes);

I have touched most of the parts related to java interop, however there are more complex scenarios than this, like how to handle errors in interop, how to do async invocations, how to access fields, how to use defaultable parameters and list goes on :) there are so many cool features in Ballerina :)

Yet I'm going to conclude the post here as it is already a bit long, I Will try to either extend this post or create a new one with rest of the details. (Maybe I have to update the post heading "..Part of what you need to know" instead of the current heading :) )

Discussion

pic
Editor guide