Remote Method Invocation (RMI)
Java RMI (Remote Method Invocation) lets one JVM call methods on an object living in a completely different JVM — even on another machine — as if that object were local. It is Java’s built-in mechanism for building distributed applications without manually dealing with sockets and serialization.
What Problem Does RMI Solve?
Imagine you have a heavy computation service running on a powerful server, and you want a lightweight client app to trigger it. Without RMI you would have to design a custom protocol, open sockets, serialize data by hand, and handle every edge case yourself. RMI wraps all of that up so you write plain Java interfaces and method calls.
RMI is part of java.rmi package (and sub-packages like java.rmi.server), and it has been in Java since version 1.1.
Note: For modern microservices you might reach for REST, gRPC, or message queues. RMI is still valuable when you want a pure-Java, tightly coupled distributed call with full object transparency, and it remains a foundational concept that every Java developer should understand.
Core Concepts
| Term | What it means |
|---|---|
| Remote interface | A Java interface extending java.rmi.Remote; declares which methods are callable remotely |
| Remote object | A server-side class that implements the remote interface |
| Stub | A client-side proxy generated automatically; intercepts calls and sends them over the network |
| Skeleton (historical) | The server-side counterpart of a stub (removed in Java 2; the JVM now handles this internally) |
| RMI Registry | A simple name service (rmiregistry) where servers bind their remote objects and clients look them up |
Step-by-Step RMI Setup
Building an RMI application always follows the same four steps:
- Define the remote interface.
- Implement the remote object on the server.
- Start the RMI registry and bind the object.
- Write the client that looks up the object and calls it.
Step 1 — Define the Remote Interface
Every method in a remote interface must declare throws RemoteException. The interface itself must extend Remote.
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Calculator extends Remote {
int add(int a, int b) throws RemoteException;
int multiply(int a, int b) throws RemoteException;
}
Step 2 — Implement the Remote Object
Extend UnicastRemoteObject (the easiest way) and implement your interface. The super constructor handles the low-level export of the object so the JVM can accept incoming calls.
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class CalculatorImpl extends UnicastRemoteObject implements Calculator {
// The constructor must declare RemoteException
public CalculatorImpl() throws RemoteException {
super();
}
@Override
public int add(int a, int b) throws RemoteException {
return a + b;
}
@Override
public int multiply(int a, int b) throws RemoteException {
return a * b;
}
}
Tip: If you cannot extend
UnicastRemoteObject(e.g., you already extend another class), callUnicastRemoteObject.exportObject(this, 0)manually inside your constructor instead.
Step 3 — Write the Server
The server creates the remote object, starts (or connects to) an RMI registry, and binds the object to a name.
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class CalculatorServer {
public static void main(String[] args) throws Exception {
CalculatorImpl calc = new CalculatorImpl();
// Create a registry on port 1099 (the default RMI port)
Registry registry = LocateRegistry.createRegistry(1099);
// Bind the remote object under a name
registry.bind("CalculatorService", calc);
System.out.println("Calculator server is ready.");
}
}
Note: Port 1099 is the conventional RMI registry port. Make sure it is not blocked by a firewall when running across machines.
Step 4 — Write the Client
The client looks up the object by name and then calls methods on it exactly as if it were a local object — the stub handles all network communication transparently.
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class CalculatorClient {
public static void main(String[] args) throws Exception {
// Connect to the registry (use server hostname in production)
Registry registry = LocateRegistry.getRegistry("localhost", 1099);
// Look up the remote object — returns a stub
Calculator calc = (Calculator) registry.lookup("CalculatorService");
System.out.println("3 + 4 = " + calc.add(3, 4));
System.out.println("3 * 4 = " + calc.multiply(3, 4));
}
}
Output:
3 + 4 = 7
3 * 4 = 12
Passing Objects Over RMI
Any object you pass as an argument or return value in an RMI call must be serializable (implement java.io.Serializable). The object is serialized on one side, transmitted, and deserialized on the other side — you receive a copy, not a reference.
import java.io.Serializable;
public class Point implements Serializable {
public final int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
If you pass a Point to a remote method, the server gets its own deserialized copy. Changes to it on the server do not affect the client’s original object.
See Serialization for a deeper look at how objects are converted to bytes.
Handling Exceptions
RemoteException is a checked exception that wraps the underlying network failure. Always handle it — or propagate it via throws — so your application can react to connectivity problems gracefully.
try {
int result = calc.add(10, 20);
System.out.println("Result: " + result);
} catch (java.rmi.RemoteException e) {
System.err.println("Remote call failed: " + e.getMessage());
}
Warning:
RemoteExceptiondoes not tell you whether the server completed the operation before the network dropped. Design your remote methods to be idempotent when possible.
Using Naming (Simpler Lookup)
The java.rmi.Naming utility class offers a URL-style shorthand for bind/lookup when the registry is on localhost:
// On the server
java.rmi.Naming.rebind("rmi://localhost:1099/CalculatorService", calc);
// On the client
Calculator calc = (Calculator) java.rmi.Naming.lookup("rmi://localhost:1099/CalculatorService");
rebind is like bind but replaces any existing binding with the same name — useful during development.
Under the Hood
How a Remote Call Travels
When the client calls calc.add(3, 4):
- The stub (a dynamically generated proxy for the
Calculatorinterface) intercepts the call. - The stub marshals (serializes) the method name and arguments into a byte stream.
- A TCP connection is opened (or reused) to the server’s host/port.
- The server side receives the stream, unmarshals the arguments, and invokes the real
CalculatorImpl.add(3, 4). - The return value is marshaled and sent back.
- The stub unmarshals the return value and returns it to the caller.
From Java 5 onward, stubs are generated dynamically using Reflection and java.lang.reflect.Proxy — no rmic compilation step is needed.
Thread Safety
The RMI runtime can dispatch multiple incoming calls concurrently using its internal thread pool (similar to a Thread Pool). Your remote object implementation must be thread-safe if it maintains shared mutable state.
Security Considerations
Older RMI tutorials reference RMISecurityManager and a policy file for loading stub classes dynamically over the network. Since Java 17, RMISecurityManager is deprecated and dynamic class loading from remote codebases is disabled by default. For modern Java:
- Keep server and client stubs on the classpath of both sides.
- Use TLS/SSL sockets (
SslRMIClientSocketFactory/SslRMIServerSocketFactory) when communicating over untrusted networks.
Quick Reference — Key Classes & Interfaces
| API | Package | Role |
|---|---|---|
Remote | java.rmi | Marker interface for remote objects |
RemoteException | java.rmi | Base exception for all RMI failures |
UnicastRemoteObject | java.rmi.server | Exports a remote object over TCP |
Registry | java.rmi.registry | The name-service interface |
LocateRegistry | java.rmi.registry | Creates or connects to a registry |
Naming | java.rmi | URL-style bind/lookup shorthand |
Related Topics
- Serialization — RMI relies on Java serialization to pass objects between JVMs
- Socket Programming — the lower-level networking layer that RMI builds on top of
- Networking Basics — understanding hosts, ports, and protocols before diving into RMI
- Reflection API — how modern Java generates RMI stubs dynamically at runtime
- Interface — remote interfaces are plain Java interfaces with
extends Remote - Exception Handling — handling
RemoteExceptionand designing resilient distributed code