In verteilten Systemen ist asynchrone Kommunikation zwischen Diensten essenziell. REST-Aufrufe koppeln Services eng aneinander – fällt ein Dienst aus, leidet die gesamte Kette. Apache Kafka löst dieses Problem mit einem Event-Driven-Ansatz: Produzenten senden Nachrichten an Topics, Konsumenten lesen sie unabhängig und entkoppelt. In Kombination mit Spring Kafka wird die Integration in Java-Anwendungen fast trivial.

Was ist Apache Kafka?

Kafka ist eine verteilte Event-Streaming-Plattform, die ursprünglich bei LinkedIn entstand und heute von der Apache Foundation betreut wird. Kernkonzepte:

  • Topic: Eine benannte Kategorie, in die Nachrichten geschrieben und aus der gelesen wird (z. B. bestellungen)
  • Partition: Jedes Topic kann in mehrere Partitionen aufgeteilt werden, die paralleles Lesen und Schreiben ermöglichen
  • Producer: Schreibt Nachrichten in ein Topic
  • Consumer: Liest Nachrichten aus einem Topic, eingebunden in eine Consumer Group zur Lastverteilung
  • Broker: Ein Kafka-Server, der Topics und Partitionen verwaltet

Kafka persistiert Nachrichten auf der Festplatte und hält sie für eine konfigurierbare Zeit vor – selbst wenn Konsumenten offline waren.

Kafka lokal starten mit Docker

Der schnellste Weg, Kafka lokal zu testen, ist eine docker-compose.yml. Seit Kafka 2.8 (2021) wird KRaft (Kafka Raft) statt Zookeeper verwendet – Zookeeper ist seit Kafka 3.x deprecated und in Kafka 4.x vollständig entfernt:

services:
  kafka:
    image: apache/kafka:latest
    ports:
      - "9092:9092"
    environment:
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: broker,controller
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@localhost:9093
      KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://localhost:9093
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
Code-Sprache: JavaScript (javascript)

Start: docker compose up -d

Spring Kafka – Producer

Mit Spring Boot ist ein Kafka-Producer in wenigen Zeilen eingerichtet. Dieser Artikel verwendet Spring Kafka 3.3.x (kompatibel mit Spring Boot 3.4.x und kafka-clients 3.8-3.9). Füge die Abhängigkeit hinzu:

<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>
Code-Sprache: HTML, XML (xml)

Aktiviere Kafka in deiner Konfigurationsklasse mit @EnableKafka:

@Configuration
@EnableKafka
public class KafkaConfig {
}
Code-Sprache: PHP (php)

Konfiguration in application.properties:

spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer

Der Producer selbst ist ein einfacher Service mit KafkaTemplate:

@Service
public class BestellungProducer {

    private final KafkaTemplate<String, Bestellung> kafkaTemplate;

    public BestellungProducer(KafkaTemplate<String, Bestellung> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void sendeBestellung(Bestellung bestellung) {
        kafkaTemplate.send("bestellungen", bestellung.getId(), bestellung)
            .whenComplete((result, ex) -> {
                if (ex != null) {
                    log.error("Fehler beim Senden: {}", ex.getMessage());
                } else {
                    log.info("Gesendet an Partition {} Offset {}",
                        result.getRecordMetadata().partition(),
                        result.getRecordMetadata().offset());
                }
            });
    }
}
Code-Sprache: PHP (php)

Das KafkaTemplate serialisiert die Bestellung automatisch nach JSON und sendet sie asynchron. Der Callback ermöglicht Fehlerbehandlung.

Spring Kafka – Consumer

Der Consumer lauscht mittels @KafkaListener auf eingehende Nachrichten:

@Component
public class BestellungConsumer {

    @KafkaListener(topics = "bestellungen", groupId = "warehouse-group")
    public void verarbeite(Bestellung bestellung) {
        log.info("Neue Bestellung empfangen: {}", bestellung.getId());
        <em>// Geschäftslogik ...</em>
    }
}
Code-Sprache: JavaScript (javascript)

Konfiguration für den Consumer:

spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer
spring.kafka.consumer.properties.spring.json.trusted.packages=com.example.model

Die groupId ordnet den Consumer einer Consumer Group zu. Mehrere Konsumenten mit derselben Gruppe verteilen die Partitionen unter sich – jeder erhält nur einen Teil der Nachrichten. Verschiedene Gruppen erhalten jeweils alle Nachrichten unabhängig voneinander.

Fehlerbehandlung mit Dead Letter Topic

Nicht verarbeitbare Nachrichten (z. B. Deserialisierungsfehler) landen idealerweise in einem Dead Letter Topic (DLT). Spring Kafka bietet einen DeadLetterPublishingRecoverer, der automatisch nach konfigurierbaren Retry-Versuchen greift:

@Bean
public DefaultErrorHandler errorHandler(
        KafkaTemplate<String, Object> template) {
    DeadLetterPublishingRecoverer recoverer =
        new DeadLetterPublishingRecoverer(template);
    return new DefaultErrorHandler(recoverer,
        new FixedBackOff(1000L, 3));
}
Code-Sprache: JavaScript (javascript)

Nach drei Wiederholungen im Sekundenabstand wird die fehlerhafte Nachricht ins Topic bestellungen.DLT umgeleitet – dort kann sie manuell analysiert werden.

Kafka Streams API

Neben Producer/Consumer bietet Kafka die Streams API für zustandsbehaftete Datenverarbeitung:

@Bean
public KStream<String, Bestellung> stream(StreamsBuilder builder) {
    KStream<String, Bestellung> stream = builder.stream("bestellungen");
    stream
        .filter((key, b) -> b.getWert() > 100)
        .mapValues(b -> b.withPriorisiert(true))
        .to("bestellungen-priorisiert");
    return stream;
}
Code-Sprache: JavaScript (javascript)

Dieser Stream filtert Bestellungen über 100 €, markiert sie als priorisiert und leitet sie in ein separates Topic weiter. Kafka Streams nutzt intern State Stores (basierend auf RocksDB) für zustandsbehaftete Operationen wie Aggregationen oder Joins – auch wenn die hier gezeigten Operationen filter() und mapValues() zustandslos sind.

Fazit

Apache Kafka ist das Rückgrat ereignisgesteuerter Architekturen im Java-Ökosystem. Spring Kafka reduziert die Integration auf wenige Annotationen und Konfigurationszeilen. Producer schreiben über KafkaTemplate, Consumer lauschen mit @KafkaListener, und die Streams API ermöglicht komplexe Datenverarbeitung direkt auf dem Broker. Kombiniert mit Dead Letter Topics und Retry-Mechanismen entsteht ein robuster Nachrichtenbus, der selbst temporäre Ausfälle übersteht.