Getting Java RMI Working

Abstract: It can be a real pain to get RMI working, as there are loads of different things that can go wrong, both at the compilation and the deployment stages. This document describes how to negotiate this mess and end up with something that works.


Firstly, the arrangement of the code I shall be illustrating with is that there is a body of code which provides some service (the server), and another body of code that uses the service (the client). The server is to know nothing about the client, and the client is to only have extremely limited access to the server (which may even be running on a different machine on different filesystems.)

Secondly, make sure you use the same version of the Java environment throughout. There are some weird faults that can happen if you don't!.

Building the Classes into JAR Files

The first thing you should do is to write your remote interface(s), all of which must extend java.rmi.Remote. Each of the methods in those remote interfaces must throw java.rmi.RemoteException at least. It is also advisable to make sure that all the parameters to the methods and results of the methods are primitive types (like int and void), objects that implement or interfaces that extend java.io.Serializable, interfaces that extend java.rmi.Remote (your objects should do one or other of them, and your implementations must do this for things to work at all) or arrays of these things (or arrays of arrays of ...) It is also highly recommended that you make sure that all classes that implement java.io.Serializable (whether directly or indirectly) contain a serialVersionUID field; the serialver program can help here.

Now, you write your objects that provide implementations of all the remote interfaces. You will probably want to make these extend java.rmi.server.UnicastRemoteObject, though there are other ways to do this as well. This done (and everything on the server-side compiled up) you need to run the rmic program on each remote implementation to build the stubs and skeletons that do all the communications for you. This done, you should have the server-side code built properly.

Before you can start building the client though, you need to partition the server code into three parts; the public interface, the remote stubs, and the private server implementation. The public interface should be the remote interfaces themselves, plus any classes they mention (and whatever classes those classes mention too; you need to form a transitive closure here of all the classes mentioned so that people can manipulate the arguments to the remote methods) and whatever support classes you want to give clients direct access to. The second partition (which overlaps with the first) contains the remote interfaces and the remote stubs (which are named the same as the remote implementation, but with _Stub appended to the class name) created by rmic (but not the skeletons) plus all the classes those remote interfaces and stubs mention, but nothing else (provided you've included all your exceptions that can be thrown across a remote method call in the first partition.) The third partition is all the rest of the server code, including the remote object skeletons created by rmic (if it created any at all, that is.)

Now that you've formed this partition, you should build three JAR files (this is the easiest way), one with the classes in the first partition (which I'll call public.jar for convenience through the rest of this document), one with the classes in the second partition (which I'll call remote.jar) and one with the classes from all three partitions (which I'll call server.jar). The public.jar should be handed to people developing clients to put on their classpath when compiling and running the application, and the remote.jar should be placed in a well-known place such as on an FTP or HTTP server. It is recommended that the server author or publisher should use jarsigner to apply a digital signature to remote.jar, and signing public.jar and server.jar is not a bad idea too, though you do not need to use the same signatures for them.

The client author should use just the classes in public.jar when writing their code, and they don't need to take any special action at this point (though it is a smart move to build the resulting class files into a JAR file too, which I'll call client.jar.)

Getting the Server Running

To get the server running, you will need to first create a suitable security policy file (policytool can help here, but it is perfectly feasable to do this by hand too.) All this needs to do is grant server.jar whatever level of permissions are appropriate, though you may well find that java.security.AllPermission is the most appropriate. It all depends on how much you trust the server code. Note that you can use either the codeBase or the signer of the server code to specify what code to trust; using the signer makes it much easier to move things around...

Before running the server, make sure you have an instance of rmiregistry running on the same machine where you want to run the server (server objects can only be registered on the same machine that they are actually hosted on) and make sure that your CLASSPATH variable is empty (or at least contains none of the JARs created above) when you run this command. Then all you need to do is start up java with the right options and you're away. The right options (which must go before the name of the class to start the execution at) are:

-Djava.security.manager

You only need this option if you don't install a security manager in the server implementation itself, but you must have a security manager to be able to get the RMI registry stub (part of the server object registration process.)

-Djava.security.policy=thePolicyFileYouCreatedAbove

You need this because the default security level is definitely far too restrictive; you won't even be able to contact the RMI registry to publish the server object(s).

-Djava.rmi.server.codebase=thePublishedUrlForRemote.jar

It is through this that clients are told where to locate the stubs. Get it right.

-classpath server.jar

Where to find the code to run; note that old versions of java (pre-1.2) should replace this with a suitably set CLASSPATH environment variable. This alone is one good reason for switching to Java 1.2 or later...

Now, so long as your server code remembers to call java.rmi.Naming.bind or java.rmi.Naming.rebind to register the remote object implementation(s), everything should be fine, though you should read Tips and Tricks below for some helpful extras.

Getting the Client Running

Client authors will also need to create a suitable security policy; the key entry will be one allowing code in the remote.jar codeBase (or which has been signed by the signer of that JAR) to open sockets (with port numbers of at least 1024) to the machine that hosts the server objects. An example policy file entry might be:

grant codeBase "http://www.bar.com/spong/remote.jar" {
    permission java.net.SocketPermission "foo.bar.com:1024-", "connect,resolve";
};

Once you've set your policy file up (remember to allow the client code to do what it needs to do too, such as reading files and opening sockets; java.security.AllPermission is useful here too) all you need to do is to start up the client with both public.jar and client.jar on the class-path, a security manager installed (either on the command line or in the client implementation) and the location of the policy file. At this point, the client should be able to use java.rmi.Naming.lookup to obtain a server stub and then invoke methods on it. It Should All Just WorkTM

Tips and Tricks

Fixing Problems

The most common problems are:

Note that many of the above problems can manifest themselves in the oddest of ways, and some of them can produce the same exception (the most inscrutable of which is java.lang.NoClassDefFoundError since it doesn't explain why there is a problem) but for different reasons. This sucks, and often the debugging properties don't help all that much either.

Intranet Links


Valid HTML 4.0!Any feedback on this? Email me and let me know so I can make this document better. Continued improvements will depend on your input...

Donal K. Fellows / Department of Computer Science, University of Manchester