So, you are developing an Elixir/Erlang application and you discovered a use case where you need to use some Java code in your app. This, as with many other use cases, is easily doable using BEAM.
To connect our Java code with Elixir/Erlang we'll use the JInterface package. JInterface
is described Erlang documentation as follows:
The Jinterface package provides a set of tools for communication with Erlang processes. It can also be used for communication with other Java processes using the same package, as well as C processes using the Erl_Interface library.
To start, we should get a hold on the Erlang OTP Java Jar; OtpErlang.jar
. This is the Erlang Java package that contains the classes that we'll use to link Java and Erlang.
To get the path of this jar, open an iex
session and execute :code.root_dir
:
iex(1)> :code.root_dir
'/usr/local/Cellar/erlang/18.3/lib/erlang'
The path of the jar on my Erlang 18.3 installation was:
/usr/local/Cellar/erlang/18.3/lib/erlang/lib/jinterface-1.6.1/priv/OtpErlang.jar
The next steps are all Java. We basically need to create an Erlang node and process that we'll later use to send and receive messages. For a working example, head to my sample project on GitHub.
JInterface
Java library vends all the classes needed to communicate with Erlang. In the sample code above, we will use these main classes (warning Java code ahead):
OtpNode
: This class is responsible for creating an Erlang node; this node is an Erlang runtime environment. The environment is analogous to a JVM instance. We also useOtpNode
to set the cookie used for this Erlang node.OtpMbox
: This class creates an Erlang OTP process. The mailbox has methods to send and receive messages. When creating a mailbox, we can also register its name.OtpErlangPid
: This class represents an Erlang process PID. The PID will be used when we want to send messages from Java to Erlang.- There other types in
JInterface
represents Erlang native types,OtpErlangBinary
for strings,OtpErlangAtom
for atoms,OtpErlangTuple
for tuples and many other native types.
The minimum Java code required to communicate with Erlang is listed below:
try {
OtpNode myOtpNode = new OtpNode("server");
myOtpNode.setCookie("secret");
myOtpMbox = myOtpNode.createMbox("java-server");
while (true) {
OtpErlangTuple tuple = (OtpErlangTuple) myOtpMbox.receive();
lastPid = (OtpErlangPid) tuple.elementAt(0);
OtpErlangAtom dispatch = (OtpErlangAtom) tuple.elementAt(1);
if (dispatch.toString().equals("settext")) {
final OtpErlangBinary message = (OtpErlangBinary) tuple.elementAt(2);
// Set labels and do stuff
} else if (dispatch.toString().equals("greet")) {
final OtpErlangBinary message = (OtpErlangBinary) tuple.elementAt(2);
// Do more UI stuff
}
} catch (Exception e) {
e.printStackTrace();
}
Ignoring all the Java exception handling code and crust, these are the important lines:
- Create an Erlang node and set its cookie.
OtpNode myOtpNode = new OtpNode("server");
myOtpNode.setCookie("secret");
- Create an OTP mailbox (process) and register its name.
myOtpMbox = myOtpNode.createMbox("java-server");
- Receiving and sending messages
When callingmyOtpMbox.receive
, as in Elixir/Erlang, will stop the code execution and wait for a message to be received.
OtpErlangTuple tuple = (OtpErlangTuple) myOtpMbox.receive();
lastPid = (OtpErlangPid) tuple.elementAt(0);
When the message is received, we are casting it to an Elixir/Erlang tuple and storing the first element in the tuple as a PID. We'll use the PID to send messages back to Elixir:
myOtpMbox.send(lastPid, new OtpErlangString("Hello from java"));
Building and running the Java code
Let's give this a go. First, we need to clone the sample repo here. When done, cd into the cloned repo.
In order to build the Java application, we need to use gradle which, depending on whom you ask, is the best Java building tool.
First, check that you have gradle
installed by running gradle
. If you have it, you should get a greeting Output. If that didn't work, head to gradle website and follow the installation steps (or run brew install gradle
🖥)
Now that you have gradle
installed, run gradle build
> gradle build
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar UP-TO-DATE
:assemble UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build UP-TO-DATE
BUILD SUCCESSFUL
Once we have the BUILD SUCCESSFUL
message, we can now run the jar, which will launch the application:
java -jar build/libs/JavaErlang-1.0-SNAPSHOT.jar
That command should launch something like this:
In that UI we have a -
label that we will update with a message from Elixir and a Send Message
button that will post a message back to Elixir.
(I know it's not much, but it will do for the demo :) )
Once the application is running, we can proceed with sending messages back and forth from elixir.
Elixir - Java communication
In this article, we will use an iex
session and connect it to Java[1]. Let's launch an iex session with a short name and a cookie
iex --sname anyname --cookie secret
One thing to note here is that the cookie used in iex
must match the one used in the Java sample.
Next, let's check that we can see the Java created OTP node. We can list all the node visible to the Erlang using :net_adm.names
> :net_adm.names
{:ok, [{'a', 64486}, {'server', 56882}]}
If the Java app successfully created an OTP node, we should see the server
appearing in the output as above.
We can then try to ping that server to see if the cookie is set correctly:
> Node.ping(:"server@your-machine-name")
:pong
The name of the java created node should be the-node-name@the-host-name
.
If we got :pong
it means we are ready to send messages to that node. On the other hand, if you got :pang
, you'll need to make sure that the secrets in Java and iex match.
Now that we know that we can ping that node, it's time to send it a message:
send({:"java-server", :"server@your-machine-name"}, {self(), :"settext", "Hello from elixir"})
We just send a message to the process named java-server
that lives in the server@your-machine-name
node. If everything was in order, your Java UI should be updated:
In the message sent above, we attached the PID
of the iex
session using self()
. If all worked as planned, the Java application has stored this PID which it will use when clicking Send Message
button.
Let's see if it works. Click on Send Message
button and go back to the iex
session to read the message.
> receive do msg -> IO.inspect(msg) end
"Hello from java"
If you got Hello from java
then we are golden :)
Summary
We just saw how (relatively) easy it is to communicate between Java and Elixir/Erlang. I was kinda surprised at how easy and frictionless this was. This is yet another indication that Elixir/Erlang OTP rocks!!!
You might have questions about how to use Erlang-Java communication in production, or how to use it in real-world web application like Spring or Play web application. I am afraid I am not qualified to answer these questions since I didn't try to tackle these problems. So use the information in this post at your own risk 🤖.
As always, if you enjoyed it consider following me on twitter @ifnottrue to keep in touch :)
We could as well use an OTP application, but for the sake of simplicity, I went for iex. ↩︎