RMI stands for remote method invocation and, as the name indicates, is a protocol for a Java program to invoke a method of an object running on another computer. It provides an API (Application Programming Interface) for exporting an object from one program (called the server) and invoking the methods of that object from another program (called the client), possibly running on a different computer.

The Java RMI Registry is a key component of the Java RMI system and provides a centralized directory for servers to register services and for clients to lookup these services. In this article, we learn how to implement a server to expose an object and a client to invoke a method on the server, as well as registering and looking up the service in the RMI Registry.

how java rmi registry works

Declaring the Server Interface

To learn the intricacies of how the Java RMI system works, let us implement a simple server object providing a method to accept a name and return a greeting. Here is the definition of the object interface:

        import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Greeting extends Remote
{
  public String greet(String name) throws RemoteException;
}

The name of the interface is called Greeting. It provides a single method called greet() which accepts a name and returns a suitable greeting.

To mark this interface as exportable, it needs to extend the java.rmi.Remote interface. Also the method needs to declare a throws clause listing java.rmi.RemoteException in addition to any application specific exceptions. This is so the client code can handle (or propagate) remote method invocation errors such as host-not-found, connection-failure, etc.

Implementing the Server Object

After declaring the interface (which is used by the clients), we implement the server side object, and provide the greet() method as shown. It uses a simple format string to format the greeting.

        public class GreetingObject implements Greeting
{
  private String fmtString = "Hello, %s";

  public String greet(String name)
  {
    return String.format(this.fmtString, name);
  }
}

The Server Main Method

Let us now collate all these pieces together and implement the main() method of the server. Let us go through each of the relevant steps.

  • The first step is to create the server object implementation.
            Greeting greeting = new GreetingObject();
  • Next, we obtain a stub for the server object from the RMI runtime. The stub implements the same interface as the server object. However the method implements the required communication with the remote server object. This stub is used by the client to transparently invoke the method on the server object.
            Greeting stub = (Greeting)UnicastRemoteObject.exportObject(greeting, 0);
  • Once the stub is obtained, we hand this stub over to the RMI registry to bind to a specified named service. When the client requests an implementation of this service, it receives the stub which knows how to communicate with the server object.In the following, the static method LocateRegistry.getRegistry() is used to obtain the local registry reference. The rebind() method is then used to bind the name to the stub.
            String name = "Greeting";
    Registry registry = LocateRegistry.getRegistry(port);
    registry.rebind(name, stub);

The complete main method.

        import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class Main
{
  static public void main(String[] args) throws Exception
  {
    if ( args.length == 0 ) {
      System.err.println("usage: java Main port#");
      System.exit(1);
    }

    int index = 0;
    int port = Integer.parseInt(args[index++]);
    String name = "Greeting";
    Greeting greeting = new GreetingObject();
    Greeting stub = (Greeting)UnicastRemoteObject.exportObject(greeting, 0);
    Registry registry = LocateRegistry.getRegistry(port);
    registry.rebind(name, stub);
    System.out.println("Greeting bound to \"" + name + "\"");
  }
}

Building the Server

Let us now look into building the server. To keep things simple, we build using the command line on Linux rather than using a build tool such as Maven.

The following compiles the source files to class files in a target directory.

        rm -rf target
mkdir target
javac -d target src/server/*.java

Collect the class files into a JAR file for execution.

        jar cvf target/rmi-server.jar -C target server

We also collect the interface files required for compiling the client into a library JAR.

        jar cvf target/rmi-lib.jar -C target server/Greeting.class

Implementing the Client

Let us now look into implementing the client used for invoking the server object methods.

  • As with the server, obtain a reference to the registry, specifying the hostname where the registry is running, and the port number.
            Registry registry = LocateRegistry.getRegistry(host, port);
  • Next, lookup the service in the registry. The lookup() method returns a stub which can be used for invoking services.
            Greeting greeting = (Greeting) registry.lookup(name);
  • And invoke the method passing the required arguments. Here, we get the greeting by passing the name and printing it out.
            System.out.println(name + " reported: " + greeting.greet(myName));

The Complete Client Code:

        package client;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import server.Greeting;

public class Client
{
  static public void main(String[] args) throws Exception
  {
    if ( args.length != 3 ) {
      System.err.println("usage: java Client host port myName");
      System.exit(1);
    }

    int index = 0;
    String host = args[index++];
    int port = Integer.parseInt(args[index++]);
    String myName = args[index++];
    String name = "Greeting";
    Registry registry = LocateRegistry.getRegistry(host, port);
    Greeting greeting = (Greeting) registry.lookup(name);
    System.out.println(name + " reported: " + greeting.greet(myName));
  }
}

The RMI Registry

Let us now run the server program so that it can start serving requests.

        java -cp target/rmi-server.jar server.Main 1099
# throws
Exception in thread "main" java.rmi.ConnectException: Connection refused to host: xxx; nested exception is:
 java.net.ConnectException: Connection refused

What is this exception? Connection refused.

The reason you get this exception is because: note from the server code that it attempts to connect to the local registry on port 1099. If that fails, you end up with this exception.

The solution is to run the RMI Registry. The RMI Registry is a program shipped with the Java Virtual Machine and is called rmiregistry. It should be located in the bin directory of the Java Virtual Machine installation. Running it is as simple as:

        /usr/lib/jvm/jdk1.8.0_71/bin/rmiregistry

By default, the registry listens on port 1099. To get it to listen on another port, specify the port number as follows:

        /usr/lib/jvm/jdk1.8.0_71/bin/rmiregistry 1100

Check that there is indeed a listener at the specified port with the netstat command:

        netstat -an -t tcp -p | grep LISTEN
...
tcp6 0 0 :::1100 :::* LISTEN 23450/rmiregistry

Running the Server

Let us now try to run the server again.

        java -cp target/rmi-server.jar server.Main 1100
# throws
java.rmi.UnmarshalException: error unmarshalling arguments
...
Caused by: java.lang.ClassNotFoundException: server.Greeting
...

An exception again! What is it this time?

The server is unable to load the interface class server.Greeting. This happens because the RMI Registry is unable to load the required class. So you need to specify the location of the required classes. One way to do it is to specify the CLASSPATH environment variable:

        CLASSPATH=../../junk/target/rmi-lib.jar /usr/lib/jvm/jdk1.8.0_71/bin/rmiregistry 1100

Trying to run the server again gives:

        java -cp target/rmi-server.jar server.Main 1100
# prints
Greeting bound to "Greeting"

Now the server is running.

Running the Client

After all the parts are assembled and executing, running the client is simple. It needs the appropriate JARs for execution. These include the class containing the main() method, and the interface class. It accepts arguments indicating where the RMI registry is running, and a name for the greeting.

        java -cp target/rmi-client.jar:target/rmi-lib.jar client.Client localhost 1100 Peter
# prints
Greeting reported: Hello, Peter

Summary

Java RMI provides an API and tools to make remote code execution easier. You can implement a server which registers a service object with the Java RMI Registry. Clients can query the registry and obtain service object stub for invoking the service methods. As this example illustrates, it is all quite simple.

Are you using Java RMI in your project? What has been your experience? Are there any alternatives you've investigated? Please let us know in the comments below.