Remote Method Invocation (RMI) in JAVA

Hey there! Welcome to KnowledgeKnot! Don't forget to share this with your friends and revisit often. Your support motivates us to create more content in the future. Thanks for being awesome!

Remote Method Invocation (RMI)

Remote Method Invocation (RMI) is a Java API that allows an object residing in one Java Virtual Machine (JVM) to invoke methods on an object residing in another JVM. This architecture enables distributed computing and allows Java applications to communicate over a network. Here is a real-world scenario where invoking methods on an object in another JVM would be necessary:

For Example: A banking system with multiple branches located in different cities. Each branch has its own local server running a JVM that manages the branch's transactions, customer records, and other banking operations. To ensure consistency and provide a seamless customer experience, these servers need to communicate with a central server that aggregates data from all branches and performs higher-level operations such as generating consolidated reports, validating inter-branch transactions, and managing overall bank policies.

When a customer wants to transfer money from their account in one branch (Branch A) to another account in a different branch (Branch B). This operation requires coordination between the two branch servers and the central server to ensure that funds are debited from the source account and credited to the destination account accurately and securely.

Here RMI architecture comes into picture to solve the problem:
β†’Step 1: The customer initiates the transfer at Branch A.
β†’Step 2: The local server at Branch A uses RMI to call a method on the central server to validate the transaction and lock the necessary funds.
β†’Step 3: Once validated, the central server uses RMI to communicate with the server at Branch B to ensure that the destination account exists and can receive the funds.
β†’Step 4: The central server then coordinates the actual transfer by debiting the source account on Branch A's server and crediting the destination account on Branch B's server.
β†’Step 5: Both branches confirm the transaction's completion, and the central server updates the overall records to reflect the transfer.

Code implementation for the above example:

β†’ Defining the Remote Interface:

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

public interface BankServer extends Remote {
    boolean validateAndLockFunds(String accountNumber, double amount) throws RemoteException;
    boolean creditFunds(String accountNumber, double amount) throws RemoteException;
    void completeTransfer(String sourceAccount, String destinationAccount, double amount) throws RemoteException;
}

β†’ Implementing the Remote Interface:

import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Map;

public class BankServerImpl extends UnicastRemoteObject implements BankServer {
    private Map<String, Double> accounts;

    public BankServerImpl() throws RemoteException {
        super();
        accounts = new HashMap<>();
        // Initialize accounts for demonstration
        accounts.put("A123", 1000.0);
        accounts.put("B456", 2000.0);
    }

    public synchronized boolean validateAndLockFunds(String accountNumber, double amount) throws RemoteException {
        if (accounts.containsKey(accountNumber) && accounts.get(accountNumber) >= amount) {
            // Lock funds by debiting temporarily
            accounts.put(accountNumber, accounts.get(accountNumber) - amount);
            return true;
        }
        return false;
    }

    public synchronized boolean creditFunds(String accountNumber, double amount) throws RemoteException {
        if (accounts.containsKey(accountNumber)) {
            accounts.put(accountNumber, accounts.get(accountNumber) + amount);
            return true;
        }
        return false;
    }

    public synchronized void completeTransfer(String sourceAccount, String destinationAccount, double amount) throws RemoteException {
        if (validateAndLockFunds(sourceAccount, amount)) {
            if (creditFunds(destinationAccount, amount)) {
                System.out.println("Transfer complete: " + amount + " from " + sourceAccount + " to " + destinationAccount);
            } else {
                // Rollback in case of failure
                accounts.put(sourceAccount, accounts.get(sourceAccount) + amount);
                throw new RemoteException("Failed to credit funds to destination account");
            }
        } else {
            throw new RemoteException("Insufficient funds or invalid source account");
        }
    }
}

β†’ RMI Server:

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

public class BankServerMain {
    public static void main(String[] args) {
        try {
            BankServerImpl bankServer = new BankServerImpl();
            // Create RMI registry on port 1099
            Registry registry = LocateRegistry.createRegistry(1099);
            registry.bind("BankServer", bankServer);
            System.out.println("BankServer is ready.");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

β†’ RMI Client:

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

public class BankClient {
    public static void main(String[] args) {
        try {
            // Locate RMI registry on localhost and port 1099
            Registry registry = LocateRegistry.getRegistry("localhost", 1099);
            BankServer bankServer = (BankServer) registry.lookup("BankServer");

            String sourceAccount = "A123";
            String destinationAccount = "B456";
            double amount = 100.0;

            bankServer.completeTransfer(sourceAccount, destinationAccount, amount);
            System.out.println("Transfer successful!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Key Components of RMI Architecture

RMI Architecture

1. Client: The client is the application that initiates a method call on a remote object. In our scenario, the client would be the local server at Branch A, where the customer initiates the funds transfer.

2. Server: The server hosts the remote objects whose methods can be invoked remotely. In this case, the central server acts as the RMI server, containing the logic to validate and process the funds transfer.

3. Remote Object: A remote object is an object whose methods can be invoked from another JVM. The BankServerImpl class in our example is the remote object. It provides the actual implementation of the methods defined in the remote interface and resides on the central server.

4. Remote Interface: The remote interface defines the methods that can be invoked remotely by the client. It extends the java.rmi.Remote interface. In our scenario, the BankServer interface declares methods like validateAndLockFunds, creditFunds, and completeTransfer.

5. Stub and Skeleton:

  • Stub: The stub acts as a proxy for the remote object on the client side. It forwards the client’s request to the skeleton. In our scenario, when the local server at Branch A invokes a method on the central server, it communicates with the stub, which then forwards the request to the remote object on the central server.
  • Skeleton: The skeleton resides on the server side. It receives the request from the stub, invokes the actual method on the remote object, and returns the result to the stub. In our example, the skeleton on the central server receives the request to validate and transfer funds, processes it using the BankServerImpl methods, and sends the result back to the stub on the client side.

6. RMI Registry:
The RMI registry is a simple naming service that allows clients to obtain references (or stubs) to remote objects. It runs on a specific port and provides a registry of remote objects for clients to look up and use.