Note – Apache Kafka is supported as a Target since version 6, by Repstance Advanced Edition only (see Supported Database Requirements).
This page describes the messages that the Kafka Apply Process publishes: the topics, the key, the value and the events of DDL. The messages are JSON. The value is an envelope with the fields before, after, source, op and ts_ms. How to set up the replication is described in Replication to Apache Kafka.
| Field | Description |
|---|---|
| before | The row before the change, an object with the columns. null for an insert and for a row of an Initial Load. |
| after | The row after the change. null for a delete. |
| source.schema, source.table | The schema and the table of the source, as they are in the source. The topic has the names after the mapping rules. |
| source.lsn | The LSN of the transaction of the source, as text. All the changes of a transaction have it. |
| source.seq | The number of the change in its transaction, from 1, in the order of the transaction. A transaction that is applied in parts is numbered as a whole. A tombstone has no number. For a row of an Initial Load it is the number of the row in the load. |
| source.md | The number of the trail file in which the definition of the tables (the dictionary) that was in force was written. It changes only when the Capture Process starts a new trail file with a new dictionary, so it is not the version of a table. To place a message in relation to an event of DDL, compare their source.lsn. |
| source.snapshot | "true" for a row of an Initial Load, otherwise "false". It is a string. |
| op | c – insert, u – update, d – delete, r – a row of an Initial Load (a snapshot). |
| ts_ms | The time of the commit of the transaction, in milliseconds since 1970-01-01 UTC. For a row of an Initial Load it is the time of the load. |
| truncated | Only when a value was cut: the list of the names of the columns that were cut, see Large Values below. |
The pair (source.lsn, source.seq) identifies a change. The Apply Process publishes a message at least once: after a failure or a restart it can publish again a message that it published before, and the repeat has the same pair, so a consumer can drop it. A consumer that writes the rows by their keys gets the same result with a repeat. With batchmode=2 the numbering of a batch that is published again can differ from the first one, so such messages are applied by the key.
An insert:
topic: dbo.emp
key: {"id":1}
header: lsn=0x0000008D00007B7E0007
value: {"before":null,"after":{"id":1,"name":"xxxx","dt":"2026-10-01T12:50:00.1234567"},"source":{"schema":"dbo","table":"emp","lsn":"0x0000008D00007B7E0007","seq":1,"md":42,"snapshot":"false"},"op":"c","ts_ms":1790859001345}An update has both images:
key: {"id":1}
value: {"before":{"id":1,"name":"xxxx","dt":"2026-10-01T12:50:00.1234567"},"after":{"id":1,"name":"yyyy","dt":"2026-10-01T12:50:00.1234567"},"source":{"schema":"dbo","table":"emp","lsn":"0x0000008D00007B90000B","seq":1,"md":42,"snapshot":"false"},"op":"u","ts_ms":1790859001345}A delete has the row in before, and a tombstone follows it:
key: {"id":2}
value: {"before":{"id":2,"name":"yyyy","dt":"2026-10-01T12:50:00.1234567"},"after":null,"source":{"schema":"dbo","table":"emp","lsn":"0x0000008D00007C10000A","seq":1,"md":42,"snapshot":"false"},"op":"d","ts_ms":1790859001345}
key: {"id":2}
value: null (a tombstone)With kafka.tombstones=1, the default, a delete is followed by a tombstone, a message with the same key and no value. A topic with the setting cleanup.policy=compact forgets the row after it. A change of the key columns of a row is an update with the new key, followed by a tombstone with the old key:
key: {"id":2}
value: {"before":{"id":1,"name":"yyyy","dt":"2026-10-01T12:50:00.1234567"},"after":{"id":2,"name":"yyyy","dt":"2026-10-01T12:50:00.1234567"},"source":{"schema":"dbo","table":"emp","lsn":"0x0000008D00007BA0000C","seq":1,"md":42,"snapshot":"false"},"op":"u","ts_ms":1790859001345}
key: {"id":1}
value: null (a tombstone)A table without a key has no tombstones: its messages have no key, and an update and a delete carry the whole row in before. A topic with cleanup.policy=compact refuses messages without a key, so validate process reports a table without a key that goes to a compacted topic as an error (see Compacted Topics in Replication to Apache Kafka).
The rows of the Initial Load are published to the topic of the table before the changes, with op r and source.snapshot "true". The key is the key of the row:
key: {"EmployeeID":1}
value: {"before":null,"after":{"EmployeeID":1,"Name":"Ann","Salary":1000.50,"Active":true},"source":{"schema":"dbo","table":"employees","lsn":"0x0000008D00007B7E0007","seq":1,"md":42,"snapshot":"true"},"op":"r","ts_ms":1792922403000}| Type in the source | Value in the message |
|---|---|
| Integers, decimals, floating point numbers, money, and a MySQL BIT(n) | A JSON number with the digits of the source: 1000.50. The value of an Oracle NUMBER is written as a valid JSON number: 01 becomes 1, .50 becomes 0.50. NaN and Infinity are not numbers in JSON, they stay strings. |
| A SQL Server bit, a PostgreSQL boolean | true or false |
| Character types, enums, sets, large text, XML | A JSON string, with the characters that JSON needs escaped: "say \"hi\"\n". A JSON column is a string with the text of the JSON. |
| A date | A string: "2026-10-01" |
| A date with a time, a timestamp | A string in the ISO 8601 form, with the precision of the type in the source: 7 digits of the second for a SQL Server datetime2, 9 for an Oracle TIMESTAMP, 6 for MySQL and PostgreSQL, none for an Oracle DATE: "2026-10-01T12:50:00.1234567". A type with a time zone is in UTC with the suffix Z: "2026-10-22T10:00:00.123456Z". |
| A MySQL TIME | A string with its text, which can have more than 24 hours. |
| Binary types, Oracle RAW, BLOB, geometry | A string with 0x and the bytes in lower case hexadecimal: "0x00ff" |
| NULL | null |
| An empty text | "" in a column of a text type, and null in a column of a type that has no empty value. |
An insert with a NULL and a text that needs escapes:
value: {"before":null,"after":{"id":3,"name":"say \"hi\"\n","dt":null},"source":{"schema":"dbo","table":"emp","lsn":"0x0000008D00007D00000D","seq":1,"md":42,"snapshot":"false"},"op":"c","ts_ms":1790859001345}A message has the columns that the trail has. When the Source Database logs only the changed columns and the key, for example Oracle without supplemental logging of all columns, the images of an update have only those columns. A column that is not in the message was not in the trail, it is not NULL.
A key and a value together are at most kafka.maxmessagebytes bytes. When a row is larger, the longest strings of the row, the text and the binary values, are cut to fit, in the images before and after alike, and the message lists the columns that were cut:
..."after":{"id":1,"doc":"…","bin":"0x…"},...,"op":"c","ts_ms":1790859001345,"truncated":["doc","bin"]}In a test a row with a text of 3,000,000 characters and a binary value of 2,000,000 bytes became one message of 979,990 bytes, with both columns cut to about half of a million characters, and a value of 400 KB stayed whole.
A change of a table, a create, an alter, a drop and a truncate, is an event in the topic of the DDL events. The topic has one partition, so the events are in the order of the trail. A change of an index that does not change the key columns is not an event. The key is the name of the table: {"table":"dbo.employees"}.
An alter that adds a column:
topic: ddl
key: {"table":"dbo.employees"}
value: {"before":[{"id":1,"name":"EmployeeID","type":"int","key":true,"nullable":false,"identity":false},{"id":2,"name":"Name","type":"nvarchar(100)","key":false,"nullable":false,"identity":false},{"id":3,"name":"Salary","type":"decimal(10,2)","key":false,"nullable":false,"identity":false},{"id":4,"name":"Active","type":"bit","key":false,"nullable":false,"identity":false}],"after":[{"id":1,"name":"EmployeeID","type":"int","key":true,"nullable":false,"identity":false},{"id":2,"name":"Name","type":"nvarchar(100)","key":false,"nullable":false,"identity":false},{"id":3,"name":"Salary","type":"decimal(10,2)","key":false,"nullable":false,"identity":false},{"id":4,"name":"Active","type":"bit","key":false,"nullable":false,"identity":false},{"id":5,"name":"Region","type":"varchar(20)","key":false,"nullable":true,"identity":false}],"source":{"schema":"dbo","table":"employees","lsn":"0x0000008D00008000000F","seq":1,"md":42,"snapshot":"false"},"op":"alter","ts_ms":1790859001345}The messages that were published before the change do not have the new column. A consumer reads the events of the DDL topic to learn which columns the messages after the change have.
The load option of an Initial Load (see Initial Load) cannot clean up a topic: a topic has no delete and no truncate. The option is published as events of DDL, before the first row of the table, so that a consumer is told that the table starts again. The events have no field ddl, because no statement of the source made them:
Apache, Apache Kafka, Kafka and the Kafka logo are either registered trademarks or trademarks of the Apache Software Foundation in the United States and/or other countries. No endorsement by The Apache Software Foundation is implied by the use of these marks.
Prev page:
Replication to Apache KafkaNext page:
DATABASE CONFIGURATION COMMANDS