1. Overview
When two JVMs need to communicate, Java RMI is one option we have to make that happen. In this article, we’ll bootstrap a simple example showcasing Java RMI technology.
2. Creating the Server
There are two steps needed to create an RMI server:
- Create an interface defining the client/server contract.
- Create an implementation of that interface.
2.1. Defining the Contract
First of all, let’s create the interface for the remote object. This interface extends the java.rmi.Remote marker interface.
In addition, each method declared in the interface throws the java.rmi.RemoteException:
public interface MessengerService extends Remote { String sendMessage(String clientMessage) throws RemoteException; }
Note, though, that RMI supports the full Java specification for method signatures, as long as the Java types implement java.io.Serializable.
We’ll see in future sections, how both the client and the server will use this interface.
For the server, we’ll create the implementation, often referred to as the Remote Object.
For the client, the RMI library will dynamically create an implementation called a Stub.
2.2. Implementation
Furthermore, let’s implement the remote interface, again called the Remote Object:
public class MessengerServiceImpl implements MessengerService { @Override public String sendMessage(String clientMessage) { return "Client Message".equals(clientMessage) ? "Server Message" : null; } public String unexposedMethod() { /* code */ } }
Notice, that we’ve left off the throws RemoteException clause from the method definition.
It’d be unusual for our remote object to throw a RemoteException since this exception is typically reserved for the RMI library to raise communication errors to the client.
Leaving it out also has the benefit of keeping our implementation RMI-agnostic.
Also, any additional methods defined in the remote object, but not in the interface, remain invisible for the client.
3. Registering the Service
Once we create the remote implementation, we need to bind the remote object to an RMI registry.
3.1. Creating a Stub
First, we need to create a stub of our remote object:
MessengerService server = new MessengerServiceImpl(); MessengerService stub = (MessengerService) UnicastRemoteObject .exportObject((MessengerService) server, 0);
We use the static UnicastRemoteObject.exportObject method to create our stub implementation. The stub is what does the magic of communicating with the server over the underlying RMI protocol.
The first argument to exportObject is the remote server object.
The second argument is the port that exportObject uses for exporting the remote object to the registry.
Giving a value of zero indicates that we don’t care which port exportObject uses, which is typical and so chosen dynamically.
Unfortunately, the exportObject() method without a port number is deprecated.
3.2 Creating a Registry
We can stand up a registry local to our server or as a separate stand-alone service.
For simplicity, we’ll create one that is local to our server:
Registry registry = LocateRegistry.createRegistry(1099);
This creates a registry to which stubs can be bound by servers and discovered by clients.
Also, we’ve used the createRegistry method, since we are creating the registry local to the server.
By default, an RMI registry runs on port 1099. Rather, a different port can also be specified in the createRegistry factory method.
But in the stand-alone case, we’d call getRegistry, passing the hostname and port number as parameters.
3.3 Binding the Stub
Consequently, let’s bind our stub to the registry. An RMI registry is a naming facility like JNDI etc. We can follow a similar pattern here, binding our stub to a unique key:
registry.rebind("MessengerService", stub);
As a result, the remote object is now available to any client that can locate the registry.
4. Creating the Client
Finally, let’s write the client to invoke the remote methods.
To do this, we’ll first locate the RMI registry. In addition, we’ll look up the remote object stub using the bounded unique key.
And finally, we’ll invoke the sendMessage method:
Registry registry = LocateRegistry.getRegistry(); MessengerService server = (MessengerService) registry .lookup("MessengerService"); String responseMessage = server.sendMessage("Client Message"); String expectedMessage = "Server Message"; assertEquals(expectedMessage, responseMessage);
Because we’re running the RMI registry on the local machine and default port 1099, we don’t pass any parameters to getRegistry.
Indeed, if the registry is rather on a different host or different port, we can supply these parameters.
Once we lookup the stub object using the registry, we can invoke the methods on the remote server.
5. Conclusion
In this tutorial, we got a brief introduction to Java RMI and how it can be the foundation for client-server applications. Stay tuned for additional posts about some of RMI’s unique features!
The source code of this tutorial can be found over on GitHub.