Skip to content
Java rmi 6 min read

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

TermWhat it means
Remote interfaceA Java interface extending java.rmi.Remote; declares which methods are callable remotely
Remote objectA server-side class that implements the remote interface
StubA 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 RegistryA 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:

  1. Define the remote interface.
  2. Implement the remote object on the server.
  3. Start the RMI registry and bind the object.
  4. 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), call UnicastRemoteObject.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: RemoteException does 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):

  1. The stub (a dynamically generated proxy for the Calculator interface) intercepts the call.
  2. The stub marshals (serializes) the method name and arguments into a byte stream.
  3. A TCP connection is opened (or reused) to the server’s host/port.
  4. The server side receives the stream, unmarshals the arguments, and invokes the real CalculatorImpl.add(3, 4).
  5. The return value is marshaled and sent back.
  6. 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

APIPackageRole
Remotejava.rmiMarker interface for remote objects
RemoteExceptionjava.rmiBase exception for all RMI failures
UnicastRemoteObjectjava.rmi.serverExports a remote object over TCP
Registryjava.rmi.registryThe name-service interface
LocateRegistryjava.rmi.registryCreates or connects to a registry
Namingjava.rmiURL-style bind/lookup shorthand
  • 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 RemoteException and designing resilient distributed code
Last updated June 13, 2026
Was this helpful?