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 handle
s 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 :) )
Top comments (0)