Inter-service Communication Patterns in Java Microservices: REST, RPC, and Async Messaging Explained

Java microservices use three fundamental inter-service communication patterns: synchronous REST HTTP for stateless request-response, synchronous RPC over TCP for high-performance binary calls, and asynchronous message brokers for event-driven decoupling.

Inter-service communication patterns in Java microservices determine how distributed components exchange data while maintaining loose coupling and high availability. The doocs/advanced-java repository provides concrete implementations demonstrating RESTful HTTP, custom socket-based RPC, and message broker integration strategies. Understanding these patterns helps architects choose the right protocol for specific latency requirements, payload sizes, and consistency models.

Synchronous Communication Patterns

Synchronous patterns block the caller until the callee returns a response, making them suitable for real-time queries and immediate consistency requirements.

REST HTTP Communication

REST over HTTP is the most common pattern, using standard verbs (GET, POST, PUT, DELETE) and JSON payloads. In docs/micro-services/what's-microservice-how-to-communicate.md (lines 27-34), the repository demonstrates a basic provider service:

@RestController
@RequestMapping("/communication")
public class RestControllerDemo {
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

The consumer uses Spring Boot's RestTemplate to invoke the endpoint synchronously. The example at lines 40-50 shows:

@RestController
@RequestMapping("/demo")
public class RestDemo {
    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/hello2")
    public String hello2() {
        String result = restTemplate.getForObject(
            "http://localhost:9013/communication/hello", String.class);
        return result;
    }
}

Key implementation details:

  • The caller blocks on getForObject() until the HTTP response arrives.
  • Spring Boot automatically handles String or JSON conversion via HttpMessageConverters.
  • Production deployments typically replace hardcoded URLs (localhost:9013) with service discovery (Eureka) and client-side load balancing (Spring Cloud LoadBalancer).

RPC over TCP Communication

RPC (Remote Procedure Call) hides network complexity behind Java interfaces, serializing method invocations over binary TCP connections for lower latency than HTTP/JSON.

The repository provides a minimal socket-based RPC framework in docs/micro-services/what's-microservice-how-to-communicate.md (lines 75-176) to illustrate the concept.

Server-side implementation: The RPCServer class registers interface implementations and listens on a specific port:

public class RPCServer {
    private static final ExecutorService executor = Executors.newFixedThreadPool(10);
    private static final ConcurrentHashMap<String, Class> serviceRegister = new ConcurrentHashMap<>();

    public void register(Class service, Class impl) {
        serviceRegister.put(service.getSimpleName(), impl);
    }

    public void start(int port) {
        try (ServerSocket server = new ServerSocket(port)) {
            while (true) {
                Socket socket = server.accept();
                executor.execute(new ServiceTask(socket, serviceRegister));
            }
        }
    }
}

Client-side dynamic proxy: The RPCclient class (lines 81-130) creates a proxy that serializes the method call over the socket:

public class RPCclient<T> {
    public static <T> T getRemoteProxyObj(final Class<T> service,
                                           final InetSocketAddress addr) {
        return (T) Proxy.newProxyInstance(
            service.getClassLoader(),
            new Class<?>[]{service},
            (proxy, method, args) -> {
                try (Socket socket = new Socket();
                     ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
                     ObjectInputStream in = new ObjectInputStream(socket.getInputStream())) {
                    socket.connect(addr);
                    out.writeUTF(service.getSimpleName());
                    out.writeUTF(method.getName());
                    out.writeObject(method.getParameterTypes());
                    out.writeObject(args);
                    return in.readObject();
                }
            });
    }
}

Service contract and test: The interface and implementation (lines 38-48) define the business logic:

public interface Tinterface {
    String send(String msg);
}

public class TinterfaceImpl implements Tinterface {
    @Override
    public String send(String msg) {
        return "send message " + msg;
    }
}

The test driver (lines 58-70) wires everything together on port 10000:

public class RunTest {
    public static void main(String[] args) {
        new Thread(() -> {
            RPCServer rpcServer = new RPCServer();
            rpcServer.register(Tinterface.class, TinterfaceImpl.class);
            rpcServer.start(10000);
        }).start();

        Tinterface proxy = RPCclient.getRemoteProxyObj(
                Tinterface.class, new InetSocketAddress("localhost", 10000));
        System.out.println(proxy.send("rpc 测试用例"));
    }
}

Key implementation details:

  • The framework uses Java dynamic proxies (Proxy.newProxyInstance) to intercept interface methods.
  • Serialization relies on standard Java ObjectOutputStream and ObjectInputStream, transmitting the service name, method name, parameter types, and arguments.
  • The server dispatches to the registered implementation using a thread pool (ExecutorService) to handle concurrent requests.

Asynchronous Messaging Patterns

Asynchronous communication decouples services in time, allowing producers to continue processing without waiting for consumers.

Message Broker Integration

While the repository focuses on synchronous examples, it documents that production Java microservices typically adopt message brokers such as Apache Kafka, RabbitMQ, RocketMQ, or ActiveMQ for asynchronous flows.

A typical Spring Boot implementation uses Spring Kafka:

@KafkaListener(topics = "order-events")
public void handleOrderEvent(String payload) {
    // Process event asynchronously
}

And the corresponding producer:

@Component
public class OrderPublisher {
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    public void publish(String orderJson) {
        kafkaTemplate.send("order-events", orderJson);
    }
}

Key characteristics:

  • Temporal decoupling: Producers and consumers operate independently; consumers can be down without affecting producers.
  • Scalability: Consumer groups allow parallel processing by adding instances.
  • Durability: Brokers persist messages, enabling replay and recovery.
  • Protocols: Java clients typically use AMQP (RabbitMQ), Kafka protocol, or MQTT for IoT scenarios.

Summary

  • REST HTTP provides the simplest inter-service communication pattern for Java microservices, using standard Spring Boot annotations (@RestController, RestTemplate) and JSON payloads over port 80/443.
  • RPC over TCP offers higher performance and type safety by serializing Java method calls through binary sockets, as demonstrated by the custom RPCServer and RPCclient implementation in docs/micro-services/what's-microservice-how-to-communicate.md.
  • Asynchronous messaging via Kafka, RabbitMQ, or RocketMQ decouples services temporally, enabling event-driven architectures that handle traffic spikes and consumer failures gracefully.
  • Choose REST for external APIs and simple CRUD, RPC for internal high-throughput Java-to-Java calls, and messaging for eventual consistency and event sourcing.

Frequently Asked Questions

What is the difference between REST and RPC in Java microservices?

REST uses HTTP verbs and JSON over port 80/443, optimizing for interoperability and caching, while RPC (such as the socket-based example in the repository) transmits binary method invocations over TCP for lower latency and stronger type safety. REST is better for public-facing APIs, whereas RPC suits internal service meshes where both endpoints run Java.

When should I use asynchronous messaging instead of synchronous calls?

Use asynchronous messaging (Kafka, RabbitMQ) when services must remain available independently, when handling traffic spikes that could overwhelm synchronous consumers, or when implementing event sourcing and eventual consistency models. Synchronous REST or RPC creates temporal coupling that causes cascading failures if the downstream service is slow or unavailable.

How does the custom RPC implementation in doocs/advanced-java handle serialization?

The RPCclient class uses Java dynamic proxies to intercept interface methods, then serializes the service name, method name, parameter types, and arguments using ObjectOutputStream over a raw TCP socket. The RPCServer deserializes these components via ObjectInputStream, looks up the implementation in a ConcurrentHashMap registry, and returns the result object back through the socket stream.

Can I combine multiple communication patterns in the same microservice architecture?

Yes, production Java architectures typically mix patterns: REST for edge services exposing APIs to mobile or web clients, RPC (gRPC, Dubbo, or the custom socket example) for high-frequency internal calls between backend services, and asynchronous messaging for background processing, audit trails, and cross-domain event publishing. The choice depends on the latency, coupling, and consistency requirements of each specific interaction.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →