---
title: "Kafka read_committed vs read_uncommitted (isolation.level)"
description: "How Kafka isolation.level works: read_committed waits for transactions and skips aborted records, read_uncommitted returns everything. Step through it live."
url: https://monedula.dev/kafka-simulator/configs/isolation-level/
---

# isolation.level: read_committed vs read_uncommitted

Consumer config · default read_uncommitted · Apache Kafka 4.3.

isolation.level controls whether a consumer sees records from Kafka transactions before they commit. read_uncommitted (the default) returns every record as soon as it is replicated, including records of open and aborted transactions. read_committed returns only committed transactional records and stops at the last stable offset (LSO) until each open transaction commits or aborts.

## At a glance

- Config: `isolation.level` (consumer)
- Default: read_uncommitted
- Valid values: read_uncommitted, read_committed
- Reference: [Apache Kafka documentation](https://kafka.apache.org/43/configuration/consumer-configs/#consumerconfigs_isolation.level)

## Values

- `read_uncommitted`: poll() returns every record up to the high watermark, whether its transaction later commits, aborts or is still open.
- `read_committed`: poll() returns records only up to the last stable offset, the first offset of the oldest open transaction, and filters out the records of aborted transactions. Non-transactional records are returned too, in offset order.

## What it does

A transactional producer writes its records to the log straight away and resolves them later with a commit or abort marker. The partition keeps two bounds: the high watermark, up to which records are replicated, and the last stable offset, below which every transaction is resolved. read_uncommitted reads to the high watermark. read_committed reads to the LSO and uses the list of aborted transactions the broker returns with each fetch to drop their records, so the application sees each transaction all at once or not at all.

## The trade-off

read_committed is what makes an exactly-once pipeline exactly once downstream: without it, a consumer reads records that are later aborted. The cost is latency: every record behind an open transaction, transactional or not, waits until that transaction ends, and a producer that hangs holds the LSO back until transaction.timeout.ms makes the coordinator abort it. read_uncommitted has no such wait but gives no transactional guarantee at all.

## Advice

- Set read_committed on every consumer downstream of a transactional producer; Kafka Streams with processing.guarantee=exactly_once_v2 sets it for its own consumers.
- Keep transactions short and transaction.timeout.ms modest: one stuck producer stalls every read_committed consumer of the partitions it wrote to.
- On a topic without transactional producers the two settings return the same records, so read_committed costs nothing there.

## See it in the simulator

- [Kafka isolation.level: read_committed vs read_uncommitted](https://monedula.dev/kafka-simulator/scenarios/transactions-read-committed/) (Transactions & read_committed), step 3: Produced · uncommitted. tx1 wrote one record to each partition. Both records are replicated (HW advanced), so read_uncommitted (ru) sees them - but the LSO is frozen, so read_committed (rc) is stuck and sees nothing. Neither partition's record is visible to rc until the transaction commits. [Open at this step](https://monedula.dev/kafka-simulator/playground/?scenario=04.2.2&t=2)
- [Kafka transaction commit vs abort with read_committed](https://monedula.dev/kafka-simulator/scenarios/txn-commit-vs-abort/) (Transactional write: commit vs abort), step 7: abort · range skipped. abort_transaction records the range [2,4) in abortedRanges and releases the LSO PAST it to 4. The read_committed consumer advances its position over the range but delivers NONE of those records - the aborted batch is discarded. The HW advanced for both batches; only commit-vs-abort decided what is visible. [Open at this step](https://monedula.dev/kafka-simulator/playground/?scenario=04.2.1&t=6)

## Related configs

- [Kafka enable.idempotence: no duplicates on retry](https://monedula.dev/kafka-simulator/configs/enable-idempotence/)
- [Kafka auto.offset.reset: earliest vs latest](https://monedula.dev/kafka-simulator/configs/auto-offset-reset/)
- [Kafka acks: 0 vs 1 vs all explained](https://monedula.dev/kafka-simulator/configs/acks/)
