Inter-Service Communication
Services must talk to each other. The first decision is synchronous (caller waits for a reply) versus asynchronous (caller sends a message and moves on). Everything else follows from that.
| Synchronous (REST, gRPC) | Asynchronous (Kafka, RabbitMQ) | |
|---|---|---|
| Coupling | Temporal: callee must be up | Loose: broker buffers |
| Latency to get answer | Immediate | Eventual |
| Failure handling | Timeouts, retries, circuit breakers | Retries, dead-letter queues |
| Good for | Queries, validation needing an answer now | Events, workflows, fan-out, spikes |
A long chain of synchronous calls multiplies failure probability and latency. If A calls B calls C and each is 99.9% available, the chain is about 99.7%, and any slow link slows everything.
Definition
Services talk in two ways. Synchronous calls (REST or gRPC) wait for an answer. Asynchronous messages (Kafka, RabbitMQ) are sent and the sender moves on. Use a call when you need the answer now, and a message when the work can finish later.
Why it exists
A business flow needs several services to cooperate, but each runs separately and can fail on its own. Different styles trade speed, coupling and reliability.
How it works
gRPC
gRPC uses HTTP/2 and Protocol Buffers: compact binary messages, strongly typed contracts generated from .proto files, streaming (server, client, bidirectional) and built-in deadlines.
syntax = "proto3";
service Inventory {
rpc GetStock (StockRequest) returns (StockReply);
}
message StockRequest { string sku = 1; }
message StockReply { int32 available = 1; }
Pick gRPC for internal, high-throughput, low-latency calls and polyglot services. Choose REST/JSON for public APIs and browser clients (browsers need gRPC-Web). Evolve protobuf schemas by adding new field numbers; never reuse or renumber.
Messaging
Publish events when something happened (OrderPlaced) rather than commanding others, so consumers can be added without changing the producer. Delivery is usually at-least-once, so consumers must be idempotent. Kafka and RabbitMQ are covered in the Messaging section.
Example code
REST with RestClient
RestClient (Spring Framework 6.1) is the modern synchronous client, replacing most uses of RestTemplate.
@Service
class PricingClient {
private final RestClient client;
PricingClient(RestClient.Builder builder) {
this.client = builder
.baseUrl("http://pricing-service")
.build();
}
BigDecimal priceOf(String sku) {
return client.get()
.uri("/prices/{sku}", sku)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (req, res) -> {
throw new UnknownProductException(sku);
})
.body(BigDecimal.class);
}
}
Always configure connect and read timeouts on the underlying request factory; the defaults can wait indefinitely.
Declarative clients: HTTP interfaces and OpenFeign
Spring 6 offers HTTP interface clients; Spring Cloud OpenFeign is the long-standing alternative integrated with discovery and load balancing.
@FeignClient(name = "inventory-service")
public interface InventoryApi {
@GetMapping("/stock/{sku}")
StockDto stock(@PathVariable("sku") String sku);
@PostMapping("/reservations")
ReservationDto reserve(@RequestBody ReserveRequest request);
}
Enable with @EnableFeignClients, inject InventoryApi like any bean. Feign resolves inventory-service through the load balancer. Configure timeouts (spring.cloud.openfeign.client.config.default.connect-timeout/read-timeout) and error decoding explicitly.
System design considerations
- Set timeouts, retries with backoff and circuit breakers on every remote call
- Version APIs and events and keep changes backward compatible
- Make consumers idempotent, since delivery is at least once
API compatibility
Services deploy independently, so contracts must evolve compatibly: add optional fields, never remove or repurpose them, tolerate unknown fields (FAIL_ON_UNKNOWN_PROPERTIES=false is Boot's default), version when breaking changes are unavoidable, and consider consumer-driven contract tests (Spring Cloud Contract, Pact).
When to use it, and when not
Use it when
- The caller needs an immediate answer (REST or gRPC with timeouts)
- Other services should react to a fact without the producer knowing them (events)
- You need efficient internal calls with strong contracts (gRPC)
Think twice when
- You chain many synchronous calls on a user request path
- You use messaging where an immediate result is required
- You change contracts without a compatibility plan
Choosing
| Situation | Prefer |
|---|---|
| Need the answer to continue (price lookup) | Synchronous REST or gRPC, with timeout and fallback |
| Notify many consumers of a fact | Event via broker |
| Long-running business process across services | Saga over events |
| Public API for third parties | REST + OpenAPI |
| Internal high-volume, polyglot | gRPC |
Common mistakes
- No timeouts, or timeouts longer than the caller's own.
- Retrying non-idempotent operations (double charges).
- Sharing DTO libraries between services, which silently couples release cycles.
- Using a message broker as a synchronous RPC substitute (request/reply everywhere).
Interview questions
1. REST vs gRPC?
REST/JSON is human-readable, universal and cache-friendly; gRPC is faster, typed and supports streaming but needs tooling and is awkward for browsers.
2. Synchronous vs asynchronous: how do you choose?
Use synchronous when the caller cannot proceed without the answer; use asynchronous for events, long processes and to decouple availability. Prefer async between core domain services to avoid call chains.
3. What is OpenFeign and what does it add over RestTemplate?
A declarative client: define an interface with annotations and Feign generates the implementation, integrated with discovery, load balancing, and resilience. RestClient and HTTP interface clients offer similar ergonomics without Spring Cloud.
4. How do you handle a downstream service being slow?
Timeouts, bulkheads to limit concurrent calls, circuit breakers, retries with backoff for idempotent calls, and fallbacks. See Resilience4j.
5. How do you prevent breaking consumers?
Backward-compatible changes, versioned APIs, contract tests, and consumer-driven contracts in CI.
6. What is the difference between orchestration and choreography?
Orchestration: a central coordinator tells services what to do. Choreography: services react to each other's events. Details in Saga.
Check yourself
1When is asynchronous messaging a better fit than REST?
2What should every remote call have?
3Which change is generally backward compatible for a JSON API?
Key takeaways
- Default to events for cross-domain notifications; use sync calls sparingly and defensively.
- REST for public/simple APIs, gRPC for internal performance.
- Always set timeouts, and design contracts to evolve compatibly.
Finished this page? Track your progress (saved only in your browser).