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 use OtpNode 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:

1) Create an Erlang node and set its cookie.

OtpNode myOtpNode = new OtpNode("server");  
myOtpNode.setCookie("secret");  

2) Create an OTP mailbox (process) and register its name.

myOtpMbox = myOtpNode.createMbox("java-server");  

3) Receiving and sending messages
When calling myOtpMbox.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:

Java application first run

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 Java1. 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:

Java application with message recieved

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 :)


  1. We could as well use an OTP application, but for the sake of simplicity, I went for iex.