{"id":652,"date":"2026-03-21T22:05:10","date_gmt":"2026-03-21T21:05:10","guid":{"rendered":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/?p=652"},"modified":"2026-06-16T22:05:53","modified_gmt":"2026-06-16T21:05:53","slug":"apache-kafka-in-java-nachrichtenorientierte-architektur-mit-spring-kafka","status":"publish","type":"post","link":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/?p=652","title":{"rendered":"Apache Kafka in Java \u2013 Nachrichtenorientierte Architektur mit Spring Kafka"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">In verteilten Systemen ist asynchrone Kommunikation zwischen Diensten essenziell. REST-Aufrufe koppeln Services eng aneinander \u2013 f\u00e4llt ein Dienst aus, leidet die gesamte Kette. Apache Kafka l\u00f6st dieses Problem mit einem Event-Driven-Ansatz: Produzenten senden Nachrichten an Topics, Konsumenten lesen sie unabh\u00e4ngig und entkoppelt. In Kombination mit Spring Kafka wird die Integration in Java-Anwendungen fast trivial.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Was ist Apache Kafka?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Kafka ist eine verteilte Event-Streaming-Plattform, die urspr\u00fcnglich bei LinkedIn entstand und heute von der Apache Foundation betreut wird. Kernkonzepte:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Topic<\/strong>: Eine benannte Kategorie, in die Nachrichten geschrieben und aus der gelesen wird (z. B.\u00a0<code>bestellungen<\/code>)<\/li>\n\n\n\n<li><strong>Partition<\/strong>: Jedes Topic kann in mehrere Partitionen aufgeteilt werden, die paralleles Lesen und Schreiben erm\u00f6glichen<\/li>\n\n\n\n<li><strong>Producer<\/strong>: Schreibt Nachrichten in ein Topic<\/li>\n\n\n\n<li><strong>Consumer<\/strong>: Liest Nachrichten aus einem Topic, eingebunden in eine\u00a0<strong>Consumer Group<\/strong>\u00a0zur Lastverteilung<\/li>\n\n\n\n<li><strong>Broker<\/strong>: Ein Kafka-Server, der Topics und Partitionen verwaltet<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Kafka persistiert Nachrichten auf der Festplatte und h\u00e4lt sie f\u00fcr eine konfigurierbare Zeit vor \u2013 selbst wenn Konsumenten offline waren.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Kafka lokal starten mit Docker<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Der schnellste Weg, Kafka lokal zu testen, ist eine&nbsp;<code>docker-compose.yml<\/code>. Seit Kafka 2.8 (2021) wird&nbsp;<strong>KRaft<\/strong>&nbsp;(Kafka Raft) statt Zookeeper verwendet \u2013 Zookeeper ist seit Kafka 3.x deprecated und in Kafka 4.x vollst\u00e4ndig entfernt:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">services:\n  kafka:\n    image: apache\/kafka:latest\n    <span class=\"hljs-attr\">ports<\/span>:\n      - <span class=\"hljs-string\">\"9092:9092\"<\/span>\n    <span class=\"hljs-attr\">environment<\/span>:\n      KAFKA_NODE_ID: <span class=\"hljs-number\">1<\/span>\n      <span class=\"hljs-attr\">KAFKA_PROCESS_ROLES<\/span>: broker,controller\n      <span class=\"hljs-attr\">KAFKA_CONTROLLER_QUORUM_VOTERS<\/span>: <span class=\"hljs-number\">1<\/span>@localhost:<span class=\"hljs-number\">9093<\/span>\n      <span class=\"hljs-attr\">KAFKA_LISTENERS<\/span>: PLAINTEXT:<span class=\"hljs-comment\">\/\/0.0.0.0:9092,CONTROLLER:\/\/localhost:9093<\/span>\n      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT:<span class=\"hljs-comment\">\/\/localhost:9092<\/span>\n      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER\n      <span class=\"hljs-attr\">KAFKA_LISTENER_SECURITY_PROTOCOL_MAP<\/span>: CONTROLLER:PLAINTEXT,<span class=\"hljs-attr\">PLAINTEXT<\/span>:PLAINTEXT\n      <span class=\"hljs-attr\">KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR<\/span>: <span class=\"hljs-number\">1<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Start:&nbsp;<code>docker compose up -d<\/code><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Spring Kafka \u2013 Producer<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Mit Spring Boot ist ein Kafka-Producer in wenigen Zeilen eingerichtet. Dieser Artikel verwendet&nbsp;<strong>Spring Kafka 3.3.x<\/strong>&nbsp;(kompatibel mit Spring Boot 3.4.x und kafka-clients 3.8-3.9). F\u00fcge die Abh\u00e4ngigkeit hinzu:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>org.springframework.kafka<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">groupId<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>spring-kafka<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">artifactId<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">dependency<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Aktiviere Kafka in deiner Konfigurationsklasse mit&nbsp;<code>@EnableKafka<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">@Configuration\n@EnableKafka\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">KafkaConfig<\/span> <\/span>{\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Konfiguration in&nbsp;<code>application.properties<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">spring.kafka.bootstrap-servers=localhost:9092\nspring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer\nspring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Der Producer selbst ist ein einfacher Service mit&nbsp;<code>KafkaTemplate<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">@Service\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">BestellungProducer<\/span> <\/span>{\n\n    <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> KafkaTemplate&lt;String, Bestellung&gt; kafkaTemplate;\n\n    <span class=\"hljs-keyword\">public<\/span> BestellungProducer(KafkaTemplate&lt;String, Bestellung&gt; kafkaTemplate) {\n        this.kafkaTemplate = kafkaTemplate;\n    }\n\n    <span class=\"hljs-keyword\">public<\/span> void sendeBestellung(Bestellung bestellung) {\n        kafkaTemplate.send(<span class=\"hljs-string\">\"bestellungen\"<\/span>, bestellung.getId(), bestellung)\n            .whenComplete((result, ex) -&gt; {\n                <span class=\"hljs-keyword\">if<\/span> (ex != <span class=\"hljs-keyword\">null<\/span>) {\n                    log.error(<span class=\"hljs-string\">\"Fehler beim Senden: {}\"<\/span>, ex.getMessage());\n                } <span class=\"hljs-keyword\">else<\/span> {\n                    log.info(<span class=\"hljs-string\">\"Gesendet an Partition {} Offset {}\"<\/span>,\n                        result.getRecordMetadata().partition(),\n                        result.getRecordMetadata().offset());\n                }\n            });\n    }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Das&nbsp;<code>KafkaTemplate<\/code>&nbsp;serialisiert die&nbsp;<code>Bestellung<\/code>&nbsp;automatisch nach JSON und sendet sie asynchron. Der Callback erm\u00f6glicht Fehlerbehandlung.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Spring Kafka \u2013 Consumer<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Der Consumer lauscht mittels&nbsp;<code>@KafkaListener<\/code>&nbsp;auf eingehende Nachrichten:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">@Component\npublic <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">BestellungConsumer<\/span> <\/span>{\n\n    @KafkaListener(topics = <span class=\"hljs-string\">\"bestellungen\"<\/span>, groupId = <span class=\"hljs-string\">\"warehouse-group\"<\/span>)\n    public <span class=\"hljs-keyword\">void<\/span> verarbeite(Bestellung bestellung) {\n        log.info(<span class=\"hljs-string\">\"Neue Bestellung empfangen: {}\"<\/span>, bestellung.getId());\n        <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span>\/\/ Gesch\u00e4ftslogik ...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span><\/span>\n    }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Konfiguration f\u00fcr den Consumer:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer\nspring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer\nspring.kafka.consumer.properties.spring.json.trusted.packages=com.example.model\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Die&nbsp;<code>groupId<\/code>&nbsp;ordnet den Consumer einer Consumer Group zu. Mehrere Konsumenten mit derselben Gruppe verteilen die Partitionen unter sich \u2013 jeder erh\u00e4lt nur einen Teil der Nachrichten. Verschiedene Gruppen erhalten jeweils alle Nachrichten unabh\u00e4ngig voneinander.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Fehlerbehandlung mit Dead Letter Topic<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Nicht verarbeitbare Nachrichten (z. B. Deserialisierungsfehler) landen idealerweise in einem Dead Letter Topic (DLT). Spring Kafka bietet einen&nbsp;<code>DeadLetterPublishingRecoverer<\/code>, der automatisch nach konfigurierbaren Retry-Versuchen greift:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">@Bean\npublic DefaultErrorHandler errorHandler(\n        KafkaTemplate&lt;<span class=\"hljs-built_in\">String<\/span>, <span class=\"hljs-built_in\">Object<\/span>&gt; template) {\n    DeadLetterPublishingRecoverer recoverer =\n        <span class=\"hljs-keyword\">new<\/span> DeadLetterPublishingRecoverer(template);\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> DefaultErrorHandler(recoverer,\n        <span class=\"hljs-keyword\">new<\/span> FixedBackOff(<span class=\"hljs-number\">1000<\/span>L, <span class=\"hljs-number\">3<\/span>));\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Nach drei Wiederholungen im Sekundenabstand wird die fehlerhafte Nachricht ins Topic&nbsp;<code>bestellungen.DLT<\/code>&nbsp;umgeleitet \u2013 dort kann sie manuell analysiert werden.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Kafka Streams API<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Neben Producer\/Consumer bietet Kafka die Streams API f\u00fcr zustandsbehaftete Datenverarbeitung:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">@Bean\npublic KStream&lt;<span class=\"hljs-built_in\">String<\/span>, Bestellung&gt; stream(StreamsBuilder builder) {\n    KStream&lt;<span class=\"hljs-built_in\">String<\/span>, Bestellung&gt; stream = builder.stream(<span class=\"hljs-string\">\"bestellungen\"<\/span>);\n    stream\n        .filter((key, b) -&gt; b.getWert() &gt; <span class=\"hljs-number\">100<\/span>)\n        .mapValues(b -&gt; b.withPriorisiert(<span class=\"hljs-literal\">true<\/span>))\n        .to(<span class=\"hljs-string\">\"bestellungen-priorisiert\"<\/span>);\n    <span class=\"hljs-keyword\">return<\/span> stream;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Dieser Stream filtert Bestellungen \u00fcber 100 \u20ac, markiert sie als priorisiert und leitet sie in ein separates Topic weiter. Kafka Streams nutzt intern&nbsp;<strong>State Stores<\/strong>&nbsp;(basierend auf RocksDB) f\u00fcr zustandsbehaftete Operationen wie Aggregationen oder Joins \u2013 auch wenn die hier gezeigten Operationen&nbsp;<code>filter()<\/code>&nbsp;und&nbsp;<code>mapValues()<\/code>&nbsp;zustandslos sind.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Fazit<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Apache Kafka ist das R\u00fcckgrat ereignisgesteuerter Architekturen im Java-\u00d6kosystem. Spring Kafka reduziert die Integration auf wenige Annotationen und Konfigurationszeilen. Producer schreiben \u00fcber&nbsp;<code>KafkaTemplate<\/code>, Consumer lauschen mit&nbsp;<code>@KafkaListener<\/code>, und die Streams API erm\u00f6glicht komplexe Datenverarbeitung direkt auf dem Broker. Kombiniert mit Dead Letter Topics und Retry-Mechanismen entsteht ein robuster Nachrichtenbus, der selbst tempor\u00e4re Ausf\u00e4lle \u00fcbersteht.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In verteilten Systemen ist asynchrone Kommunikation zwischen Diensten essenziell. REST-Aufrufe koppeln Services eng aneinander \u2013 f\u00e4llt ein Dienst aus, leidet die gesamte Kette. Apache Kafka l\u00f6st dieses Problem mit einem Event-Driven-Ansatz: Produzenten senden Nachrichten an Topics, Konsumenten lesen sie unabh\u00e4ngig und entkoppelt. In Kombination mit Spring Kafka wird die Integration in Java-Anwendungen fast trivial. Was [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,5],"tags":[],"class_list":["post-652","post","type-post","status-publish","format-standard","hentry","category-plain_java","category-spring"],"_links":{"self":[{"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/posts\/652","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=652"}],"version-history":[{"count":1,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/posts\/652\/revisions"}],"predecessor-version":[{"id":653,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/posts\/652\/revisions\/653"}],"wp:attachment":[{"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=652"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=652"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=652"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}