Stage 12: Microservices, lesson 3 of 7

Sync vs async communication (REST and Kafka)

Advanced3 min read@since 17Code runs on your Java 25
Explain it forThe essentials plus production detail and pitfalls.

Synchronous calls (REST or gRPC) make the caller wait for the response. They're simple but couple availability: if payment-service is down, checkout fails. Use them for queries that need an immediate answer.

Asynchronous messaging (Kafka, RabbitMQ): a service publishes an event such as OrderPlaced and moves on, and interested services consume it in their own time. You get loose coupling, natural buffering, and new consumers can be added without touching the producer.

Spring options:

  • HTTP: RestClient, declarative HTTP interface clients (@HttpExchange), or OpenFeign.
  • gRPC: Spring gRPC, auto-configured since Boot 4.1.
  • Messaging: KafkaTemplate with @KafkaListener, or Spring Cloud Stream.

Kafka basics: topics are split into partitions; messages with the same key go to the same partition and stay in order; consumer groups share partitions between instances for scaling.

Example

Java
// Declarative HTTP client (Spring 6+)
@HttpExchange("/api/v1/users")
public interface UserClient {
    @GetExchange("/{id}")
    UserDto get(@PathVariable String id);
}

// Publish an event after saving the order
@Service
public class OrderService {
    private final KafkaTemplate<String, OrderPlaced> kafka;
    OrderService(KafkaTemplate<String, OrderPlaced> kafka) { this.kafka = kafka; }

    public void place(Order order) {
        // ... save the order in this service's own database
        kafka.send("orders.placed", order.id().toString(),
                   new OrderPlaced(order.id(), order.userId(), order.totalPaise()));
    }
}

// Another service reacts independently
@Component
class EnrollmentListener {
    @KafkaListener(topics = "orders.placed", groupId = "enrollment-service")
    void on(OrderPlaced event) {
        enrollmentService.grantAccess(event.userId(), event.orderId());
    }
}

Common mistake

Long chains of synchronous calls (A calls B calls C calls D). Latencies add up, and one slow service drags everything down. Prefer events, or keep a local copy of the data you need.

Under the hood

Saving to the database and then publishing to Kafka is a dual write: a crash between the two loses the event. The transactional outbox pattern writes the event to an outbox table in the same database transaction, and a relay (Debezium change data capture, or a poller) publishes it. Kafka delivers at least once, so consumers must be idempotent, for example by de-duplicating on the event id.

Check yourself

What makes a Kafka consumer safe against duplicate delivery?

How this connects

Part of Microservices and production.

Was this lesson helpful?

Finished reading? Mark it complete to track your progress.