REST vs gRPC vs Messaging — Inter-Service Communication
Fast recall
Quick revision
Core answer
Understanding of synchronous vs asynchronous communication trade-offs
Defaulting to REST for everything including high-volume internal calls
Mention temporal coupling (sync) vs behavioral coupling (async) as the core trade-off
Interview-ready guide
Interview answer
REST, gRPC, and Kafka are not interchangeable — each solves a different coupling problem, and the right choice depends on who is calling and whether the caller needs an immediate answer. Mature architectures use all three; using one protocol everywhere is the most common architectural mistake.
REST — external facing:
- The default for anything external-facing: browsers, mobile apps, and partners all speak HTTP and JSON natively
- Unmatched tooling — curl, Postman, Swagger, CDN caching; for internal calls under a few hundred RPS, REST is still fine because debugging simplicity outweighs the performance difference
- For internal calls, temporal coupling is the risk — a synchronous chain A→B→C→D makes the slowest service determine everyone's latency
gRPC — internal hot paths:
- Worth the switch when you have high-frequency internal calls — thousands per second — where Protobuf's binary encoding (5–10× smaller than JSON) and HTTP/2 multiplexing provide measurable latency improvement
- Strict schema contracts via
.protofiles catch breaking changes at compile time instead of at 2 AM when deserialization starts failing - Cannot be used for browser clients or third-party partners without a grpc-web proxy
Kafka — decoupled side effects:
- The right choice when the producer doesn't need a response and multiple downstream services react to the same event — publish
OrderPlacedonce, and inventory, notifications, analytics, and fraud all process independently - Breaks temporal coupling entirely — the producer returns immediately; consumers process at their own pace even after an outage and recovery
I recommend REST at the edge, gRPC for internal hot paths where performance is measured, and Kafka for cross-domain events where the producer doesn't need an immediate response.
REST vs gRPC vs Messaging — Inter-Service Communication
Direct Answer
REST for simple synchronous CRUD. gRPC for high-performance internal RPC with strong contracts. Kafka/messaging for async event-driven flows where services must be decoupled.
Code Example
Same business call ("place order") expressed three ways. The right protocol depends on coupling tolerance, not preference.
// ====== 1) REST: simple, universal, synchronous ======
@RestController
@RequestMapping("/orders")
public class OrderController {
private final InventoryClient inventory; // RestTemplate / WebClient
@PostMapping
public OrderDto place(@RequestBody OrderRequest req) {
InventoryReply r = inventory.reserve(req.sku(), req.qty()); // blocks on HTTP
return service.create(req, r.reservationId());
}
}
// ====== 2) gRPC: typed contract, faster, still synchronous ======
// inventory.proto
service Inventory {
rpc Reserve(ReserveRequest) returns (ReserveReply);
}
@GrpcClient("inventory")
private InventoryGrpc.InventoryBlockingStub stub;
public OrderDto place(OrderRequest req) {
ReserveReply r = stub
.withDeadlineAfter(500, TimeUnit.MILLISECONDS) // built-in deadline
.reserve(ReserveRequest.newBuilder()
.setSku(req.sku()).setQty(req.qty()).build());
return service.create(req, r.getReservationId());
}
// ====== 3) Messaging (Kafka): asynchronous, decoupled ======
@PostMapping
public ResponseEntity<?> place(@RequestBody OrderRequest req) {
Order order = service.createPending(req);
kafkaTemplate.send("orders.placed", order.getId().toString(),
new OrderPlaced(order.getId(), req.sku(), req.qty())); // fire-and-forget
return ResponseEntity.accepted().body(Map.of("id", order.getId()));
}
@KafkaListener(topics = "orders.placed", groupId = "inventory")
public void onOrder(OrderPlaced ev) {
inventoryService.reserve(ev.sku(), ev.qty()); // happens later
kafkaTemplate.send("inventory.reserved",
new InventoryReserved(ev.orderId(), UUID.randomUUID().toString()));
}
| Aspect | REST | gRPC | Messaging |
|---|---|---|---|
| Coupling | temporal | temporal | none (broker buffers) |
| Latency overhead | text/JSON, HTTP/1.1 or 2 | binary protobuf, HTTP/2 multiplex | broker hop (10-50 ms) |
| Throughput | medium | high | very high |
| Schema | OpenAPI (loose) | .proto (strict, codegen) | Avro/JSON-Schema in registry |
| Streaming | SSE (one-way) | full bi-di streaming | native pub/sub |
| Failure mode | caller blocks | caller blocks | message stays in queue |
| Best fit | public APIs, browser | high-volume internal services | events, fan-out, async work |
- REST is the right default for external APIs and any boundary where curl, browser, or third-party tooling matters.
- gRPC wins for internal service-to-service traffic at high RPS — typed contracts catch breaking changes at compile time, HTTP/2 multiplexing avoids connection churn, deadlines and cancellation are first-class.
- Messaging breaks temporal coupling. Use it when the producer must not block on the consumer (events, fan-out, work distribution, audit). It introduces eventual consistency — design for it explicitly.
- A mature platform uses all three: REST at the edge, gRPC between core services, Kafka for events. Mixing is normal; pretending one fits everywhere is the anti-pattern.
Common Mistakes & Pitfalls
Defaulting to REST for everything including high-volume internal calls
Check your understanding
Answer these without looking back at the article.
- How do you implement gRPC in Spring Boot?
- What is the difference between at-least-once and exactly-once delivery in Kafka?
- How do you handle versioning in a gRPC API?
Related interview prep categories
2Want a guided curriculum order?
These modules are also sequenced in our flagship backend interview track — open it when you prefer an ordered path instead of browsing by topic.