Java RMI (Remote Method Invocation)

Naveen AnamaneniUpdated · 8 min read

RMI (Remote Method Invocation) lets a Java program call a method on an object that lives in a different JVM, possibly on another machine, as if it were a local object. You write service.marksOf("Navika"); RMI packs up the method name and arguments, sends them across the network, runs the method on the server, and brings the result back. It has been part of Java since version 1.1, in the package java.rmi. RMI is a legacy technology: new systems use REST over HTTP, gRPC or messaging instead. It is still worth understanding, because it appears in older enterprise code and in interviews, and because the ideas behind it (stubs, registries, serialization) appear in every remote-call system. In this lesson, you will learn how RMI works and run a complete example.

How RMI Works

PartRole
Remote interfaceDeclares the methods a client may call. It extends java.rmi.Remote, and every method declares throws RemoteException.
Remote objectThe implementation, living in the server's JVM.
StubA stand-in object on the client that implements the same interface. Calling it sends the request over the network.
RegistryA simple name service: the server registers the stub under a name; the client looks the name up.
SerializationArguments and results are serialized to travel between JVMs.
Bind, look up, then call through the stub

The Classes

MemberDescription
RemoteThe marker interface every remote interface extends.
RemoteExceptionIt is thrown when the network or the remote side fails. It is checked.
UnicastRemoteObject.exportObject(obj, port)It is used to make an object callable from other JVMs and returns its stub.
LocateRegistry.createRegistry(port)It starts a registry in this JVM.
LocateRegistry.getRegistry(host, port)It connects to a registry on another machine.
registry.rebind(name, stub)It registers a stub under a name.
registry.lookup(name)It returns the stub registered under a name.

Example: A Remote Marks Service

A real RMI system has two programs. To run as one file, this example plays both parts in one JVM; the calls still travel through RMI's network machinery.

RmiDemo.java▶ Try it
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Map;
 
public class RmiDemo {
    // 1. The remote interface: what a client is allowed to call.
    public interface MarksService extends Remote {
        int marksOf(String student) throws RemoteException;
 
        String grade(int marks) throws RemoteException;
    }
 
    // 2. The implementation, which lives on the server.
    static class MarksServiceImpl implements MarksService {
        private final Map<String, Integer> marks =
            Map.of("Navika", 91, "Srihitha", 84, "Advitha", 78);
 
        @Override
        public int marksOf(String student) throws RemoteException {
            Integer value = marks.get(student);
            if (value == null) {
                throw new RemoteException("No student named " + student);
            }
            return value;
        }
 
        @Override
        public String grade(int marks) {
            return marks >= 90 ? "A" : marks >= 35 ? "B" : "F";
        }
    }
 
    public static void main(String[] args) throws Exception {
        // ---- Server side ----
        MarksServiceImpl impl = new MarksServiceImpl();
        MarksService stub =
            (MarksService) UnicastRemoteObject.exportObject(impl, 0);
        Registry registry = LocateRegistry.createRegistry(0);
        registry.rebind("marks", stub);
        System.out.println("Server: bound "
            + String.join(", ", registry.list()));
 
        // ---- Client side ----
        MarksService service = (MarksService) registry.lookup("marks");
        System.out.println("Client got a proxy? "
            + Proxy.isProxyClass(service.getClass()));
        int marks = service.marksOf("Navika");
        System.out.println("Navika: " + marks + ", grade "
            + service.grade(marks));
        try {
            service.marksOf("Nobody");
        } catch (RemoteException e) {
            System.out.println("RemoteException from the server: "
                + e.getCause().getMessage());
        }
 
        // Shut down so the program can exit.
        registry.unbind("marks");
        UnicastRemoteObject.unexportObject(impl, true);
        UnicastRemoteObject.unexportObject(registry, true);
    }
}
Output
Server: bound marks
Client got a proxy? true
Navika: 91, grade A
RemoteException from the server: No student named Nobody

Explanation:

  1. MarksService extends Remote, and both methods declare throws RemoteException. A remote call can fail in ways a local call can't: the network may be down or the server gone. The checked exception makes callers face that.
  2. exportObject(impl, 0) makes the object reachable on a free port and returns its stub.
  3. createRegistry and rebind start a registry and publish the stub under the name marks. (The usual registry port is 1099; port 0 picks any free one for this demonstration.)
  4. registry.lookup("marks") is the client's step. What it receives is a proxy implementing MarksService, not the real object.
  5. service.marksOf("Navika") looks like an ordinary call. The stub serializes the argument, sends it to the server object, and deserializes the result.
  6. An exception thrown on the server comes back to the client wrapped in a RemoteException.
  7. The last three lines unexport the objects. RMI keeps background threads running, so without this the program wouldn't exit.

In a two-machine setup, the server code stays the same, and the client would call LocateRegistry.getRegistry("server-host", 1099) to find the registry.

Rules for Remote Interfaces

RuleWhy
The interface extends RemoteIt marks the methods as callable remotely
Every method declares throws RemoteExceptionRemote calls can fail at any time
Arguments and return values must be serializable (or remote objects)They are copied across the network
Objects are passed by copyChanging an argument on the server doesn't change the client's object

Why RMI Is Legacy

LimitationWhat replaced it
Java only: both sides must be JavaREST with JSON, gRPC: any language
Based on Java serialization, a long-standing source of security holesJSON, Protocol Buffers
Uses its own ports, awkward with firewalls and proxiesHTTP on standard ports
Client and server tightly coupled to shared interfaces and classesVersioned APIs and message formats
Hides the network behind method calls, so slow and failing calls look localExplicit requests, timeouts and retries

Pieces of RMI have already been removed from the JDK: the rmic stub compiler and the RMI activation system are gone. The core API remains, and is still used internally by technologies such as JMX, which monitoring tools rely on.

Common Mistakes

Forgetting throws RemoteException on an interface method. Exporting the object fails.

Passing objects that aren't serializable. The call fails with NotSerializableException.

Expecting changes to an argument to appear on the client. Arguments are copies.

Treating remote calls like local ones. Each call crosses the network: it is thousands of times slower and can fail. Avoid calling in tight loops.

Not unexporting objects. The JVM keeps running.

Interview Corner

What is RMI in Java?

RMI (Remote Method Invocation) is a Java API that lets an object in one JVM invoke methods on an object in another JVM. The server defines a remote interface extending java.rmi.Remote, implements it, exports the object with UnicastRemoteObject and binds its stub in an RMI registry. The client looks up the stub by name and calls methods on it; the stub marshals the arguments using serialization, sends them to the server, and unmarshals the result. Every remote method must declare RemoteException. RMI is Java-specific and has largely been replaced by REST, gRPC and messaging.

What are a stub and a skeleton in RMI?

The stub is the client-side proxy for the remote object: it implements the remote interface, forwards each call over the network and returns the result. The skeleton was the server-side counterpart that received the call and dispatched it to the real object. Since Java 5, stubs are generated dynamically as proxies and skeletons are no longer needed; the rmic compiler that produced them has been removed.

Practice

  1. Easy: Add a method average() to MarksService that returns the average of all marks, and call it from the client part.
  2. Harder: Split the example into two programs, Server and Client, using port 1099 and LocateRegistry.getRegistry("localhost", 1099), and run them in two terminals.

Quick check: RMI

4 questions
1What does RMI allow?
2Which exception must every remote method declare?
3What does the client actually hold after a lookup?
4Why is RMI considered legacy?

Key Takeaways

Was this tutorial helpful?

Lesson 520 of 578