IBM Event Streams and IBM Event Automation

IBM Event Streams and IBM Event Automation

Join this online group to communicate across IBM product users and experts by sharing advice and best practices with peers and staying up to date regarding product enhancements.

 View Only

Streaming from Event Streams to PostgreSQL using JDBC Sink Connector

By Sampoorna Varshinii M.K.S posted 08/20/26 01:22 AM

  

Streaming from Event Streams to PostgreSQL: JDBC Sink Connector
Sampoorna Varshinii M.K.S

Technical Support Specialist – MQ & Event Automation 

This technical note focuses on demonstrating how discussing the  integration of event streams with database by configuring a Kafka Connect JDBC Sink Connector to reliably consume those events, transform them on the fly, and persist them into a PostgreSQL database running on OpenShift Container Platform (OCP).

By the end you will have a working pipeline that consumes events from a Kafka topic, applies Single Message Transforms (SMTs) to shape the payload, and writes each record to a Postgres table.

Key Components: Kafka Connect with JDBC Sink plugin, PostgreSQL Database, and IBM Event Streams (Kafka). In the examples below, I have used version: OpenShift 4.19.26, deployed instances of IBM Event Streams 12.2.2 and a persistent PostgreSQL instance, plus the oc CLI and keytool 11.0.30 utilities.

Architectural overview

The message is assumed to be published to Kafka as a byte array with a null key and the original message as the value, and is sent to the configured. Once the message reaches Kafka, Single Message Transforms (SMTs) are applied to structure and enrich the payload. The raw message is first wrapped into a JSON object, after which additional fields, such as a timestamp, are appended. The transformed message is then consumed by the sink connector and written to the PostgreSQL database, where the JSON fields are mapped to the corresponding table columns.

For authentication configuration you can refer:

https://ibm.github.io/event-automation/es/security/managing-access/#managing-access-to-kafka-resources

JDBC Sink Connector Setup

Step 1: Create a Kafka Connect cluster for JDBC

1a. Create a KafkaConnect custom resource on OCP.

apiVersion: eventstreams.ibm.com/v1beta2

kind: KafkaConnect # kafka connect

metadata:

  annotations:

    # ...

  name: sach-jdbc-connect # name of the JDBC kafka connect

  managedFields:

    # ...

  namespace: test-ns # namespace in which the connect is being configured

spec:

  # ...

  type: imagestream

  plugins: # refer to the description below

    - artifacts:

        - type: zip

          url: 'https://github.com/Aiven-Open/jdbc-connector-for-apache-kafka/releases/download/v6.10.0/jdbc-connector-for-apache-kafka-6.10.0.zip'

      name: postgres-connect-plugin

    - artifacts:

        - type: jar

          url: 'https://jdbc.postgresql.org/download/postgresql-42.7.3.jar'

      name: postgres-jdbc-driver

Refer to the plugins part here carefully. This configuration defines the set of plugins required for the Kafka Connect environment to enable integration with external systems.

• It includes a PostgreSQL connector plugin, packaged as a ZIP artifact, which provides the necessary functionality to stream data between Kafka and a PostgreSQL database.

• Additionally, a PostgreSQL JDBC driver is included as a JAR file to facilitate database connectivity.

Together, these plugins ensure that Kafka Connect is equipped to process, transform, and persist data effectively across the defined pipeline.

For details on each of the field and its values, refer https://ibm.github.io/event-

automation/es/connecting/setting-up-connectors/#use-the-event-streams-operator

Refer to the connect catalog to get the correct type and the url:

https://ibm.github.io/event-automation/connectors/kc-sink-jdbc-aiven/installation

1b. Apply the configuration:

 oc apply -f jdbc-kafka-connect.yaml

1c. Verify Kafka Connect status:

oc get kafkaconnect <jdbc-connect-cluster-name> -n <namespace>

Step 2: Set up PostgreSQL on OCP

Before creating the connector instance, you need a running PostgreSQL deployment.

2a. To set up PostgreSQL:

oc new-app postgresql-persistent \

  -p POSTGRESQL_USER=<db-username> \

  -p POSTGRESQL_PASSWORD=<db-password> \

  -p POSTGRESQL_DATABASE=<db-name> \

  -p VOLUME_CAPACITY=1Gi \

  -n <namespace>

>oc new-app postgresql-persistent -p POSTGRESQL_USER=testuser -p POSTGRESQL_PASSWORD=testpass -p POSTGRESQL_DATABASE=testdb -p VOLUME_CAPACITY=1Gi -n test-ns

2b. Confirm the pod is running and note the service name:

oc get pods -n <namespace> | grep postgresql

oc get svc postgresql -n <namespace>

>oc get pods -n test-ns | grep postgresql

postgresql-1-deploy               0/1     Completed   0          47s

postgresql-1-flkrh                1/1     Running     0          43s

>oc get svc postgresql -n test-ns

NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE

postgresql   ClusterIP   172.30.24.250   <none>        5432/TCP   5

Step 3: Create the JDBC Sink Connector instance

3a. Describe the PostgreSQL service to find the cluster-internal hostname and port:

>oc describe svc postgresql -n test-ns

Name:              postgresql

Namespace:         test-ns

Selector:          name=postgresql

Type:              ClusterIP

IP Family Policy:  SingleStack

IP Families:       IPv4

IP:                172.30.24.250

IPs:               172.30.24.250

Port:              postgresql  5432/TCP

TargetPort:        5432/TCP

Endpoints:         10.254.21.160:5432

The JDBC URL follows this pattern:

jdbc:postgresql://<service-name>.<namespace>.svc.cluster.local:<port>/<database>

>For example:

jdbc:postgresql://postgresql.my-namespace.svc.cluster.local:5432/testdb

Host: postgresql.test-ns.svc.cluster.local; Port: 5432; Database: testdb

3b. Connector configuration

A minimal `jdbc-sink-connector.yaml` looks like this:

apiVersion: eventstreams.ibm.com/v1beta2

kind: KafkaConnector

metadata:

  name: jdbc-sink-connector

  labels:

    eventstreams.ibm.com/cluster: <jdbc-connect-cluster-name>

spec:

  class: io.aiven.connect.jdbc.JdbcSinkConnector

  tasksMax: 1

  config:

    topics: mq-source-topic

    connection.url: jdbc:postgresql://postgresql.<namespace>.svc.cluster.local:5432/<db-name>

    connection.user: < ${file:/mnt/db-username:username}>

    connection.password: < ${file:/mnt/db-credentials:password}>

    # Write mode — insert a new row for every message

    insert.mode: insert

    # Auto-create the target table from the record schema (disabled by default)

    auto.create: true

    # Converter must have schemas enabled so the connector knows the field types

    value.converter: org.apache.kafka.connect.json.JsonConverter

    value.converter.schemas.enable: "true"

    # SMTs: wrap the raw value in a struct, then add a timestamp field

    transforms: wrapValue,addTimestamp

    transforms.wrapValue.type: org.apache.kafka.connect.transforms.HoistField$Value

    transforms.wrapValue.field: payload

    transforms.addTimestamp.type: org.apache.kafka.connect.transforms.InsertField$Value

    transforms.addTimestamp.timestamp.field: event_timestamp

This configuration defines a Kafka Connect JDBC sink connector that consumes messages from the mq-source-topic Kafka topic and writes them to a PostgreSQL database. Before the data is persisted, SMTs wrap the incoming message into a structured JSON object and add a timestamp field. The connector is configured to insert records, automatically create the target table if it does not exist, and connect to PostgreSQL using the specified JDBC connection details, ensuring that the transformed messages are stored in a structured and queryable format.

Note: The connector supports four insertion modes: insert, multi, upsert, and update. Refer document

3c. Apply the JDBC Sink Connector:

oc apply -f jdbc-sink-connector.yaml

3d. Verify it is running:

oc get kafkaconnector jdbc-sink-connector -n <namespace>

oc get kafkaconnector jdbc-sink-connector -n <namespace> \

  -o jsonpath='{.status.connectorStatus.connector.state}'

Example:

>oc get kafkaconnector jdbc-sink-test -n test-ns

NAME             CLUSTER                 CONNECTOR CLASS                             MAX TASKS   READY

jdbc-sink-test   sach-jdbc-connector     io.aiven.connect.jdbc.JdbcSinkConnector     1           True

>oc get kafkaconnector jdbc-sink-test -o jsonpath='{.status.connectorStatus.connector.state}'

RUNNING%

You want to see `RUNNING`. If you see `FAILED`, check the Kafka Connect pod logs first.

TESTING

To validate the end-to-end streaming pipeline, incoming records must first reside in the designated IBM Event Streams (Kafka) topic, regardless of the source application connected to the cluster.

IBM MQ publishes messages to a queue. An MQ Source Connector moves those messages into a Kafka topic(mq-source-topic) in Event Streams. From there, the JDBC Sink Connector picks them up and writes them to Postgres. The sink connector needs structured, schema-aware records, so SMTs sit in the middle to wrap raw bytes into a JSON object and attach a timestamp field before the records reach the connector.

STEP1: Send a message to MQ

Here for testing, I have put 2 messages: ‘testing connector configuration’ ,‘end to end flow success!!’, as below.

>sh-5.1$ dspmq

QMNAME(sachmq)                                            STATUS(Running)

>sh-5.1$ /opt/mqm/samp/bin/amqsput DEV.QUEUE.1 sachmq

Sample AMQSPUT0 start

target queue is DEV.QUEUE.1

testing connector configuration

end to end flow Success!!

STEP 2: Verify the message arrived in Event Streams

2a. Open the Event Streams UI (the link appears on the right-hand side of the ES instance page in OCP).

2b. Log in with the Kafka user you configured. To get the password, navigate to the Kafka user's resources section and scroll down to copy it.

2c. Select Topics from the left menu, open mq-source-topic, go to the Messages tab, and refresh. Your test message should appear as shown below.

STEP 3: Query PostgreSQLQuery

To check if the data from the kafka topic has been inserted into the target database.

3a. Using OC CLI:

• List all tables:

oc exec <postgresql-pod-name> -n <namespace> -- \

  psql -U <db-username> -d <db-name> -c "\dt"

> oc exec postgresql-1-flkrh -n test-ns -- psql -U testuser -d testdb -c "\dt"

         List of relations

 Schema |       Name       | Type  |  Owner 

-------------+------------------+-------+------------

 public | mq-source-topic | table | testuser

(1 row)

• View all rows in the target table:

oc exec <postgresql-pod-name> -n <namespace> -- \

  psql -U <db-username> -d <db-name> -c 'SELECT * FROM "<table-name>";'

> oc exec postgresql-1-flkrh -n test-ns -- psql -U testuser -d testdb -c 'SELECT * FROM "mq-source-topic";'

message                                               |        timestamp       

------------------------------------------------------+-------------------------

 helooooo!! this is sam here!                         | 2026-03-18 17:35:14.091

 hi                                                   | 2026-03-18 17:41:49.742

 test1                                                | 2026-03-18 17:41:52.502

 test2                                                | 2026-03-18 17:41:54.882

 hi there! testing connect!                           | 2026-04-21 13:56:17.289

 {"orderId":"21","price":300,"product":"laptop"}      | 2026-04-22 04:34:38.14

 {"orderId":"5","price":140,"product":"keyboard"}     | 2026-04-22 04:35:25.863

 {"orderId":"24","price":330,"product":"monitor"}     | 2026-04-22 04:35:35.671

 Test message from MQ                                 | 2026-04-26 08:41:44.076

 testing connector configuration                      | 2026-04-26 08:43:35.275

 end to end flow success!!                            | 2026-04-26 08:43:46.657

(11 rows)

• View latest messages

oc exec <postgresql-pod-name> -n <namespace> -- \

  psql -U <db-username> -d <db-name> \

  -c 'SELECT * FROM "<table-name>" ORDER BY event_timestamp DESC LIMIT 5;'

> oc exec postgresql-1-flkrh -n test-ns -- psql -U testuser -d testdb -c 'SELECT * FROM "mq-source-topic" ORDER BY timestamp DESC LIMIT 5;'

message                                               |        timestamp       

------------------------------------------------------+-------------------------

 end to end flow success!!                            | 2026-04-26 08:43:46.657

 testing connector configuration                      | 2026-04-26 08:43:35.275

 Test message from MQ                                 | 2026-04-26 08:41:44.076

 exit                                                 | 2026-04-22 04:39:47.333

 {"orderId":"24","price":330,"product":"monitor"}     | 2026-04-22 04:35:35.671

(5 rows)

3b. Using OCP UI:

1. Go to Workloads → Pods, find the PostgreSQL pod, and open Terminal.

2. Connect to the database: psql -h localhost -p 5432 -U <db-username> -d <db-name>

3. Run your query: SELECT * FROM "mq-source-topic" ORDER BY event_timestamp DESC LIMIT 5;

0 comments
14 views

Permalink