Java RMI

Remote Method Invocation for distributed applications

🔄 What is Java RMI?

RMI (Remote Method Invocation) enables Java objects to call methods on objects running on different JVMs across networks. It provides distributed computing capabilities for building scalable enterprise applications.


// RMI interface example
public interface Calculator extends Remote {
    int add(int a, int b) throws RemoteException;
    int multiply(int a, int b) throws RemoteException;
}
                                    

RMI Components

📋

Remote Interface

Defines methods for remote access

interface MyService extends Remote { }
🏗️

Remote Object

Implementation of remote interface

class MyServiceImpl extends UnicastRemoteObject
📖

RMI Registry

Naming service for remote objects

Registry registry = LocateRegistry.createRegistry(1099);
👤

RMI Client

Calls methods on remote objects

MyService service = (MyService) registry.lookup("MyService");

🔹 Remote Interface

Define the interface for remote method calls:

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

// Remote interface must extend Remote
public interface Calculator extends Remote {
    
    // All methods must throw RemoteException
    int add(int a, int b) throws RemoteException;
    
    int subtract(int a, int b) throws RemoteException;
    
    int multiply(int a, int b) throws RemoteException;
    
    double divide(int a, int b) throws RemoteException;
    
    String getServerInfo() throws RemoteException;
}

🔹 Remote Object Implementation

Implement the remote interface:

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

// Extend UnicastRemoteObject for automatic stub generation
public class CalculatorImpl extends UnicastRemoteObject implements Calculator {
    
    // Constructor must handle RemoteException
    public CalculatorImpl() throws RemoteException {
        super();
    }
    
    @Override
    public int add(int a, int b) throws RemoteException {
        System.out.println("Server: Adding " + a + " + " + b);
        return a + b;
    }
    
    @Override
    public int subtract(int a, int b) throws RemoteException {
        System.out.println("Server: Subtracting " + a + " - " + b);
        return a - b;
    }
    
    @Override
    public int multiply(int a, int b) throws RemoteException {
        System.out.println("Server: Multiplying " + a + " * " + b);
        return a * b;
    }
    
    @Override
    public double divide(int a, int b) throws RemoteException {
        if (b == 0) {
            throw new RemoteException("Division by zero!");
        }
        System.out.println("Server: Dividing " + a + " / " + b);
        return (double) a / b;
    }
    
    @Override
    public String getServerInfo() throws RemoteException {
        return "Calculator Server v1.0 - Running on " + 
               System.getProperty("os.name");
    }
}

🔹 RMI Server

Create and register the remote object:

import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.RemoteException;

public class CalculatorServer {
    public static void main(String[] args) {
        try {
            // Create the remote object
            Calculator calculator = new CalculatorImpl();
            
            // Create RMI registry on port 1099
            Registry registry = LocateRegistry.createRegistry(1099);
            
            // Bind the remote object to a name
            registry.rebind("Calculator", calculator);
            
            System.out.println("Calculator Server is running...");
            System.out.println("Server ready on port 1099");
            
            // Keep server running
            while (true) {
                Thread.sleep(1000);
            }
            
        } catch (RemoteException e) {
            System.err.println("Server error: " + e.getMessage());
        } catch (InterruptedException e) {
            System.err.println("Server interrupted: " + e.getMessage());
        }
    }
}

Server Output:

Calculator Server is running...

Server ready on port 1099

🔹 RMI Client

Connect to and use the remote object:

import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.RemoteException;
import java.rmi.NotBoundException;

public class CalculatorClient {
    public static void main(String[] args) {
        try {
            // Get reference to RMI registry
            Registry registry = LocateRegistry.getRegistry("localhost", 1099);
            
            // Look up the remote object
            Calculator calculator = (Calculator) registry.lookup("Calculator");
            
            // Call remote methods
            System.out.println("Connected to Calculator Server");
            
            int result1 = calculator.add(10, 5);
            System.out.println("10 + 5 = " + result1);
            
            int result2 = calculator.multiply(7, 8);
            System.out.println("7 * 8 = " + result2);
            
            double result3 = calculator.divide(20, 4);
            System.out.println("20 / 4 = " + result3);
            
            String serverInfo = calculator.getServerInfo();
            System.out.println("Server Info: " + serverInfo);
            
            // Test error handling
            try {
                calculator.divide(10, 0);
            } catch (RemoteException e) {
                System.out.println("Error: " + e.getMessage());
            }
            
        } catch (RemoteException e) {
            System.err.println("Client error: " + e.getMessage());
        } catch (NotBoundException e) {
            System.err.println("Service not found: " + e.getMessage());
        }
    }
}

Client Output:

Connected to Calculator Server

10 + 5 = 15

7 * 8 = 56

20 / 4 = 5.0

Server Info: Calculator Server v1.0 - Running on Windows 10

🔹 RMI with Custom Objects

Pass custom objects between client and server:

import java.io.Serializable;

// Custom class must implement Serializable
public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String name;
    private int age;
    private String email;
    
    public Person(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }
    
    // Getters and setters
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", email='" + email + "'}";
    }
}

// Updated interface with custom object methods
public interface PersonService extends Remote {
    void addPerson(Person person) throws RemoteException;
    Person getPerson(String name) throws RemoteException;
    java.util.List getAllPersons() throws RemoteException;
}

🔹 RMI Security

Configure security for RMI applications:

import java.rmi.RMISecurityManager;

public class SecureRMIServer {
    public static void main(String[] args) {
        // Set security manager (optional but recommended)
        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new RMISecurityManager());
        }
        
        // Set system properties for RMI
        System.setProperty("java.rmi.server.hostname", "localhost");
        System.setProperty("java.security.policy", "server.policy");
        
        try {
            Calculator calculator = new CalculatorImpl();
            Registry registry = LocateRegistry.createRegistry(1099);
            registry.rebind("Calculator", calculator);
            
            System.out.println("Secure RMI Server started");
            
        } catch (Exception e) {
            System.err.println("Server error: " + e.getMessage());
        }
    }
}

RMI Security Policy (server.policy):

grant {
    permission java.security.AllPermission;
};

Note: In production, use more restrictive permissions.

🔹 RMI Best Practices

Important guidelines for RMI development:

Design Guidelines:

  • Interface Design: Keep remote interfaces simple and focused
  • Exception Handling: Always handle RemoteException
  • Serialization: Ensure all parameter/return types are Serializable
  • Performance: Minimize remote method calls

Common Pitfalls:

  • Firewall Issues: RMI uses dynamic ports
  • ClassPath Problems: Ensure all classes are available
  • Registry Location: Client must know server location
  • Object Lifecycle: Manage remote object references properly

🧠 Test Your Knowledge

What must all RMI remote interfaces extend?