Files

3.1 KiB

← The on-ramp · Module README · Serialisation →

2. Producing, and the return value everybody throws away

OrderProducer has three send methods because there are exactly three useful answers to "when do I find out this failed".

public void sendAndForget(OrderEvent event) {
    this.template.send(TOPIC, event.orderId(), event);   // returns a CompletableFuture. Ignored.
}

KafkaTemplate.send is asynchronous and returns a CompletableFuture<SendResult<K, V>>. Discarding it discards the only notification you will get. The method returns normally, the record may never reach the broker, and nothing in your logs says so — the producer's own retry and expiry messages are at WARN under org.apache.kafka, which most applications turn down.

This is the most common way to lose messages in a Spring Kafka application, and it looks like correct code.

this.template.send(TOPIC, event.orderId(), event)
        .whenComplete((result, ex) -> { /* log, meter, compensate */ });

Handle the future, or block on it when the caller genuinely must not proceed without a durable write:

SendResult<String, OrderEvent> result = this.template.send(record).get();
result.getRecordMetadata().partition();   // where it landed
result.getRecordMetadata().offset();      // and at what offset

Blocking costs a network round trip plus the replication acknowledgement, so it belongs at the edges of a system, not inside a loop.

Durability comes from defaults you did not set

Spring Boot sets nothing on the producer beyond bootstrap servers and serializers. Here is the effective configuration, printed by EffectiveConfigTest and committed at docs/output/effective-config.txt:

property                                       kafka-clients default        set by Spring Boot
acks                                           all                          -
enable.idempotence                             true                         -
retries                                        2147483647                   -
max.in.flight.requests.per.connection          5                            -
delivery.timeout.ms                            120000                       -

Since Kafka 3.0 the client defaults are acks=all and enable.idempotence=true, so a stock Boot application already has a durable, deduplicating producer. Two consequences:

  • You do not need to set acks=all. It is already on.
  • An old runbook that sets acks=1 or retries=0 is now a downgrade. Those lines were written when the defaults were weaker, and deleting them makes the system safer, which is the opposite of how configuration usually ages.

delivery.timeout.ms at two minutes is the one worth revisiting: it is the total budget for a send including retries, and a request that exceeds it fails permanently. If your future never completes, that is the clock you are waiting on.

Serialisation →