{"id":681,"date":"2026-06-06T00:33:41","date_gmt":"2026-06-05T23:33:41","guid":{"rendered":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/?p=681"},"modified":"2026-06-17T00:35:14","modified_gmt":"2026-06-16T23:35:14","slug":"debezium-change-data-capture-mit-java-und-kafka-connect","status":"publish","type":"post","link":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/?p=681","title":{"rendered":"Debezium \u2014 Change Data Capture mit Java und Kafka Connect"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">In Microservice-Architekturen m\u00fcssen Daten\u00e4nderungen oft an mehrere Dienste propagiert werden \u2014 ein neuer Kunde, eine aktualisierte Bestellung, eine stornierte Buchung. Anstatt jeden Dienst aktiv die Datenbank pollen zu lassen, nutzt&nbsp;<strong>Change Data Capture (CDC)<\/strong>&nbsp;die Write-Ahead-Logs der Datenbank selbst als Event-Quelle.&nbsp;<strong>Debezium<\/strong>&nbsp;ist die f\u00fchrende Open-Source-CDC-Plattform, die Datenbanken wie PostgreSQL, MySQL, MongoDB und SQL Server in Echtzeit an Apache Kafka streamt.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Das CDC-Prinzip<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Traditionelle Synchronisationsans\u00e4tze pollen in regelm\u00e4\u00dfigen Abst\u00e4nden die Datenbank:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">Dienst<\/span> <span class=\"hljs-selector-tag\">A<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-attr\">&#91;Poll: SELECT * FROM bestellungen WHERE updated &gt; ?]<\/span> <span class=\"hljs-selector-tag\">--<\/span>&gt; <span class=\"hljs-selector-tag\">Datenbank<\/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\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Das belastet die Datenbank und liefert \u00c4nderungen nur mit Verz\u00f6gerung. Debezium hingegen liest das&nbsp;<strong>Write-Ahead-Log (WAL)<\/strong>&nbsp;der Datenbank:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">PostgreSQL WAL --&gt; Debezium Connector --&gt; Kafka Topic --&gt; Consumer 1, 2, 3\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Jede INSERT-, UPDATE- und DELETE-Operation wird sofort als Kafka-Event publiziert \u2014 ohne die Source-Datenbank zus\u00e4tzlich zu belasten.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Architektur<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Debezium setzt auf&nbsp;<strong>Kafka Connect<\/strong>&nbsp;als Laufzeitumgebung:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510     \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510     \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502PostgreSQL\u2502\u2500\u2500\u2500\u2500\u25b6\u2502Debezium Connector\u2502\u2500\u2500\u2500\u2500\u25b6\u2502  Kafka   \u2502\n\u2502  (WAL)   \u2502     \u2502  (Kafka Connect) \u2502     \u2502  Topic   \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518     \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518     \u2514\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2518\n                                              \u2502\n                         \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n                         \u25bc                    \u25bc             \u25bc\n                   Suchindex-         Analyse-         Audit-\n                   Aktualisierung     Pipeline         Service\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">PostgreSQL-Connector konfigurieren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Der Debezium-Connector wird als JSON-Konfiguration an Kafka Connect \u00fcbergeben:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JSON \/ JSON mit Kommentaren\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">{\n  <span class=\"hljs-attr\">\"name\"<\/span>: <span class=\"hljs-string\">\"bestellungen-connector\"<\/span>,\n  <span class=\"hljs-attr\">\"config\"<\/span>: {\n    <span class=\"hljs-attr\">\"connector.class\"<\/span>: <span class=\"hljs-string\">\"io.debezium.connector.postgresql.PostgresConnector\"<\/span>,\n    <span class=\"hljs-attr\">\"plugin.name\"<\/span>: <span class=\"hljs-string\">\"pgoutput\"<\/span>,\n    <span class=\"hljs-attr\">\"database.hostname\"<\/span>: <span class=\"hljs-string\">\"localhost\"<\/span>,\n    <span class=\"hljs-attr\">\"database.port\"<\/span>: <span class=\"hljs-string\">\"5432\"<\/span>,\n    <span class=\"hljs-attr\">\"database.user\"<\/span>: <span class=\"hljs-string\">\"debezium\"<\/span>,\n    <span class=\"hljs-attr\">\"database.password\"<\/span>: <span class=\"hljs-string\">\"geheim\"<\/span>,\n    <span class=\"hljs-attr\">\"database.dbname\"<\/span>: <span class=\"hljs-string\">\"shop\"<\/span>,\n    <span class=\"hljs-attr\">\"topic.prefix\"<\/span>: <span class=\"hljs-string\">\"shop_cdc\"<\/span>,\n    <span class=\"hljs-attr\">\"table.include.list\"<\/span>: <span class=\"hljs-string\">\"public.bestellungen\"<\/span>,\n    <span class=\"hljs-attr\">\"publication.autocreate.mode\"<\/span>: <span class=\"hljs-string\">\"filtered\"<\/span>\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON mit Kommentaren<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">PostgreSQL ben\u00f6tigt daf\u00fcr&nbsp;<code>wal_level = logical<\/code>&nbsp;in der&nbsp;<code>postgresql.conf<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Events verarbeiten in Java<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Jeder Datenbank-Change wird als Kafka-Event mit einer spezifischen Struktur publiziert:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JSON \/ JSON mit Kommentaren\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">{\n  <span class=\"hljs-attr\">\"before\"<\/span>: <span class=\"hljs-literal\">null<\/span>,\n  <span class=\"hljs-attr\">\"after\"<\/span>: {\n    <span class=\"hljs-attr\">\"id\"<\/span>: <span class=\"hljs-number\">42<\/span>,\n    <span class=\"hljs-attr\">\"kunde_id\"<\/span>: <span class=\"hljs-number\">100<\/span>,\n    <span class=\"hljs-attr\">\"betrag\"<\/span>: <span class=\"hljs-number\">99.90<\/span>,\n    <span class=\"hljs-attr\">\"status\"<\/span>: <span class=\"hljs-string\">\"NEU\"<\/span>\n  },\n  <span class=\"hljs-attr\">\"op\"<\/span>: <span class=\"hljs-string\">\"c\"<\/span>,\n  <span class=\"hljs-attr\">\"ts_ms\"<\/span>: <span class=\"hljs-number\">1718123456000<\/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\">JSON \/ JSON mit Kommentaren<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Der&nbsp;<code>op<\/code>-Wert steht f\u00fcr&nbsp;<code>c<\/code>&nbsp;(create),&nbsp;<code>u<\/code>&nbsp;(update),&nbsp;<code>d<\/code>&nbsp;(delete) oder&nbsp;<code>r<\/code>&nbsp;(snapshot). In Java verarbeitet man die Events mit einem Kafka-Streams-Topologie oder einem einfachen Consumer:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> org.apache.kafka.clients.consumer.KafkaConsumer;\n<span class=\"hljs-keyword\">import<\/span> org.apache.kafka.clients.consumer.ConsumerRecords;\n\n<span class=\"hljs-keyword\">import<\/span> java.time.Duration;\n<span class=\"hljs-keyword\">import<\/span> java.util.List;\n<span class=\"hljs-keyword\">import<\/span> java.util.Properties;\n\npublic <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">BestellungsConsumer<\/span> <\/span>{\n\n    public <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">void<\/span> main(<span class=\"hljs-built_in\">String<\/span>&#91;] args) {\n        <span class=\"hljs-keyword\">var<\/span> props = <span class=\"hljs-keyword\">new<\/span> Properties();\n        props.put(<span class=\"hljs-string\">\"bootstrap.servers\"<\/span>, <span class=\"hljs-string\">\"localhost:9092\"<\/span>);\n        props.put(<span class=\"hljs-string\">\"group.id\"<\/span>, <span class=\"hljs-string\">\"bestellungs-service\"<\/span>);\n        props.put(<span class=\"hljs-string\">\"key.deserializer\"<\/span>,\n            <span class=\"hljs-string\">\"org.apache.kafka.common.serialization.StringDeserializer\"<\/span>);\n        props.put(<span class=\"hljs-string\">\"value.deserializer\"<\/span>,\n            <span class=\"hljs-string\">\"org.apache.kafka.common.serialization.StringDeserializer\"<\/span>);\n\n        <span class=\"hljs-keyword\">try<\/span> (<span class=\"hljs-keyword\">var<\/span> consumer = <span class=\"hljs-keyword\">new<\/span> KafkaConsumer&lt;<span class=\"hljs-built_in\">String<\/span>, <span class=\"hljs-built_in\">String<\/span>&gt;(props)) {\n            consumer.subscribe(List.of(<span class=\"hljs-string\">\"shop_cdc.public.bestellungen\"<\/span>));\n\n            <span class=\"hljs-keyword\">while<\/span> (<span class=\"hljs-literal\">true<\/span>) {\n                ConsumerRecords&lt;<span class=\"hljs-built_in\">String<\/span>, <span class=\"hljs-built_in\">String<\/span>&gt; records =\n                    consumer.poll(Duration.ofMillis(<span class=\"hljs-number\">100<\/span>));\n                records.forEach(record -&gt; {\n                    System.out.printf(<span class=\"hljs-string\">\"Topic: %s, Key: %s, Value: %s%n\"<\/span>,\n                        record.topic(), record.key(), record.value());\n                });\n                consumer.commitSync();\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\">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<h2 class=\"wp-block-heading\">Das Outbox-Pattern<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ein verbreitetes Muster in Kombination mit CDC ist das&nbsp;<strong>Outbox-Pattern<\/strong>: Statt eine Nachricht direkt in Kafka zu publizieren, schreibt der Service ein Event in eine Outbox-Tabelle \u2014 innerhalb derselben Datenbank-Transaktion. Debezium streamt die Outbox-Tabelle dann nach Kafka:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">&lt;em&gt;-- Transaktionale Outbox-Tabelle&lt;\/em&gt;\nCREATE TABLE outbox (\n    id UUID PRIMARY KEY,\n    aggregatetype VARCHAR(<span class=\"hljs-number\">255<\/span>) NOT <span class=\"hljs-keyword\">NULL<\/span>,\n    aggregateid VARCHAR(<span class=\"hljs-number\">255<\/span>) NOT <span class=\"hljs-keyword\">NULL<\/span>,\n    type VARCHAR(<span class=\"hljs-number\">255<\/span>) NOT <span class=\"hljs-keyword\">NULL<\/span>,\n    payload JSONB NOT <span class=\"hljs-keyword\">NULL<\/span>,\n    timestamp TIMESTAMP <span class=\"hljs-keyword\">DEFAULT<\/span> NOW()\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\">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<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\">&lt;em&gt;<span class=\"hljs-comment\">\/\/ Innerhalb einer @Transactional-Methode&lt;\/em&gt;<\/span>\nbestellungsRepo.save(bestellung);\noutboxRepo.save(<span class=\"hljs-keyword\">new<\/span> OutboxEvent(\n    UUID.randomUUID(),\n    <span class=\"hljs-string\">\"Bestellung\"<\/span>,\n    bestellung.getId().toString(),\n    <span class=\"hljs-string\">\"BestellungErstellt\"<\/span>,\n    toJson(bestellung)\n));\n\n<span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span>\/\/ Debezium erwartet den Outbox Event Router SMT:<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span><\/span>\n<span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span>\/\/ transforms=outbox<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span><\/span>\n<span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span>\/\/ transforms.outbox.type=io.debezium.transforms.outbox.EventRouter<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span><\/span>\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\">So ist garantiert, dass die Bestellung und das dazugeh\u00f6rige Event atomar persistiert werden \u2014 ohne verteilte Transaktionen (2PC).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Embedded Debezium Engine<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr Anwendungen, die kein vollst\u00e4ndiges Kafka-Cluster betreiben wollen, bietet Debezium eine&nbsp;<strong>Embedded Engine<\/strong>:<\/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\"><span class=\"hljs-keyword\">var<\/span> engine = DebeziumEngine.create(Connect.class)\n    .using(props)\n    .notifying(record -&gt; {\n        &lt;em&gt;<span class=\"hljs-comment\">\/\/ Event-Verarbeitung direkt in der JVM&lt;\/em&gt;<\/span>\n        System.out.println(<span class=\"hljs-string\">\"Change: \"<\/span> + record.key() + <span class=\"hljs-string\">\" -&gt; \"<\/span> + record.value());\n    })\n    .build();\n\nExecutorService executor = Executors.newSingleThreadExecutor();\nexecutor.execute(engine);\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\">Die Embedded Engine l\u00e4uft im selben Java-Prozess und verzichtet auf Kafka Connect \u2014 ideal f\u00fcr Tests und Single-Service-Szenarien.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Fazit<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Debezium bringt CDC in die Java-Welt und entkoppelt Daten\u00e4nderungen von ihrer Verarbeitung. In Kombination mit dem Outbox-Pattern entstehen zuverl\u00e4ssige, transaktionssichere Event-getriebene Architekturen. F\u00fcr Java-Entwickler, die mit Microservices und asynchroner Kommunikation arbeiten, ist Debezium ein unverzichtbares Werkzeug im Werkzeugkasten.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In Microservice-Architekturen m\u00fcssen Daten\u00e4nderungen oft an mehrere Dienste propagiert werden \u2014 ein neuer Kunde, eine aktualisierte Bestellung, eine stornierte Buchung. Anstatt jeden Dienst aktiv die Datenbank pollen zu lassen, nutzt&nbsp;Change Data Capture (CDC)&nbsp;die Write-Ahead-Logs der Datenbank selbst als Event-Quelle.&nbsp;Debezium&nbsp;ist die f\u00fchrende Open-Source-CDC-Plattform, die Datenbanken wie PostgreSQL, MySQL, MongoDB und SQL Server in Echtzeit an Apache [&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],"tags":[],"class_list":["post-681","post","type-post","status-publish","format-standard","hentry","category-plain_java"],"_links":{"self":[{"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/posts\/681","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=681"}],"version-history":[{"count":1,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/posts\/681\/revisions"}],"predecessor-version":[{"id":682,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/posts\/681\/revisions\/682"}],"wp:attachment":[{"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=681"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=681"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=681"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}