00:00

Kafka Connect

Kafka Connect is a framework, included with Apache Kafka, that moves data between Kafka and other systems such as databases, file systems, cloud storage and search engines, without writing custom code.

This guide explains Connect’s components, how it works, its standalone and distributed modes, the REST API, converters, transforms and error handling, with formatted configuration and command-line examples and a custom connector written in Java. It builds on Kafka Introduction and Kafka Topic.

1. What Is Kafka Connect?

In simple words, Kafka Connect acts like a bridge. It takes data from one system and sends it to Kafka, or takes data from Kafka and sends it to another system, automatically and continuously.

Kafka Connect as a bridge
  MySQL, PostgreSQL, files, queues, APIs      (source systems)
                     |
                     v
        Kafka Connect: source connectors
                     |
                     v
              Kafka topics
                     |
                     v
        Kafka Connect: sink connectors
                     |
                     v
  Elasticsearch, S3, data warehouse           (target systems)

You choose a ready-made connector, give it a configuration, and Connect handles reading, writing, scaling, progress tracking and restarts.

2. Why Do We Need Kafka Connect?

In real-world applications, data comes from many sources such as MySQL, PostgreSQL, log files or APIs, and has to reach many destinations. Writing custom code for each integration is time-consuming and error-prone, because every program must handle:

  • Connecting to and reading from the source system.
  • Remembering how far it has read, so nothing is lost or repeated after a restart.
  • Converting data formats.
  • Retries, failures and scaling.

Kafka Connect solves this problem by providing a common framework and ready-made connectors that already handle all of this.

3. Key Components of Kafka Connect

ComponentMeaning
ConnectorA configured job that defines what data moves where, such as “copy table users into Kafka”
Source connectorReads data from an external system and writes it into Kafka topics
Sink connectorReads data from Kafka topics and writes it to an external system
TaskA unit of work that actually moves the data; a connector can split its work into several tasks
WorkerA Kafka Connect process (JVM) that runs connectors and tasks
ConverterTranslates between Kafka’s bytes and Connect’s internal data, for example JSON or Avro
Transform (SMT)A small change applied to each record as it passes through, such as renaming a field
Dead letter queueA topic where a sink connector can send records it could not process

4. Features of Kafka Connect

FeatureExplanation
No coding requiredMost integrations are done with configuration instead of code
ScalableIn distributed mode, tasks are spread across many workers to handle large volumes
Fault tolerantIf a worker stops, its connectors and tasks move to the other workers automatically. A task that fails because of an error stays failed until you restart it, usually through the REST API
Progress trackingConnect saves offsets, so connectors continue where they stopped after a restart
Reusable connectorsMany pre-built connectors exist for databases, cloud services, storage and message systems
Data transformationSingle Message Transforms (SMTs) modify data while it is being moved
Easy managementA REST API creates, updates, pauses and monitors connectors

5. How Kafka Connect Works

Inside a Connect worker
  SOURCE SIDE
  MySQL table
      |
      v
  +-------------------- Connect worker --------------------+
  |  source task  ->  transforms (SMTs)  ->  converter     |
  |  (saves source offsets in Kafka)                       |
  +--------------------------------------------------------+
      |
      v
  Kafka topic

  SINK SIDE
  Kafka topic
      |
      v
  +-------------------- Connect worker --------------------+
  |  converter  ->  transforms (SMTs)  ->  sink task       |
  |  (commits consumer offsets in Kafka)                   |
  +--------------------------------------------------------+
      |
      v
  Elasticsearch
  • A source task reads new data, turns it into records, and remembers its position (for example a database log position) as a source offset.
  • Records pass through optional transforms, then the converter turns them into bytes for Kafka.
  • A sink task is a Kafka consumer: it reads records, converts and transforms them, writes them to the target, and commits its consumer offsets.

6. Kafka Connect Example

Let’s say you have a MySQL database that stores user information, and you want to stream this data to Kafka in real time:

  1. A source connector reads changes from MySQL.
  2. The data is published into a Kafka topic.
  3. A sink connector reads the data from Kafka and writes it to Elasticsearch.
  4. The data is stored in Elasticsearch for searching and analytics.

This entire flow happens automatically without writing any custom integration code. Other applications can also read the same topic, for example to send welcome emails to new users. Section 11 shows the configuration for this example.

7. Kafka Connect Modes

AspectStandalone modeDistributed mode
ProcessesOne workerSeveral workers forming a group
ConfigurationProperties files on the command lineJSON sent to the REST API
Where state is keptA local fileKafka topics
Fault toleranceNone; if the process stops, data movement stopsTasks move to other workers automatically
ScalingLimited to one machineAdd workers to share the load
Use forDevelopment, testing, simple single-machine jobsProduction

8. Quick Start: Standalone Mode

Kafka ships with simple file connectors that are perfect for learning. In Kafka 3.2 and later they are not loaded by default, so add their JAR to the plugin path first:

config/connect-standalone.properties
bootstrap.servers=localhost:9092
key.converter=org.apache.kafka.connect.storage.StringConverter
value.converter=org.apache.kafka.connect.storage.StringConverter
offset.storage.file.filename=/tmp/connect.offsets

# Folder or JAR files where connector plugins are found
plugin.path=libs/connect-file-4.0.0.jar
file-source.properties
name=local-file-source
connector.class=FileStreamSource
tasks.max=1
file=/tmp/input.txt
topic=file-lines
Run it
# Start a standalone worker with the file source connector
bin/connect-standalone.sh config/connect-standalone.properties file-source.properties

# In another terminal: add lines to the file...
echo "first line" >> /tmp/input.txt
echo "second line" >> /tmp/input.txt

# ...and watch them arrive in Kafka
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 \
  --topic file-lines --from-beginning

Each new line in the file becomes a message in the file-lines topic. Stop and restart the worker, and it continues from where it stopped, because it saved its position in /tmp/connect.offsets.

9. Running Kafka Connect in Distributed Mode

In production, run several workers with the same group.id. They form a Connect cluster, store their state in Kafka topics, and share the connectors’ tasks:

config/connect-distributed.properties
bootstrap.servers=broker1:9092,broker2:9092,broker3:9092

# Workers with the same group.id form one Connect cluster
group.id=connect-cluster

# Kafka topics where Connect stores its state
config.storage.topic=connect-configs
offset.storage.topic=connect-offsets
status.storage.topic=connect-status
config.storage.replication.factor=3
offset.storage.replication.factor=3
status.storage.replication.factor=3

# Default data format
key.converter=org.apache.kafka.connect.json.JsonConverter
value.converter=org.apache.kafka.connect.json.JsonConverter

# REST API and plugins
listeners=http://0.0.0.0:8083
plugin.path=/opt/connectors

# Allow ${file:...} references so passwords stay out of connector configs
config.providers=file
config.providers.file.class=org.apache.kafka.common.config.provider.FileConfigProvider
Start a worker (repeat on each machine)
bin/connect-distributed.sh config/connect-distributed.properties

Install connector plugins by placing their JAR folders in plugin.path on every worker. In distributed mode, connectors are created through the REST API, not with properties files.

10. Managing Connectors with the REST API

Create a connector
curl -X POST http://localhost:8083/connectors \
  -H "Content-Type: application/json" \
  -d '{
    "name": "orders-file-sink",
    "config": {
      "connector.class": "FileStreamSink",
      "tasks.max": "1",
      "topics": "orders",
      "file": "/tmp/orders-out.txt"
    }
  }'
Check its status
curl -s http://localhost:8083/connectors/orders-file-sink/status
Sample response
{
  "name": "orders-file-sink",
  "connector": { "state": "RUNNING", "worker_id": "10.0.0.5:8083" },
  "tasks": [
    { "id": 0, "state": "RUNNING", "worker_id": "10.0.0.6:8083" }
  ],
  "type": "sink"
}
RequestPurpose
GET /connectorsList all connectors
GET /connector-pluginsList installed connector plugins
PUT /connectors/{name}/configCreate or update a connector’s configuration
GET /connectors/{name}/statusShow connector and task states
PUT /connectors/{name}/pause and /resumeTemporarily stop and continue data movement
POST /connectors/{name}/restart?includeTasks=true&onlyFailed=trueRestart failed tasks
DELETE /connectors/{name}Remove a connector

Because PUT /connectors/{name}/config both creates and updates, it is convenient for deployment scripts that keep connector configurations in version control.

11. Real Example: Database to Kafka to Search Engine

This example implements the MySQL to Elasticsearch flow from section 6 with two widely used connector plugins: a change data capture (CDC) source connector that reads every insert, update and delete from MySQL’s change log, and an Elasticsearch sink connector. Both are installed separately into plugin.path.

mysql-users-source.json
{
  "name": "mysql-users-source",
  "config": {
    "connector.class": "io.debezium.connector.mysql.MySqlConnector",
    "tasks.max": "1",
    "database.hostname": "mysql",
    "database.port": "3306",
    "database.user": "connect_user",
    "database.password": "${file:/opt/secrets/mysql.properties:password}",
    "database.server.id": "5401",
    "topic.prefix": "shop",
    "table.include.list": "shop.users",
    "schema.history.internal.kafka.bootstrap.servers": "broker1:9092",
    "schema.history.internal.kafka.topic": "schema-history.shop"
  }
}
users-search-sink.json
{
  "name": "users-search-sink",
  "config": {
    "connector.class": "io.confluent.connect.elasticsearch.ElasticsearchSinkConnector",
    "tasks.max": "2",
    "topics": "shop.shop.users",
    "connection.url": "http://elasticsearch:9200",
    "key.ignore": "false",
    "schema.ignore": "true"
  }
}
Deploy both connectors
curl -X POST -H "Content-Type: application/json" \
  --data @mysql-users-source.json http://localhost:8083/connectors

curl -X POST -H "Content-Type: application/json" \
  --data @users-search-sink.json http://localhost:8083/connectors

The source connector writes changes for table users to the topic shop.shop.users (prefix, database, table). The password is read from a separate file through the file config provider, so it never appears in the REST API. Check each plugin’s documentation and license for the exact options of your version; the CDC source also needs a MySQL user with replication permissions and binary logging enabled.

12. Converters and Data Formats

ConverterFormat in KafkaNotes
StringConverterPlain textSimple, no structure
JsonConverterJSONWith schemas.enable=true, each message also carries its schema, which makes messages larger
ByteArrayConverterRaw bytesPasses data through unchanged
Avro, Protobuf or JSON Schema convertersCompact binary or JSON with a schema registryThird-party converters; schemas are stored once in a registry and evolve safely
Per-connector converter settings
"key.converter": "org.apache.kafka.connect.storage.StringConverter",
"value.converter": "org.apache.kafka.connect.json.JsonConverter",
"value.converter.schemas.enable": "false"

Converter settings in a connector override the worker defaults. The producer and consumer sides of a pipeline must agree on the format, or the sink will fail to read the data.

13. Single Message Transforms (SMTs)

Single Message Transforms make small, record-by-record changes without writing code. Several transforms can be chained:

Add a field and rename the topic
"transforms": "addSource,route",

"transforms.addSource.type": "org.apache.kafka.connect.transforms.InsertField$Value",
"transforms.addSource.static.field": "source_system",
"transforms.addSource.static.value": "mysql-shop",

"transforms.route.type": "org.apache.kafka.connect.transforms.RegexRouter",
"transforms.route.regex": "shop\\.shop\\.(.*)",
"transforms.route.replacement": "shop-$1"

Here every record gets a source_system field, and the topic shop.shop.users is renamed to shop-users.

Built-in transformWhat it does
InsertFieldAdds a fixed value, topic name, partition, offset or timestamp as a field
ReplaceFieldKeeps, drops or renames fields
MaskFieldReplaces sensitive field values, for example with an empty value
ValueToKey and ExtractFieldBuild the record key from value fields
RegexRouter and TimestampRouterChange the topic name
Filter with predicatesDrop records that match a condition
CastChange field data types

Use SMTs for simple changes. For joins, aggregations or complex logic, use a stream processing application instead.

14. Error Handling and Dead Letter Queues

By default (errors.tolerance=none), a task stops on the first record it cannot convert or transform. For sink connectors you can instead skip bad records and keep them in a dead letter queue (DLQ) topic:

Sink connector error settings
"errors.tolerance": "all",
"errors.retry.timeout": "300000",
"errors.log.enable": "true",
"errors.log.include.messages": "false",
"errors.deadletterqueue.topic.name": "dlq-users-search-sink",
"errors.deadletterqueue.topic.replication.factor": "3",
"errors.deadletterqueue.context.headers.enable": "true"
  • Failed records go to dlq-users-search-sink, with headers describing the error.
  • Temporary errors are retried for up to 5 minutes before the record counts as failed.
  • Watch the DLQ topic: a growing DLQ means data is not reaching the target.

Only sink connectors support a DLQ. If a task fails anyway, find the cause in the status API or worker logs, fix it, and restart the task through the REST API.

15. Writing a Custom Source Connector in Java

If no ready-made connector fits, you can write your own. A connector has two parts: the connector class (configuration and splitting work into tasks) and the task class (moving data). This small source connector writes an increasing counter to a topic and resumes from the last value after a restart:

pom.xml
<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>connect-api</artifactId>
    <version>4.0.0</version>
    <scope>provided</scope>
</dependency>
CounterSourceConnector.java
package com.example.connect;

import java.util.List;
import java.util.Map;

import org.apache.kafka.common.config.ConfigDef;
import org.apache.kafka.connect.connector.Task;
import org.apache.kafka.connect.source.SourceConnector;

public class CounterSourceConnector extends SourceConnector {

    static final String TOPIC = "topic";
    static final String INTERVAL_MS = "interval.ms";

    private static final ConfigDef CONFIG = new ConfigDef()
            .define(TOPIC, ConfigDef.Type.STRING, ConfigDef.Importance.HIGH,
                    "Topic to write counter values to")
            .define(INTERVAL_MS, ConfigDef.Type.LONG, 1000L, ConfigDef.Importance.MEDIUM,
                    "Pause between values in milliseconds");

    private Map<String, String> settings;

    @Override
    public void start(Map<String, String> props) {
        this.settings = props;
    }

    @Override
    public Class<? extends Task> taskClass() {
        return CounterSourceTask.class;
    }

    @Override
    public List<Map<String, String>> taskConfigs(int maxTasks) {
        return List.of(settings);          // one counter, so one task is enough
    }

    @Override
    public void stop() {
    }

    @Override
    public ConfigDef config() {
        return CONFIG;
    }

    @Override
    public String version() {
        return "1.0.0";
    }
}
CounterSourceTask.java
package com.example.connect;

import java.util.List;
import java.util.Map;

import org.apache.kafka.connect.data.Schema;
import org.apache.kafka.connect.source.SourceRecord;
import org.apache.kafka.connect.source.SourceTask;

public class CounterSourceTask extends SourceTask {

    private static final Map<String, String> SOURCE_PARTITION = Map.of("source", "counter");

    private String topic;
    private long intervalMs;
    private long next;

    @Override
    public void start(Map<String, String> props) {
        topic = props.get(CounterSourceConnector.TOPIC);
        intervalMs = Long.parseLong(props.getOrDefault(CounterSourceConnector.INTERVAL_MS, "1000"));

        // Resume from the last saved position, if any
        Map<String, Object> saved = context.offsetStorageReader().offset(SOURCE_PARTITION);
        next = (saved == null) ? 0L : (Long) saved.get("position") + 1;
    }

    @Override
    public List<SourceRecord> poll() throws InterruptedException {
        Thread.sleep(intervalMs);

        SourceRecord record = new SourceRecord(
                SOURCE_PARTITION,
                Map.of("position", next),     // source offset saved by Connect
                topic,
                Schema.INT64_SCHEMA,
                next);
        next++;
        return List.of(record);
    }

    @Override
    public void stop() {
    }

    @Override
    public String version() {
        return "1.0.0";
    }
}

Package the classes as a JAR, copy it into a folder listed in plugin.path on every worker, restart the workers, and create the connector with "connector.class": "com.example.connect.CounterSourceConnector" and a topic setting. Connect takes care of producing the records, saving offsets and restarting the task on another worker if one fails.

16. Monitoring Kafka Connect

What to watchHow
Connector and task statesGET /connectors/{name}/status; alert on any FAILED state
Sink connector lagSink tasks are consumers in the group connect-<connector name>; check lag with kafka-consumer-groups.sh
Throughput and errorsJMX metrics such as records read and written per task, and error counts
Dead letter queuesMessage count in each DLQ topic
Worker logsStack traces explaining why a task failed

17. Kafka Connect vs Writing Your Own Code

AspectKafka ConnectCustom producer or consumer code
EffortConfiguration of an existing connectorWrite, test and maintain code
Offset tracking and restartsBuilt inYou implement it
ScalingAdd workers or tasksYou design it
Data formatsPluggable convertersYou implement serialization
Best forMoving data between standard systemsBusiness logic and custom processing

Use Connect to move data, and normal applications or stream processors to process it.

18. Advantages of Kafka Connect

  • Reduces development effort.
  • Provides a standard way to move data in and out of Kafka.
  • Handles offsets, retries and worker failures automatically.
  • Is easy to configure and manage through a REST API.
  • Has a large ecosystem of ready-made connectors.

19. Best Practices

DoAvoid
Use distributed mode in production with at least two workersRunning production pipelines in standalone mode
Use replication factor 3 for Connect’s internal topicsLow replication for config, offset and status topics
Keep passwords in config providers or secret storesPutting passwords directly in connector JSON
Store connector configs in version control and deploy with PUT .../configCreating connectors by hand without a record
Set tasks.max based on partitions or tablesVery high task counts that only add overhead
Use a DLQ for sink connectors and monitor itSilently dropping bad records
Alert on failed tasks and restart them after fixing the causeAssuming failed tasks restart by themselves
Check each connector plugin’s license and version compatibilityMixing plugin versions across workers

20. Summary

Kafka Connect is a powerful and easy-to-use framework that simplifies data integration with Kafka. Source connectors bring data in, sink connectors send it out, and workers run their tasks with built-in offset tracking, scaling and failover. Converters handle data formats, SMTs make small changes on the way, and dead letter queues catch bad records, all managed through a REST API.

It allows teams to focus on business logic instead of writing and maintaining complex data pipelines. With ready-made connectors, scalability and fault tolerance, Kafka Connect is an essential tool for building reliable real-time data systems.