Repository: debezium/debezium-examples
Pull request #374: Dbz 8754: Develop a Quarkus Application Example with Debezium Optimized for GraalVM
Author: login: kmos
Created: 2025-03-13T11:37:39Z | Merged: 2025-03-14T14:37:16Z

## Context

Demostrate the capabilities of debezium inside a native image


## Decision
Integrate Debezium within a Quarkus application to create a CDC system that benefits from Quarkus's native compilation capabilities via GraalVM. This integration aims to achieve ultra-fast startup times and a reduced memory footprint.

closes [DBZ-8754](https://issues.redhat.com/browse/DBZ-8754)


Diff:
diff --git a/.github/workflows/quarkus-native-workflow.yml b/.github/workflows/quarkus-native-workflow.yml
new file mode 100644
index 0000000000..7a7043ea27
--- /dev/null
+++ b/.github/workflows/quarkus-native-workflow.yml
@@ -0,0 +1,31 @@
+name: Build [quarkus-native]
+
+on:
+  push:
+    paths:
+      - 'quarkus-native/**'
+      - '.github/workflows/quarkus-native-workflow.yml'
+  pull_request:
+    paths:
+      - 'quarkus-native/**'
+      - '.github/workflows/quarkus-native-workflow.yml'
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Set up Java
+        uses: actions/setup-java@v4
+        with:
+          distribution: "temurin"
+          java-version: 23
+      - uses: actions/checkout@v4
+      - name: Cache local Maven repository
+        uses: actions/cache@v4
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('quarkus-native/**/pom.xml') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-
+      - name: Check changes in [quarkus-native] example
+        run: cd quarkus-native && mvn clean package -Dnative
diff --git a/README.md b/README.md
index 12ab90e150..8a4bad0b0f 100644
--- a/README.md
+++ b/README.md
@@ -50,4 +50,5 @@ For getting started please check the [tutorial example](./tutorial).
 * [Postgres - TOAST Column Values](./postgres-toast): Dealing With Postgres TOAST Column Values
 * [SQL Server - Replication](./sql-server-read-replica): How to deploy the topology of services to stream from SQL Server read-only replica
 * [MongoDB - Streaming to PostgresSQL](./unwrap-mongodb-smt): How to capture events from a MongoDB database and stream them to a PostgresSQL database
-* [MySQL - Streaming to PostgresSQL](./unwrap-smt): How to capture events from a MySQL database and stream them to a PostgresSQL database
\ No newline at end of file
+* [MySQL - Streaming to PostgresSQL](./unwrap-smt): How to capture events from a MySQL database and stream them to a PostgresSQL database
+* [Quarkus Native Image with Debezium](./quarkus-native): superfast CDC with Debezium and Quarkus
\ No newline at end of file
diff --git a/quarkus-native/README.md b/quarkus-native/README.md
new file mode 100644
index 0000000000..f2f88b0a1c
--- /dev/null
+++ b/quarkus-native/README.md
@@ -0,0 +1,34 @@
+# Quarkus Native image with Debezium
+
+This example demonstrate how to create a superfast CDC with Quarkus built with graalVM. We will ingest data from a postgres database into quarkus application with Debezium.
+
+## Disclaimer
+
+The example suggested here serve solely as a **proof of concept** to demonstrate Debezium's capabilities. They are not intended for production use but rather to showcase its potential. Debezium and its surrounding third-party libraries are not designed to be fully native for all use cases. Instead, the example presented here focus on a specific **scenario**.
+
+## Prerequisite
+
+- GraalVM installed and configured appropriately (sdkman: 23.0.2-graal)
+- Apache Maven 3.9.9
+- A working container runtime (Docker or Podman)
+- A working C development environment
+
+You can use this link to set up your [working environment](https://quarkus.io/guides/building-native-image#configuring-c-development).
+
+## Create a native build
+
+```console
+$ mvn clean package -Dnative
+```
+
+## Run the example
+
+Start a postgres instance with docker compose:
+```console
+$ docker compose up
+```
+
+Start the application:
+```console
+$ ./debezium-native-1.0-SNAPSHOT-runner
+```
\ No newline at end of file
diff --git a/quarkus-native/docker-compose.yaml b/quarkus-native/docker-compose.yaml
new file mode 100644
index 0000000000..32911194d0
--- /dev/null
+++ b/quarkus-native/docker-compose.yaml
@@ -0,0 +1,8 @@
+services:
+  postgres:
+    image: quay.io/debezium/example-postgres:3.0
+    ports:
+     - 5432:5432
+    environment:
+     - POSTGRES_USER=postgresuser
+     - POSTGRES_PASSWORD=postgrespw
diff --git a/quarkus-native/pom.xml b/quarkus-native/pom.xml
new file mode 100644
index 0000000000..018f546bbd
--- /dev/null
+++ b/quarkus-native/pom.xml
@@ -0,0 +1,261 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>io.debezium</groupId>
+    <artifactId>quarkus-native</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <properties>
+        <compiler-plugin.version>3.13.0</compiler-plugin.version>
+        <maven.compiler.release>21</maven.compiler.release>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
+        <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
+        <quarkus.platform.version>3.15.3</quarkus.platform.version>
+        <skipITs>true</skipITs>
+        <surefire-plugin.version>3.3.1</surefire-plugin.version>
+        <version.debezium>3.0.8.Final</version.debezium>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency> <groupId>io.debezium</groupId>
+                <artifactId>debezium-bom</artifactId>
+                <version>${version.debezium}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>${quarkus.platform.group-id}</groupId>
+                <artifactId>${quarkus.platform.artifact-id}</artifactId>
+                <version>${quarkus.platform.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-arc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.debezium</groupId>
+            <artifactId>debezium-embedded</artifactId>
+            <version>${version.debezium}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.fasterxml.jackson.core</groupId>
+                    <artifactId>jackson-core</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>com.fasterxml.jackson.core</groupId>
+                    <artifactId>jackson-databind</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>com.fasterxml.jackson.datatype</groupId>
+                    <artifactId>jackson-datatype-jsr310</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>com.fasterxml</groupId>
+                    <artifactId>jackson-module-afterburner</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.kafka</groupId>
+                    <artifactId>connect-json</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.kafka</groupId>
+                    <artifactId>connect-runtime</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.kafka</groupId>
+            <artifactId>connect-runtime</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.fasterxml.jackson.core</groupId>
+                    <artifactId>jackson-annotations</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.bitbucket.b_c</groupId>
+                    <artifactId>jose4j</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>com.fasterxml.jackson.jaxrs</groupId>
+                    <artifactId>jackson-jaxrs-json-provider</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.glassfish.jersey.containers</groupId>
+                    <artifactId>jersey-container-servlet</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.glassfish.jersey.inject</groupId>
+                    <artifactId>jersey-hk2</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>javax.xml.bind</groupId>
+                    <artifactId>jaxb-api</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>javax.activation</groupId>
+                    <artifactId>activation</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.eclipse.jetty</groupId>
+                    <artifactId>jetty-server</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.eclipse.jetty</groupId>
+                    <artifactId>jetty-servlet</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.eclipse.jetty</groupId>
+                    <artifactId>jetty-servlets</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.eclipse.jetty</groupId>
+                    <artifactId>jetty-client</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.eclipse.jetty</groupId>
+                    <artifactId>jetty-client</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.reflections</groupId>
+                    <artifactId>reflections</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.maven</groupId>
+                    <artifactId>maven-artifact</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.kafka</groupId>
+            <artifactId>connect-json</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.fasterxml</groupId>
+                    <artifactId>jackson-module-afterburner</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-jackson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-util</artifactId>
+            <version>9.4.56.v20240826</version>
+        </dependency>
+        <dependency>
+            <groupId>io.debezium</groupId>
+            <artifactId>debezium-connector-postgres</artifactId>
+            <version>${version.debezium}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.postgresql</groupId>
+                    <artifactId>postgresql</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-jdbc-postgresql</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.debezium</groupId>
+            <artifactId>debezium-api</artifactId>
+            <version>${version.debezium}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.kafka</groupId>
+            <artifactId>connect-transforms</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>${quarkus.platform.group-id}</groupId>
+                <artifactId>quarkus-maven-plugin</artifactId>
+                <version>${quarkus.platform.version}</version>
+                <extensions>true</extensions>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>build</goal>
+                            <goal>generate-code</goal>
+                            <goal>generate-code-tests</goal>
+                            <goal>native-image-agent</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>${compiler-plugin.version}</version>
+                <configuration>
+                    <parameters>true</parameters>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>${surefire-plugin.version}</version>
+                <configuration>
+                    <systemPropertyVariables>
+                        <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
+                        <maven.home>${maven.home}</maven.home>
+                    </systemPropertyVariables>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>native</id>
+            <activation>
+                <property>
+                    <name>native</name>
+                </property>
+            </activation>
+            <properties>
+                <quarkus.native.enabled>true</quarkus.native.enabled>
+                <quarkus.package.jar.enabled>false</quarkus.package.jar.enabled>
+            </properties>
+            <build>
+                <plugins>
+                    <plugin>
+                        <artifactId>maven-failsafe-plugin</artifactId>
+                        <version>${surefire-plugin.version}</version>
+                        <executions>
+                            <execution>
+                                <goals>
+                                    <goal>integration-test</goal>
+                                    <goal>verify</goal>
+                                </goals>
+                                <configuration>
+                                    <systemPropertyVariables>
+                                        <native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
+                                        <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
+                                        <maven.home>${maven.home}</maven.home>
+                                    </systemPropertyVariables>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
+</project>
\ No newline at end of file
diff --git a/quarkus-native/src/main/java/io/debezium/example/quarkus/DatabaseChangeEventListener.java b/quarkus-native/src/main/java/io/debezium/example/quarkus/DatabaseChangeEventListener.java
new file mode 100644
index 0000000000..c9e53e99a5
--- /dev/null
+++ b/quarkus-native/src/main/java/io/debezium/example/quarkus/DatabaseChangeEventListener.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright Debezium Authors.
+ *
+ * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package io.debezium.example.quarkus;
+
+import io.debezium.config.Configuration;
+import io.debezium.data.Envelope.Operation;
+import io.debezium.embedded.Connect;
+import io.debezium.engine.DebeziumEngine;
+import io.debezium.engine.RecordChangeEvent;
+import io.debezium.engine.format.ChangeEventFormat;
+import io.quarkus.runtime.Startup;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.apache.kafka.connect.data.Struct;
+import org.apache.kafka.connect.source.SourceRecord;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.ExecutorService;
+import java.util.function.Function;
+
+
+@ApplicationScoped
+@Startup
+public class DatabaseChangeEventListener {
+
+    private static final String CONFIG_PREFIX = "quarkus.debezium-cdc.";
+    private static final Logger LOG = LoggerFactory.getLogger(DatabaseChangeEventListener.class);
+
+    private DebeziumEngine<?> engine;
+    @Inject
+    private ExecutorService executor;
+
+    @Inject
+    private DebeziumConfiguration debeziumConfiguration;
+
+    @PostConstruct
+    public void startEmbeddedEngine() {
+        LOG.info("Launching Debezium embedded engine");
+
+        final Configuration config = Configuration.empty()
+                .withSystemProperties(Function.identity())
+                .edit()
+                .with(Configuration.from(debeziumConfiguration.configuration()))
+                .build();
+
+        this.engine = DebeziumEngine.create(ChangeEventFormat.of(Connect.class))
+                .using(config.asProperties())
+                .notifying((list, recordCommitter) -> {
+                    for (RecordChangeEvent<SourceRecord> record : list) {
+                        handleDbChangeEvent(record.record());
+                        recordCommitter.markProcessed(record);
+                    }
+                    recordCommitter.markBatchFinished();
+                })
+                .build();
+
+        executor.execute(engine);
+    }
+
+    @PreDestroy
+    public void shutdownEngine() throws Exception {
+        LOG.info("Stopping Debezium embedded engine");
+        engine.close();
+        executor.shutdown();
+    }
+
+    private void handleDbChangeEvent(SourceRecord record) {
+        LOG.info("f DB change event {}", record);
+
+        if (record.topic().equals("dbserver1.inventory.products")) {
+            Integer productId = ((Struct) record.key()).getInt32("id");
+            Struct payload = (Struct) record.value();
+            Operation op = Operation.forCode(payload.getString("op"));
+            Long txId = ((Struct) payload.get("source")).getInt64("txId");
+
+            LOG.info("received event with productId: {} op: {} txId: {}", productId, op, txId);
+        }
+    }
+}
diff --git a/quarkus-native/src/main/java/io/debezium/example/quarkus/DebeziumConfiguration.java b/quarkus-native/src/main/java/io/debezium/example/quarkus/DebeziumConfiguration.java
new file mode 100644
index 0000000000..a83d1981e1
--- /dev/null
+++ b/quarkus-native/src/main/java/io/debezium/example/quarkus/DebeziumConfiguration.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright Debezium Authors.
+ *
+ * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package io.debezium.example.quarkus;
+
+import io.quarkus.runtime.annotations.StaticInitSafe;
+import io.smallrye.config.ConfigMapping;
+
+import java.util.Map;
+
+@StaticInitSafe
+@ConfigMapping(prefix = "debezium")
+public interface DebeziumConfiguration {
+    Map<String, String> configuration();
+}
diff --git a/quarkus-native/src/main/java/io/debezium/example/quarkus/ReflectingConfig.java b/quarkus-native/src/main/java/io/debezium/example/quarkus/ReflectingConfig.java
new file mode 100644
index 0000000000..18921b80d7
--- /dev/null
+++ b/quarkus-native/src/main/java/io/debezium/example/quarkus/ReflectingConfig.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright Debezium Authors.
+ *
+ * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package io.debezium.example.quarkus;
+
+import io.debezium.connector.postgresql.PostgresConnector;
+import io.debezium.connector.postgresql.PostgresConnectorTask;
+import io.debezium.connector.postgresql.PostgresSourceInfoStructMaker;
+import io.debezium.connector.postgresql.snapshot.lock.NoSnapshotLock;
+import io.debezium.connector.postgresql.snapshot.lock.SharedSnapshotLock;
+import io.debezium.connector.postgresql.snapshot.query.SelectAllSnapshotQuery;
+import io.debezium.embedded.ConvertingEngineBuilderFactory;
+import io.debezium.embedded.async.ConvertingAsyncEngineBuilderFactory;
+import io.debezium.engine.DebeziumEngine;
+import io.debezium.engine.spi.OffsetCommitPolicy;
+import io.debezium.pipeline.notification.channels.LogNotificationChannel;
+import io.debezium.pipeline.notification.channels.SinkNotificationChannel;
+import io.debezium.pipeline.notification.channels.jmx.JmxNotificationChannel;
+import io.debezium.pipeline.signal.actions.StandardActionProvider;
+import io.debezium.pipeline.signal.channels.FileSignalChannel;
+import io.debezium.pipeline.signal.channels.KafkaSignalChannel;
+import io.debezium.pipeline.signal.channels.SourceSignalChannel;
+import io.debezium.pipeline.signal.channels.jmx.JmxSignalChannel;
+import io.debezium.pipeline.signal.channels.process.InProcessSignalChannel;
+import io.debezium.pipeline.txmetadata.DefaultTransactionMetadataFactory;
+import io.debezium.schema.SchemaTopicNamingStrategy;
+import io.debezium.snapshot.lock.NoLockingSupport;
+import io.debezium.snapshot.mode.*;
+import io.debezium.snapshot.spi.SnapshotLock;
+import io.debezium.transforms.ExtractNewRecordState;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import org.apache.kafka.common.security.authenticator.SaslClientAuthenticator;
+import org.apache.kafka.connect.json.JsonConverter;
+import org.apache.kafka.connect.transforms.predicates.Predicate;
+import org.apache.kafka.connect.transforms.predicates.TopicNameMatches;
+
+@RegisterForReflection(targets = {
+        DebeziumEngine.BuilderFactory.class,
+        ConvertingEngineBuilderFactory.class,
+        ConvertingAsyncEngineBuilderFactory.class,
+        SaslClientAuthenticator.class,
+        JsonConverter.class,
+        PostgresConnector.class,
+        PostgresSourceInfoStructMaker.class,
+        DefaultTransactionMetadataFactory.class,
+        SchemaTopicNamingStrategy.class,
+        OffsetCommitPolicy.class,
+        PostgresConnectorTask.class,
+        SinkNotificationChannel.class,
+        LogNotificationChannel.class,
+        JmxNotificationChannel.class,
+        SnapshotLock.class,
+        NoLockingSupport.class,
+        NoSnapshotLock.class,
+        SharedSnapshotLock.class,
+        SelectAllSnapshotQuery.class,
+        AlwaysSnapshotter.class,
+        InitialSnapshotter.class,
+        InitialOnlySnapshotter.class,
+        NoDataSnapshotter.class,
+        RecoverySnapshotter.class,
+        WhenNeededSnapshotter.class,
+        NeverSnapshotter.class,
+        SchemaOnlySnapshotter.class,
+        SchemaOnlyRecoverySnapshotter.class,
+        ConfigurationBasedSnapshotter.class,
+        SourceSignalChannel.class,
+        KafkaSignalChannel.class,
+        FileSignalChannel.class,
+        JmxSignalChannel.class,
+        InProcessSignalChannel.class,
+        StandardActionProvider.class,
+        TopicNameMatches.class,
+        ExtractNewRecordState.class
+})
+public class ReflectingConfig { }
diff --git a/quarkus-native/src/main/resources/application.properties b/quarkus-native/src/main/resources/application.properties
new file mode 100644
index 0000000000..b44715adcd
--- /dev/null
+++ b/quarkus-native/src/main/resources/application.properties
@@ -0,0 +1,30 @@
+# Debezium CDC configuration
+debezium.configuration.connector.class=io.debezium.connector.postgresql.PostgresConnector
+debezium.configuration.offset.storage=org.apache.kafka.connect.storage.MemoryOffsetBackingStore
+debezium.configuration.name=native
+debezium.configuration.database.hostname=localhost
+debezium.configuration.database.port=5432
+debezium.configuration.database.user=postgresuser
+debezium.configuration.database.password=postgrespw
+debezium.configuration.database.dbname=postgresuser
+debezium.configuration.topic.prefix=dbserver1
+debezium.configuration.table.include.list=inventory.products
+debezium.configuration.plugin.name=pgoutput
+debezium.configuration.snapshot.mode=never
+
+
+# Transformation
+debezium.configuration.transforms.t0.add.fields=op,table
+debezium.configuration.transforms.t0.add.headers=db,table
+debezium.configuration.transforms.t0.negate=false
+debezium.configuration.transforms.t0.predicate=p2
+debezium.configuration.transforms.t0.type=io.debezium.transforms.ExtractNewRecordState
+debezium.configuration.transforms=t0
+debezium.configuration.predicates.p2.pattern=inventory.inventory.products
+debezium.configuration.predicates.p2.type=org.apache.kafka.connect.transforms.predicates.TopicNameMatches
+debezium.configuration.predicates=p2
+
+# Quarkus
+quarkus.native.resources.includes=META-INF/services/*
+quarkus.native.native-image-agent.enabled=true
+quarkus.native.additional-build-args=--initialize-at-run-time=org.apache.kafka.common.security.authenticator.SaslClientAuthenticator
\ No newline at end of file


Reviews:
user: login: mfvitale
body: LGTM!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-13T14:43:39Z
user: login: Naros
body: Do we need to also include `Transformation` and `Predicate` types as reflected so they can be loaded as part of the  runtime? I suspect so, which is why I raise the question.

I would think we should add a transform/predicate in the configuration to be sure that we cover at least most of the more common class loading scenarios.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-14T04:46:10Z
user: login: kmos
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-14T10:43:45Z
user: login: kmos
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-14T10:45:55Z
user: login: Naros
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-14T10:48:17Z
user: login: Naros
body: LGTM, so unless @jpechane has anything to add, feel free to merge yourself.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-14T10:57:18Z

Review comments:
diff_hunk: @@ -0,0 +1,31 @@
+name: Build [quarkus-native]
+
+on:
+  push:
+    paths:
+      - 'quarkus-native/**'
+      - '.github/workflows/quarkus-native-workflow.yml'
+  pull_request:
+    paths:
+      - 'quarkus-native/**'
+      - '.github/workflows/quarkus-native-workflow.yml'
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Set up Java
+        uses: actions/setup-java@v4
+        with:
+          distribution: "temurin"
path: .github/workflows/quarkus-native-workflow.yml
user: login: kmos
body: I think that here we need to use `graalVM`
created_at: 2025-03-14T10:43:45Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,31 @@
+name: Build [quarkus-native]
+
+on:
+  push:
+    paths:
+      - 'quarkus-native/**'
+      - '.github/workflows/quarkus-native-workflow.yml'
+  pull_request:
+    paths:
+      - 'quarkus-native/**'
+      - '.github/workflows/quarkus-native-workflow.yml'
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Set up Java
+        uses: actions/setup-java@v4
+        with:
+          distribution: "temurin"
+          java-version: 21
path: .github/workflows/quarkus-native-workflow.yml
user: login: kmos
body: 23
created_at: 2025-03-14T10:45:55Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,31 @@
+name: Build [quarkus-native]
+
+on:
+  push:
+    paths:
+      - 'quarkus-native/**'
+      - '.github/workflows/quarkus-native-workflow.yml'
+  pull_request:
+    paths:
+      - 'quarkus-native/**'
+      - '.github/workflows/quarkus-native-workflow.yml'
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Set up Java
+        uses: actions/setup-java@v4
+        with:
+          distribution: "temurin"
+          java-version: 21
path: .github/workflows/quarkus-native-workflow.yml
user: login: Naros
body: We can bump Java to 23 on CI if you'd like, I have no issue with that.
created_at: 2025-03-14T10:48:17Z
author_association: MEMBER

PR comments:
user: login: kmos
created_at: 2025-03-14T09:49:04Z
body: > Do we need to also include `Transformation` and `Predicate` types as reflected so they can be loaded as part of the runtime? I suspect so, which is why I raise the question.
> 
> I would think we should add a transform/predicate in the configuration to be sure that we cover at least most of the more common class loading scenarios.



> Do we need to also include `Transformation` and `Predicate` types as reflected so they can be loaded as part of the runtime? I suspect so, which is why I raise the question.
> 
> I would think we should add a transform/predicate in the configuration to be sure that we cover at least most of the more common class loading scenarios.

good catch! I have added the following configuration:

```properties
# Transformation
debezium.configuration.transforms.t0.add.fields=op,table
debezium.configuration.transforms.t0.add.headers=db,table
debezium.configuration.transforms.t0.negate=false
debezium.configuration.transforms.t0.predicate=p2
debezium.configuration.transforms.t0.type=io.debezium.transforms.ExtractNewRecordState
debezium.configuration.transforms=t0
debezium.configuration.predicates.p2.pattern=inventory.inventory.products
debezium.configuration.predicates.p2.type=org.apache.kafka.connect.transforms.predicates.TopicNameMatches
debezium.configuration.predicates=p2
```


and the application failed with the following error:

```text
2025-03-14 10:35:26,159 ERROR [io.qua.run.Application] (main) Failed to start application: java.lang.RuntimeException: Failed to start quarkus
        at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source)
        at io.quarkus.runtime.Application.start(Application.java:101)
        at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119)
        at io.quarkus.runtime.Quarkus.run(Quarkus.java:71)
        at io.quarkus.runtime.Quarkus.run(Quarkus.java:44)
        at io.quarkus.runtime.Quarkus.run(Quarkus.java:124)
        at io.quarkus.runner.GeneratedMain.main(Unknown Source)
Caused by: io.debezium.DebeziumException: Error while instantiating predicate 'p2'
```

If I add `Predicate` nothing change. If I dig deeper in the error, the problem is that is unable to load the class that I have defined in the configuration `org.apache.kafka.connect.transforms.predicates.TopicNameMatches`. If I had it in the `ReflectingConfig` the result is:

```text
2025-03-14 10:43:13,230 ERROR [io.qua.run.Application] (main) Failed to start application: java.lang.RuntimeException: Failed to start quarkus
        at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source)
        at io.quarkus.runtime.Application.start(Application.java:101)
        at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:119)
        at io.quarkus.runtime.Quarkus.run(Quarkus.java:71)
        at io.quarkus.runtime.Quarkus.run(Quarkus.java:44)
        at io.quarkus.runtime.Quarkus.run(Quarkus.java:124)
        at io.quarkus.runner.GeneratedMain.main(Unknown Source)
Caused by: io.debezium.DebeziumException: Error while instantiating transformation 't0'
```

Now it fails on `io.debezium.transforms.ExtractNewRecordState`. If I had it in the `ReflectingConfig` the application works.

I think that this use case (the transformation) should be added also in the blog post to help the reader to understand that the classes added in the properties are not loaded automatically in a native build: https://github.com/debezium/debezium.github.io/pull/1116

author_association: MEMBER
user: login: Naros
created_at: 2025-03-14T10:05:33Z
body: I think this also highlights that when we create the actual Debezium Quarkus extension, we will need to think about adding `@BuildStep` processor bits so that we can collect all `Transformation`, `Predicate`, etc implementations on the classpath and auto-register them for reflection rather than hardcoding them in the config class used here.  Would you agree @kmos ?
author_association: MEMBER
user: login: kmos
created_at: 2025-03-14T10:08:38Z
body: Absolutely, I totally agree. I can underline it in the article
author_association: MEMBER