DevNotes
Support
Microservices Fundamentals · Chapter 03

Inter-Service Communication

Synchronous vs asynchronous communication, REST with RestClient, WebClient and OpenFeign, gRPC, timeouts and connection pools, propagating headers, idempotency, API versioning, contract testing, and choosing between calls and events.

◷ 6 min read§ 12 sections? 5 interview questions{ } 4 code examples

#Introduction

Once a system is split into services, they have to talk. The choice between synchronous calls and asynchronous events shapes latency, coupling and how failures spread.

#Synchronous vs Asynchronous

Synchronous (request/response)Asynchronous (events / messages)
REST, gRPC, GraphQLKafka, RabbitMQ, SQS
Caller waits for the answerCaller publishes and continues
Simple to reason about; immediate resultEventual consistency; the result arrives later
Temporal coupling — both services must be upProducer and consumer don't need to be up at the same time
Failures and latency propagate along the call chainA slow consumer just builds a backlog
Good for queries and commands needing an immediate answerGood for notifications, workflows, data replication
Does the caller need the answer right now to continue?
synchronous call — with a timeout, retries and a circuit breaker
publish an event — Kafka or RabbitMQ

#REST with Spring

RestClient (Spring Framework 6.1+)

The modern synchronous HTTP client, with a fluent API:

Java
@Configuration
public class ClientConfig {

    @Bean
    RestClient inventoryClient(RestClient.Builder builder) {
        return builder
                .baseUrl("http://inventory-service")
                .requestFactory(timeouts())
                .build();
    }

    private ClientHttpRequestFactory timeouts() {
        var factory = new SimpleClientHttpRequestFactory();
        factory.setConnectTimeout(Duration.ofSeconds(2));
        factory.setReadTimeout(Duration.ofSeconds(3));
        return factory;
    }
}
Java
@Service
public class InventoryGateway {

    private final RestClient inventoryClient;

    public InventoryGateway(RestClient inventoryClient) {
        this.inventoryClient = inventoryClient;
    }

    public StockLevel stockFor(String sku) {
        return inventoryClient.get()
                .uri("/api/stock/{sku}", sku)
                .retrieve()
                .onStatus(HttpStatusCode::is4xxClientError,
                        (req, res) -> { throw new ProductNotFoundException(sku); })
                .body(StockLevel.class);
    }
}

RestTemplate still works but is in maintenance mode — prefer RestClient for new synchronous code.

WebClient (Reactive)

Non-blocking, for Spring WebFlux applications or when making many concurrent calls:

Java
Mono<StockLevel> stock = webClient.get()
        .uri("/api/stock/{sku}", sku)
        .retrieve()
        .bodyToMono(StockLevel.class)
        .timeout(Duration.ofSeconds(3));

OpenFeign — Declarative Clients

Write an interface; Spring Cloud generates the client:

Java
@SpringBootApplication
@EnableFeignClients
public class OrderServiceApplication { }

@FeignClient(name = "inventory-service")          // resolved via service discovery
public interface InventoryClient {

    @GetMapping("/api/stock/{sku}")
    StockLevel stockFor(@PathVariable String sku);

    @PostMapping("/api/reservations")
    Reservation reserve(@RequestBody ReservationRequest request);
}
YAML
spring:
  cloud:
    openfeign:
      client:
        config:
          default:
            connect-timeout: 2000
            read-timeout: 3000

#gRPC

gRPC uses HTTP/2 and Protocol Buffers for compact, typed, fast service-to-service calls:

Text
service InventoryService {
  rpc GetStock (StockRequest) returns (StockReply);
}
REST + JSONgRPC
Human-readable, easy to debug with curlBinary, needs tooling
Universal browser and tool supportLimited browser support (gRPC-Web)
Loosely typedStrongly typed contracts, generated code
HTTP/1.1 or 2HTTP/2, streaming in both directions
Best for public APIsBest for high-volume internal calls

#Timeouts Are Mandatory

Without a timeout, a slow downstream service holds your threads until they run out — and your service fails too.

TimeoutMeaningTypical value
Connect timeoutTime to establish a TCP connection1–2 s
Read (response) timeoutTime to wait for the responseBased on the downstream's p99 latency + margin, e.g. 2–5 s
Overall deadlineTotal time budget for the whole request, including retriesSet at the edge and passed down

Also configure a connection pool (Apache HttpClient or Reactor Netty) with sensible limits, so each call doesn't open a new TCP/TLS connection.

#Propagating Context

Downstream calls should carry:

  • the authentication token (or a service token) — see Security in Microservices;
  • a correlation / trace ID so logs and traces link up — Micrometer Tracing propagates W3C traceparent headers automatically with Spring's clients;
  • the user's locale or tenant ID where relevant.

#Idempotency for Retries

A retried POST /payments after a timeout could charge twice — the first request may actually have succeeded. Make unsafe operations idempotent with an idempotency key:

HTTP
POST /api/payments
Idempotency-Key: 6f1c2a7e-9a3b-4f0e-b1d2-3c4d5e6f7a8b

The server stores the key with the result; a repeat with the same key returns the stored result instead of charging again.

#API Versioning and Compatibility

Services are deployed independently, so APIs must evolve without breaking callers:

  • Additive changes are safe — new optional fields, new endpoints.
  • Tolerant readers — clients ignore unknown fields (Jackson: FAIL_ON_UNKNOWN_PROPERTIES=false, Spring Boot's default).
  • Breaking changes get a new version — /api/v2/orders or a version header — and the old version is retired only after all callers move.

#Contract Testing

How do you know a provider's change won't break its consumers? Consumer-driven contract tests (Pact, Spring Cloud Contract) record what each consumer expects; the provider's build verifies it still satisfies every contract — catching breaking changes before deployment, without spinning up every service.

#Avoiding Synchronous Call Chains

Gateway
Order service
Pricing service
Discount service
Customer service

If each hop is 99.9% available, five hops in series give about 99.5%, and latencies add up. Reduce chains by:

  • keeping a local copy of data you need (updated from events);
  • calling services in parallel rather than in sequence (CompletableFuture, Mono.zip);
  • moving non-essential work to events;
  • revisiting boundaries if two services always call each other.

#Interview Questions

Q1Synchronous vs asynchronous communication — when do you use each?

Synchronous (REST/gRPC) when the caller needs an immediate answer; asynchronous (events through Kafka or RabbitMQ) for state changes other services react to, long-running workflows and decoupling from consumer availability.

Q2RestTemplate vs RestClient vs WebClient vs Feign?

RestTemplate is the older synchronous client (maintenance mode); RestClient is its modern fluent replacement; WebClient is reactive and non-blocking; OpenFeign (or @HttpExchange interfaces) generates clients from annotated interfaces.

Q3Why are timeouts important?

Without them, slow dependencies exhaust threads and connections and failures cascade. Every remote call needs connect and read timeouts, ideally within an overall deadline.

Q4How do you make retries safe?

Retry only idempotent operations, or make unsafe ones idempotent with idempotency keys, and combine retries with back-off and a circuit breaker.

Q5What is contract testing?

Testing that a provider's API still meets the expectations recorded by its consumers (Pact, Spring Cloud Contract), so breaking changes are caught in the build.

#Key Takeaways

  • Use synchronous calls for immediate answers, events for reactions and workflows.
  • Prefer RestClient, WebClient or declarative clients (OpenFeign, @HttpExchange); gRPC for fast internal calls.
  • Always set connect and read timeouts and use connection pools.
  • Propagate trace IDs and credentials; make retried writes idempotent.
  • Evolve APIs additively; version breaking changes; verify with contract tests.
  • Avoid long synchronous call chains.