Remote Procedure Invocation in Java: REST, gRPC, and Beyond

Illustration for Remote Procedure Invocation in Java: REST, gRPC, and Beyond
By Last updated:

Introduction

Imagine calling a method that runs on another machine as if it were a local method. That’s the core idea behind the Remote Procedure Invocation (RPI) pattern. Whether you're building microservices with REST APIs or high-performance backend systems with gRPC, RPI helps abstract the complexities of network communication.

This pattern is foundational in modern distributed systems, cloud-native applications, and service-oriented architectures (SOA). It allows developers to focus on business logic while the underlying framework handles serialization, communication, and service discovery.

In this tutorial, we’ll explore the Remote Procedure Invocation pattern in depth — from REST to gRPC, and beyond.


📌 What Is the Remote Procedure Invocation Pattern?

The Remote Procedure Invocation (RPI) pattern enables an application to execute methods on a remote object as though they were local. It hides the network, serialization, and transport logic behind well-defined interfaces.

UML Structure (Text-Based)

Client                Stub/Proxy            Network Transport         Remote Service
  |                        |                        |                        |
  | -- call(method) ----> |                        |                        |
  |                        | -- serialize + send ->|                        |
  |                        |                        | -- route ------------>|
  |                        |                        |                        |-- execute()
  |                        |                        |<-- response --------- |
  |<-- deserialize ------- |                        |                        |

Key Participants

  • Client: Invokes the method as if it's local.
  • Stub/Proxy: Acts as a local representative of the remote service.
  • Transport Layer: Manages serialization and communication.
  • Remote Service: Executes the actual logic and sends back a response.

🚀 Real-World Use Cases

  • Microservice communication in Spring Boot using REST.
  • High-performance internal APIs using gRPC in fintech and game servers.
  • Language-agnostic services (Java backend, Python frontend) with Protocol Buffers.
  • Remote management services like Kubernetes APIs.
  • Mobile apps calling cloud-based APIs.

🧰 Common Implementation Strategies in Java

1. RESTful APIs (Spring Boot)

// Controller in Service A
@RestController
public class GreetingController {
  @GetMapping("/greet")
  public String greet() {
    return "Hello from Service A";
  }
}

// Calling from Service B using RestTemplate
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject("http://service-a/greet", String.class);

2. gRPC with Protocol Buffers

greeting.proto

syntax = "proto3";
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
  string name = 1;
}
message HelloReply {
  string message = 1;
}

Server-side (Java)

public class GreeterImpl extends GreeterGrpc.GreeterImplBase {
  public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
    HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
    responseObserver.onNext(reply);
    responseObserver.onCompleted();
  }
}

Client-side (Java)

ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051).usePlaintext().build();
GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);
HelloReply response = stub.sayHello(HelloRequest.newBuilder().setName("John").build());

✅ Pros and Cons

Pros Cons
Abstraction over network calls Network latency and failures
Language/platform interoperability Requires contracts/schemas
Loose coupling Debugging is more complex than local calls
Scalability and separation of concerns Versioning and backward compatibility needed

⚠️ Anti-Patterns & Misuse Cases

  • Too Chatty Communication: Frequent small RPC calls instead of batching data.
  • No Timeouts/Retry: Missing resilience in failure scenarios.
  • Direct coupling to transport: Hardcoding HTTP or gRPC logic in business layers.
  • Ignoring versioning: Breaking clients after schema changes.

Pattern Purpose
Remote Procedure Invocation Execute remote code as if it’s local
Factory / Abstract Factory Create objects without exposing instantiation logic
Proxy Pattern Control access or add behavior to an object
Observer Pattern Notify multiple observers of state changes

Proxy vs RPI: Proxy can be used locally or remotely, while RPI must be remote and uses a proxy/stub to represent remote logic.


🔧 Refactoring Legacy Code Using RPI

Problem:

Legacy code using direct JDBC calls now needs to be moved to a microservice.

Refactor:

  • Create a REST/gRPC interface for DB access.
  • Migrate logic to a new service.
  • Replace JDBC calls with API invocations.

Before:

Connection conn = DriverManager.getConnection(...);
ResultSet rs = conn.prepareStatement("SELECT * FROM users").executeQuery();

After:

User user = restTemplate.getForObject("http://user-service/users/1", User.class);

💡 Best Practices

  • Define a clear API contract (OpenAPI or Protobuf)
  • Separate business logic from transport logic
  • Use client libraries or SDKs to wrap remote calls
  • Add retry, timeout, and circuit breaker (Resilience4j)
  • Version your APIs from day one (/v1/endpoint)
  • Use testing stubs/mocks for isolation in unit tests

🧠 Real-World Analogy

Imagine ordering food via a food delivery app:

  • You (client) place an order via the app (proxy).
  • The app talks to the restaurant (remote service).
  • You get your food delivered (response), without knowing all the internal routing or logistics (transport layer).

This is how RPI works — you interact with a proxy, not worrying how the actual task is executed behind the scenes.


☕ Java Feature Relevance

  • Java Records: Perfect for DTOs in REST/gRPC.
  • Sealed Interfaces (Java 17+): Enforce response types.
  • Lambdas/Streams: Process responses efficiently.

🔚 Conclusion & Key Takeaways

The Remote Procedure Invocation pattern is essential for building distributed systems in Java. With tools like REST and gRPC, it abstracts away transport details, helping developers create modular, scalable, and maintainable architectures.

✅ Key Takeaways:

  • Use RPI to invoke remote logic as if it were local.
  • REST and gRPC are the two most common implementations in Java.
  • Always include contracts, resilience, and abstraction.
  • Proxy/stub is the hero of this pattern.

❓ FAQ – Remote Procedure Invocation in Java

1. What is the RPI pattern in Java?

It's a design pattern that enables invoking methods across network boundaries using proxies.

2. How is RPI different from the Proxy Pattern?

RPI always involves remote communication, while Proxy can be local or remote.

3. Which is better: REST or gRPC?

REST is simpler and widely adopted. gRPC is faster and better for internal microservices.

4. Can I use RPI for real-time apps?

gRPC (with streaming) works well for real-time scenarios.

5. What are common Java tools for RPI?

Spring Boot (REST), gRPC-Java, Retrofit, Feign, OpenFeign.

6. Is REST still relevant in 2025?

Absolutely. REST is still dominant for public APIs.

7. Should I write my own proxy logic?

No. Use tools like Feign or gRPC generated stubs.

8. How do I handle versioning?

Use URI-based versioning or media-type headers.

9. What are some performance tips?

Use connection pooling, compress payloads, and avoid chatty APIs.

10. Is RPI secure?

Only if you use HTTPS, authentication, and input validation.