BIT401 Advanced Java Programming

Advanced Java ProgrammingUnit 911 min read

Remote Method Invocation (RMI): Architecture, Implementation & Real-World Use

Unit 9 of Advanced Java Programming explores how Java’s RMI enables distributed objects to communicate across networks, covering its architecture, stub/skeleton generation, security, and practical applications in banking, e-commerce, and cloud services. This note includes step-by-step implementation, real-world example

TAKEAWAYS:

  • RMI lets Java objects invoke methods on remote machines as if they were local, using stubs, skeletons, and a registry.
  • The architecture follows a client-server model with RMI runtime (rmiregistry, RMI compiler, and transport layers).
  • Interfaces are mandatory in RMI to define remote methods, and classes must extend java.rmi.Remote and declare exceptions as RemoteException.
  • Security requires policy files (server.policy, client.policy) to grant permissions for remote access.
  • Real-world uses include Ncell’s remote billing systems, eSewa’s distributed transaction processing, and Google’s internal microservices.
  • Debugging RMI issues requires checking firewall settings, port conflicts (default: 1099), and class version mismatches.

Core Concepts of RMI

Remote Method Invocation (RMI) is a Java API that allows objects on different JVMs (even on different machines) to communicate seamlessly. Unlike traditional network programming (e.g., sockets), RMI abstracts away low-level details like serialization, marshalling, and transport, making distributed programming intuitive.

How RMI Works: The Magic Behind the Scenes

When a client calls a remote method, the following steps occur (visualized below):

flowchart TD
    A["Client JVM"] -->|"1. Method Call"| B["Stub (Local Proxy)"]
    B -->|"2. Serialization"| C["Transport Layer (TCP/IP)"]
    C -->|"3. Remote JVM"| D["Skeleton (Remote Object)"]
    D -->|"4. Deserialization"| E["Remote Object"]
    E -->|"5. Method Execution"| F["Result"]
    F -->|"6. Serialization"| C
    C -->|"7. Transport"| B
    B -->|"8. Return"| A

Key Components:

  1. Stub: A local proxy that forwards method calls to the remote object.
  2. Skeleton: A server-side object that receives calls and invokes the actual remote object.
  3. Remote Reference Layer (RRL): Handles object references and communication.
  4. Registry (rmiregistry): A name service that binds remote objects to names (default port: 1099).

Step-by-Step RMI Implementation

1. Define the Remote Interface

All remote objects must implement an interface that extends java.rmi.Remote. Methods must declare RemoteException.

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

public interface Calculator extends Remote {
    double add(double a, double b) throws RemoteException;
    double subtract(double a, double b) throws RemoteException;
}

2. Implement the Remote Object

The server class must extend UnicastRemoteObject (for single-JVM instances) or RemoteObject (for multi-JVM).

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

public class CalculatorImpl extends UnicastRemoteObject implements Calculator {
    public CalculatorImpl() throws RemoteException {
        super(); // Generates stub/skeleton
    }

    @Override
    public double add(double a, double b) throws RemoteException {
        return a + b;
    }

    @Override
    public double subtract(double a, double b) throws RemoteException {
        return a - b;
    }
}

3. Create and Export the Remote Object

The server binds the remote object to the registry.

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

public class Server {
    public static void main(String[] args) {
        try {
            Calculator calculator = new CalculatorImpl();
            Registry registry = LocateRegistry.createRegistry(1099);
            registry.rebind("calculator", calculator);
            System.out.println("Server ready");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4. Client-Side Code

The client looks up the remote object and invokes methods.

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

public class Client {
    public static void main(String[] args) {
        try {
            Registry registry = LocateRegistry.getRegistry("localhost", 1099);
            Calculator calculator = (Calculator) registry.lookup("calculator");
            double result = calculator.add(5.0, 3.2);
            System.out.println("Result: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Visualizing RMI in Action: Step-by-Step Trace

Let’s trace the execution of calculator.add(5.0, 3.2) with a state diagram for each step.

State After Client Calls add(5.0, 3.2)

stateDiagram-v2
    [*] --> ClientStub: Method Call
    ClientStub -->|Serializes Args| TransportLayer: [5.0, 3.2]
    TransportLayer --> ServerStub: TCP Packet
    ServerStub --> CalculatorImpl: Deserializes
    CalculatorImpl --> CalculatorImpl: Computes 8.2
    CalculatorImpl --> ServerStub: Serializes Result
    ServerStub --> TransportLayer: TCP Packet
    TransportLayer --> ClientStub: [8.2]
    ClientStub --> Client: Returns 8.2
    Client --> [*]

Network Packet Flow (Simplified)

Client (192.168.1.100:1234) → Server (192.168.1.101:1099)
[TCP Packet: Method=add, Args=[5.0, 3.2], ObjectID=123]
Server → Client
[TCP Packet: Result=8.2, ObjectID=123]

Real-World Applications of RMI

1. Ncell’s Billing System

  • Use Case: Ncell’s central billing server processes transactions from thousands of base stations across Nepal.
  • How RMI Helps:
    • Each base station runs a lightweight client that invokes chargeCustomer() on the central server.
    • Visual: The central server is a remote object bound to rmiregistry on port 1099.
    • Advantage: Avoids polling; clients call methods only when needed (e.g., after a call ends).

2. eSewa’s Distributed Transaction Processing

  • Use Case: When you pay a bill via eSewa, your request is routed to a load-balanced server farm.
  • How RMI Helps:
    • The eSewa app (client) calls processPayment() on a remote PaymentService object.
    • Visual: Multiple PaymentServiceImpl instances run on different servers, all bound to the same registry name ("paymentService").

3. Google’s Internal Microservices

  • Use Case: Google uses RMI-like mechanisms (though now mostly gRPC) for internal services like Ads, Maps, and YouTube.
  • How RMI Helps:
    • A UserProfileService remote object might expose getUserData() for authentication across data centers.
    • Visual: A distributed system diagram with clients in multiple regions calling a global registry.

Security in RMI

RMI requires explicit security policies due to its network nature. Two key files are needed:

1. Server Policy File (server.policy)

grant {
    permission java.security.AllPermission;
};
  • Grants the server full access to local resources (e.g., files, network).

2. Client Policy File (client.policy)

grant {
    permission java.net.SocketPermission "localhost:1099", "connect";
};
  • Restricts the client to only connect to localhost:1099.

How to Enable Security:

  1. Compile with -Djava.security.policy=server.policy (server side).
  2. Run with java -Djava.security.policy=client.policy Client.

Common Pitfalls and Debugging

Issue Cause Solution
java.rmi.ConnectException Firewall blocking port 1099 Open port 1099 in firewall (ufw allow 1099).
ClassNotFoundException Client/server class versions mismatch Ensure both use the same .class files.
AccessControlException Missing policy file Run with -Djava.security.policy=file.policy.
java.rmi.ServerException Stub/skeleton mismatch Recompile with rmic (deprecated in Java 9+).

Note: In Java 9+, rmic is removed. Use dynamic stub generation (default behavior).


RMI vs. Alternatives: Comparison Table

Feature RMI Sockets REST/gRPC Java EE EJB
Ease of Use High (object-oriented) Low (manual serialization) Medium (JSON/XML) High (container-managed)
Performance Medium (Java serialization) High (raw bytes) High (Protocol Buffers) Medium (JNDI overhead)
Language Independence No (Java-only) No (language-specific) Yes (HTTP/JSON) No (Java EE)
Scalability Limited (registry bottleneck) High (manual load balancing) High (stateless) Medium (container-dependent)
Use Case Internal Java apps (e.g., banks) Custom protocols (e.g., games) Public APIs (e.g., YouTube) Enterprise apps (e.g., ERP)

Worked Example: Bank Loan Calculator

Scenario: A bank’s head office wants to expose a LoanCalculator service to branches. Each branch runs a client that calls calculateEMI() remotely.

1. Remote Interface

public interface LoanCalculator extends Remote {
    double calculateEMI(double principal, double rate, int years) throws RemoteException;
}

2. Server Implementation

public class LoanCalculatorImpl extends UnicastRemoteObject implements LoanCalculator {
    public LoanCalculatorImpl() throws RemoteException {
        super();
    }

    @Override
    public double calculateEMI(double p, double r, int y) throws RemoteException {
        double monthlyRate = r / 12 / 100;
        int months = y * 12;
        return (p * monthlyRate) / (1 - Math.pow(1 + monthlyRate, -months));
    }
}

3. Client Usage (Branch Office)

public class BranchClient {
    public static void main(String[] args) {
        try {
            Registry registry = LocateRegistry.getRegistry("bank-server", 1099);
            LoanCalculator calculator = (LoanCalculator) registry.lookup("loanService");
            double emi = calculator.calculateEMI(1000000, 8.5, 5);
            System.out.println("Monthly EMI: Rs." + emi);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

State After calculateEMI(1000000, 8.5, 5)

sequenceDiagram
    participant Branch as Branch Client
    participant Stub as Stub (Local)
    participant Network as TCP/IP
    participant Skeleton as Skeleton (Server)
    participant Server as LoanCalculatorImpl

    Branch->>Stub: calculateEMI(1M, 8.5, 5)
    Stub->>Network: Serialized Args
    Network->>Skeleton: TCP Packet
    Skeleton->>Server: Deserializes
    Server->>Server: Computes EMI = Rs. 20,556.50
    Server->>Skeleton: Serializes Result
    Skeleton->>Network: TCP Packet
    Network->>Stub: Rs. 20,556.50
    Stub->>Branch: Returns EMI

Exam Tip: How to Score Full Marks

  1. Architecture Diagram: Always draw the client-stub-skeleton-server flow in your answer. Label:

    • Stub (client-side proxy)
    • Skeleton (server-side dispatcher)
    • Registry (rmiregistry)
    • Transport layer (TCP/IP).
  2. Code Snippets: Include one complete remote interface and one server/client class in your answer. Use comments to highlight:

    • extends Remote
    • throws RemoteException
    • UnicastRemoteObject constructor.
  3. Security: Mention policy files and their purpose. Example:

    "The server requires server.policy to grant AllPermission, while the client needs SocketPermission for the registry port (1099)."

  4. Real-World Tie-In: Relate RMI to Ncell’s billing or eSewa’s transactions. Example:

    "In Ncell’s system, the BillingServer remote object implements ChargeCustomer, allowing base stations to invoke deductAmount() without direct database access."

  5. Common Errors: List 2-3 pitfalls (e.g., port blocking, class mismatch) with fixes.


Pro Tip: For short-answer questions, use bullet points for the architecture:

  • Client ↔ Stub (local proxy)
  • Stub ↔ Skeleton (via TCP)
  • Skeleton ↔ Remote Object
  • Registry binds names to objects (port 1099).

Based on the TU BIT syllabus for Advanced Java Programming (BIT401), unit 9.

Discussion

Loading…