Home › Data Engineering › Kafka RecordTooLargeException — Fix the Size Chain
Beginner 5 min · September 23, 2026

Kafka RecordTooLargeException — Fix the Size Chain

RecordTooLarge means one of four size limits bit.

N
Naren Founder & Principal Engineer

20+ years shipping production backend systems. Notes here come from systems that actually shipped.

Follow
✓ Production
production tested
September 27, 2026
last updated
2,085
articles · all by Naren
Before you start⏱ 15 min
  • ✓A Kafka topic you can produce to
  • ✓Basic producer and broker config familiarity
  • ✓Sample payloads near your size limits
 ● Production Incident 🔎 Debug Guide
⚡Quick Answer
  • RecordTooLarge means your payload beat one of four size limits — find which link before touching any knob
  • The chain: broker max.message.bytes, topic message.max.bytes, replica.fetch.max.bytes, consumer fetch.max.bytes
  • Don't forget producer max.request.size: legal-size records can still fail as an oversized batch
  • Durable fix is usually chunking or claim-check (blob in storage, reference in Kafka), not bigger knobs
✦ Definition~90s read
What is Kafka RecordTooLargeException?

Kafka caps message size to protect the cluster: huge records hog replication threads, blow consumer heaps, and stall every fetch behind them. RecordTooLargeException is the client-side name for breaching one of those caps. Under the hood the broker replies MESSAGE_TOO_LARGE, and the producer client raises — no retry policy fixes it, because the payload won't shrink on attempt two.

★
Imagine mailing a package through four checkpoints: post office counter, sorting machine, delivery truck door, and your mailbox slot.

Four settings form the enforcement chain. Broker max.message.bytes (default ~1 MB, older name message.max.bytes at topic scope) gates appends. Producer max.request.size (default 1 MB) gates the whole batch envelope. Replica replica.fetch.max.bytes gates follower replication.

Consumer fetch.max.bytes plus max.partition.fetch.bytes gate reader fetches. Every gate must exceed your largest record, or the failure simply relocates to the next narrow link.

Two design patterns avoid the chain entirely. Chunking splits payloads into ordered sub-MB pieces with reassembly metadata. Claim-check uploads the blob to object storage and sends a tiny reference event instead. Both keep the commit log fast and bounded while handling arbitrarily large business payloads — which is why senior teams reach for them before reaching for config.

Plain-English First

Imagine mailing a package through four checkpoints: post office counter, sorting machine, delivery truck door, and your mailbox slot. Widening just the counter changes nothing if the truck door stays narrow — the package gets stuck at the next stop. Kafka's four size settings work the same way: every checkpoint must fit the package. And the smarter move is usually splitting the shipment into small boxes (chunking) or mailing a pickup slip instead of the piano itself (claim-check).

Your producer throws RecordTooLargeException on a Tuesday afternoon. Someone suggests raising max.message.bytes, so you do — and the error moves to the replicas. You raise that too, and now the consumers choke. Three config changes later the original 12 MB payload flows, and so does a quiet tax on every other topic on the cluster.

This is the size-chain trap: Kafka enforces message size in four places, and widening one link just pushes the failure to the next. Worse, each increase has a real cost — broker memory, replication bandwidth, consumer heap — paid by every topic, not just yours.

This beginner-friendly guide maps the whole chain: max.message.bytes, message.max.bytes, replica.fetch.max.bytes, and consumer fetch.max.bytes, what each guards, and how to check all four in minutes. More importantly, it shows why the durable fix is usually a design change — chunking or claim-check references — instead of bigger knobs. You'll leave knowing exactly when raising a limit is fine and when it's a loan your cluster will collect.

What RecordTooLargeException Tells You

RecordTooLargeException is the producer client telling you the broker refused your batch: at least one record (or the batch envelope) exceeded a configured limit. The broker answers with MESSAGE_TOO_LARGE, the client surfaces it as RecordTooLargeException, and the send fails — usually retrying pointlessly, since retries don't shrink payloads. It's a config-and-design error, not a transient one.

Beginners misread it as a single knob because the first search result says 'raise max.message.bytes.' That advice is a quarter of the story. Kafka checks size at four gates: the broker append limit, the topic-level override, the replica fetch limit followers use, and the consumer fetch limit your readers use. Clearing gate one with a 12 MB payload just schedules a failure at gate two or three — each louder and weirder than the last.

The right reflex is measurement before mutation. Log the serialized byte size of the failing payload, read all four caps in one pass, and identify every link smaller than your payload. Then — and only then — decide whether this payload deserves bigger gates or a smaller shape. Most oversize payloads are accidents (full PDFs, raw images, unbounded JSON arrays) that no commit log should carry at any limit.

📊 Production Insight
A team retried 12 MB sends for 3 days because retries feel productive. Rule: RecordTooLarge never heals with retries — it needs a smaller payload or wider gates.
🎯 Key Takeaway
It's a design-and-config signal, not a transient error — measure the payload, read all four gates, then decide shape vs knobs.

The Four-Knob Size Chain, End to End

The chain has four links, and the narrowest wins. Broker max.message.bytes (default ~1 MB) caps single records at append time. Topic-level message.max.bytes overrides it per topic — same meaning, narrower scope. Replica replica.fetch.max.bytes caps what followers may fetch for replication; smaller than your records means followers starve and under-replicated partitions grow. Consumer fetch.max.bytes and max.partition.fetch.bytes cap what readers pull; smaller than your records wedges consumers at one offset forever.

Two producer-side settings complete the picture. max.request.size (default 1 MB) caps the entire produce request — batch envelope, not single record. With batch.size at 16 KB and linger.ms batching aggressively, fifty modest records can breach it together. buffer.memory and compression.type (lz4 or zstd) shape how often you hit the ceiling: compression shrinks JSON payloads 4-10x and is the cheapest headroom you'll ever buy.

Audit the chain as one unit with the script above. Write down all six numbers (four gates plus batch/request caps) next to your largest real payload before changing anything. If any gate is smaller than the payload, that gate is your current error — and every other smaller-or-equal gate is your next error. Change them as a reviewed set or don't change them at all.

size_chain_audit.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Read all four gates in one pass
grep -E 'max.message.bytes|message.max.bytes|replica.fetch.max.bytes' /etc/kafka/server.properties
grep -E 'max.request.size|batch.size|linger.ms|buffer.memory' /etc/app/producer.properties
grep -E 'fetch.max.bytes|max.partition.fetch.bytes' /etc/app/consumer.properties

# Topic-level overrides beat broker defaults
bin/kafka-configs.sh --bootstrap-server $BROKERS \
  --entity-type topics --entity-name invoices --describe

# Defaults for reference:
# broker message.max.bytes      = 1000012 (~1 MB)
# producer max.request.size     = 1048576 (1 MB)
# consumer fetch.max.bytes      = 57671680 (55 MB)
# consumer max.partition.fetch  = 1048576 (1 MB)
📊 Production Insight
Raising only max.message.bytes moved one team's failure from producer to replicas to 24 OOMKilled consumers. Rule: the chain ships as a unit.
🎯 Key Takeaway
Audit all six numbers (four gates plus batch and request caps) against your largest payload — change them as a set or not at all.

Broker vs Topic Config: Where to Set What

Broker defaults apply to every topic, so raising them globally taxes the whole cluster for one producer's appetite. Topic-level overrides are the containment vessel: kafka-configs.sh --alter sets max.message.bytes on invoices-pdf alone, leaving the other 200 topics at 1 MB. When the PDF team later migrates to claim-check, you revert one topic instead of renegotiating cluster-wide policy.

Pair the topic override with a dedicated producer config. Big-payload producers want small batches (32 KB keeps the request envelope predictable), short linger.ms (don't accumulate a mountain), lz4 compression (halves most document payloads for near-zero CPU), and acks=all (large records deserve full durability since each one hurts more to lose). Don't share this config with your clickstream producer — their tuning fights yours.

Document the exception where everyone can find it: topic name, approved max size, owning team, expiry date for review. Size exceptions without owners become permanent, and permanent exceptions become the new default when the next team copies them. A registry entry with a 90-day review turns 'temporary 8 MB allowance' into a migration deadline instead of a tradition. Review every exception quarterly and retire the ones whose payloads migrated to claim-check — exceptions should shrink, never grow.

topic_override.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
# Isolate big payloads: topic-level override, not broker-wide
bin/kafka-configs.sh --bootstrap-server $BROKERS \
  --entity-type topics --entity-name invoices-pdf \
  --alter --add-config max.message.bytes=8388608

# Producer for that topic only
cat >> /etc/app/producer-invoices.properties <<'EOF'
max.request.size=8388608
batch.size=32768
linger.ms=20
compression.type=lz4
acks=all
EOF
📊 Production Insight
One global raise to 15 MB slowed 200 innocent topics' fetches. Rule: exceptions live on topics with owners and expiry dates, not on brokers.
🎯 Key Takeaway
Contain big payloads with topic-level overrides plus dedicated producer configs — never raise broker defaults for one team.

Why Raising Limits Is Usually the Wrong Fix

Raising limits feels like a fix and behaves like a loan. A 12 MB record at replication factor 3 pushes ~36 MB across brokers before any consumer reads it. Fetches for that partition stall behind the giant, page cache fills with bytes read once, and consumers need heap headroom for the largest record plus its deserialized form — a 12 MB JSON blob easily becomes 60 MB of objects. Multiply by partition count and the cluster carries your payload everywhere.

Growth finishes the argument. Payloads that hit limits once almost always grow: 2 MB exports become 12 MB PDFs become 40 MB bundles, because nothing upstream constrains them. Each raise buys weeks while the tax compounds permanently. Teams that raise quarterly end up running a blob store with commit-log prices — slow brokers, giant disks, fragile consumers.

Apply the 1 MB rule of thumb: payloads under ~1 MB with stable schemas are fine with tuned knobs. Anything bigger or still growing gets chunking (ordered, bounded pieces) or claim-check (blob outside, reference inside). The afternoon you spend on plumbing pays back the first time upstream doubles payload size and your pipeline doesn't notice. Show the growth chart at review: payloads that doubled twice will double again, and each raise mortgages every topic's latency.

⚠ Big Records Tax the Whole Cluster, Not Just Your Topic
Every doubling of record size doubles replication traffic, fetch memory, and consumer heap pressure for that topic — paid on every record, forever. A 12 MB record costs roughly 36 MB of cross-broker traffic at RF=3 before any consumer reads it.
📊 Production Insight
Payloads grew 2 MB to 12 MB in 6 weeks; each raise bought weeks while P99 for all topics degraded. Rule: growing payloads get design fixes, stable ones may get knobs.
🎯 Key Takeaway
Limits compound as permanent cluster tax while payloads keep growing — chunk or claim-check anything big or still growing.

Chunking Large Payloads Without Breaking Gates

Chunking splits one big payload into ordered pieces that each fit the gates, with metadata for reassembly. Key every chunk identically so they land on one partition in order; attach headers (blob id, sequence, total count, checksum) so consumers can detect gaps. Keep chunks comfortably under the smallest gate — 900 KB against 1 MB caps leaves headroom for headers and envelope overhead.

Consumers reassemble with a small state machine: buffer chunks per blob id, and when sequence count reaches total, concatenate, verify the SHA-256, and process. Add a timeout for incomplete blobs (a missing chunk shouldn't pin memory forever) and a max-buffer cap per blob so a corrupt manifest can't OOM the consumer. Idempotent replays stay safe because reassembly is deterministic — same chunks, same bytes, same checksum.

Chunking fits when you must keep bytes inside Kafka: strict ordering, log-compaction semantics, or no external store available. It's honest plumbing with real edge cases (partial blobs, version skew on header format), so prefer claim-check when an object store exists. But against 12 MB PDFs on a 1 MB topic, 14 chunks of 900 KB beats any knob raise — bounded, reviewable, and invisible to other topics.

chunked_producer.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import hashlib
from kafka import KafkaProducer

producer = KafkaProducer(bootstrap_servers="kafka:9092", max_request_size=1048576)
CHUNK = 900 * 1024  # stay under the 1 MB gates with headroom

def publish_blob(topic: str, blob_id: str, data: bytes) -> None:
    digest = hashlib.sha256(data).hexdigest()
    parts = [data[i:i + CHUNK] for i in range(0, len(data), CHUNK)]
    for seq, part in enumerate(parts):
        producer.send(topic, key=blob_id.encode(), value=part,
                      headers=[("blob", blob_id.encode()),
                               ("seq", str(seq).encode()),
                               ("total", str(len(parts)).encode()),
                               ("sha256", digest.encode())])
    producer.flush()  # all chunks appended in order on one partition
📊 Production Insight
14 ordered chunks carried 12 MB PDFs through untouched 1 MB gates. Rule: chunk size stays 10% under the smallest gate for envelope headroom.
🎯 Key Takeaway
Same key, ordered 900 KB chunks with seq/total/checksum headers — reassemble, verify, and time out incomplete blobs.

Storing References Instead of Blobs: Claim-Check

Claim-check stores the bulky bytes outside Kafka and sends a small reference event through the log. The producer uploads the PDF to object storage, then publishes an event with bucket, key, byte size, and SHA-256 checksum — typically under 1 KB. Consumers download the blob, verify the checksum, and process. Ordering, partitioning, and replay all keep working because the log still carries one event per PDF in sequence.

This pattern wins on every axis that matters. Broker traffic drops 1000x per message, replication stays cheap, consumers need no special heap, and retention policies apply to tiny events while lifecycle rules expire the blobs independently. Checksums make corruption detectable instead of silent, and the object store's versioning gives you an audit trail the commit log was never designed to provide.

Handle the two edge cases up front: blob lifecycle (7-day expiry with a tombstone event for late consumers) and missing blobs (dead-letter with the reference intact so ops can re-upload). With those covered, claim-check scales from 12 MB PDFs to 500 MB videos without another Kafka config change — which is precisely why it's the default answer for growing payloads. Start new large-payload topics on claim-check by default so the easy path is also the scalable one.

claim_check.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import hashlib
from kafka import KafkaProducer, KafkaConsumer

producer = KafkaProducer(bootstrap_servers="kafka:9092")  # default 1 MB caps fine

def publish_pdf(order_id: str, pdf: bytes, bucket: str = "invoices") -> None:
    key = f"invoices/{order_id}.pdf"
    s3_put(bucket, key, pdf)  # object store holds the bytes
    event = {"order_id": order_id, "bucket": bucket, "key": key,
             "bytes": len(pdf), "sha256": hashlib.sha256(pdf).hexdigest()}
    producer.send("invoices-ready", key=order_id.encode(), value=json.dumps(event).encode())

# consumer: fetch reference, download, verify, process
# blob = s3_get(evt["bucket"], evt["key"])
# assert hashlib.sha256(blob).hexdigest() == evt["sha256"]
📊 Production Insight
Claim-check cut one topic from 38 GB/day to 900 MB/day and P99 from 1,900 ms to 120 ms. Rule: growing bytes live outside the log.
🎯 Key Takeaway
Bytes in object storage, sub-KB reference events in Kafka with checksums — scales to any size with zero config churn.
● Production incidentPOST-MORTEMseverity: high

The 12 MB PDF That Moved Through Three Knobs and Killed 24 Pods

Symptom
At 3:40 PM the invoice producer threw RecordTooLargeException on every send; 1,900 invoices queued unsent. After the broker knob was raised, under-replicated partitions hit 34 and P99 produce latency across all topics spiked to 1,900 ms. After the replica knob was raised, consumer lag froze at 412,000 with FetchResponse size errors, and Kubernetes OOMKilled 24 consumer pods over 6 hours.
Assumption
The team assumed size limits were a single broker knob, so they raised max.message.bytes from 1 MB to 15 MB and closed the ticket. Nobody checked replica.fetch.max.bytes (still 1 MB) or the consumer's fetch.max.bytes (still 1 MB). The ticket was reopened twice in 48 hours, and each reopen widened a different knob without asking why 12 MB PDFs flowed through a commit log.
Root cause
An invoice-export job began attaching full 12 MB PDFs to events on a topic capped at ~1 MB. The first fix raised only broker max.message.bytes to 15 MB, so followers (replica.fetch.max.bytes=1 MB) couldn't replicate — under-replicated partitions grew to 34. The second fix raised the replica cap, and then all 24 consumer pods OOMKilled in sequence on fetch.max.bytes=1 MB, wedging the group at one offset for 3 days while brokers burned replication threads on 12 MB fetches.
Fix
They rolled all four knobs back to ~1 MB, built claim-check in one afternoon (PDF to S3, event carries bucket/key/size/SHA-256, 7-day lifecycle policy), and backfilled the 3 stuck days in 40 minutes. P99 produce latency fell from 1,900 ms to 120 ms, the consumer fleet dropped from 24 OOMKilled pods to zero, and the topic's disk growth flattened from 38 GB/day to 900 MB/day.
Key lesson
  • The four knobs are one atomic change or none at all — raising max.message.bytes alone just relocates the failure to replicas, then consumers.
  • Payloads that grow (2 MB to 12 MB in 6 weeks) will outrun every limit; claim-check ends the ratchet permanently.
  • Validate size at the producer and auto-route oversize payloads — humans shouldn't hand-tune broker configs per PDF.
Production debug guideFive checks — payload size, batch envelope, replica fetch, consumer fetch, growth trend — that end the loop.5 entries
Symptom · 01
Producer throws RecordTooLargeException
→
Fix
Measure the actual payload first: ls -l payload.bin plus a producer-side log of serialized byte size per record. Then compare against broker caps via bin/kafka-topics.sh --bootstrap-server $BROKERS --describe --topic $TOPIC and grep -E 'max.message.bytes|message.max.bytes' /etc/kafka/server.properties. If the payload exceeds the smallest cap, you've found the blocking link — but keep reading before raising it.
Symptom · 02
Each record is small but batches still fail
→
Fix
Check the batch envelope, not just records: grep -E 'batch.size|max.request.size|linger.ms' /etc/app/producer.properties. A batch of fifty 200 KB records is ~10 MB against default max.request.size=1048576 (1 MB). Fix by lowering batch.size, shortening linger.ms, or splitting large sends across batches — and confirm with a production-shaped load test, not single-record probes.
Symptom · 03
Produces pass but replicas fall behind
→
Fix
After any produce-side raise, watch replication: bin/kafka-topics.sh --bootstrap-server $BROKERS --describe --under-replicated-partitions and grep -E 'replica.fetch.max.bytes' /etc/kafka/server.properties. Growing under-replicated counts mean followers can't fetch the new bigger records — raise replica.fetch.max.bytes (and max.message.bytes) together as one change, never alone.
Symptom · 04
Consumer wedged at one offset after limits were raised
→
Fix
Check the consumer's fetch path: grep -E 'fetch.max.bytes|max.partition.fetch.bytes' /etc/app/consumer.properties and look for size errors via grep -iE 'FetchResponse.large|record.too large|stuck at offset' /var/log/app/consumer.log. A consumer parked at one offset with unchanging lag is the signature — raise fetch.max.bytes past the largest real record, commit past the wedge, and add a CI test consuming that record.
Symptom · 05
Payloads keep growing past every raised limit
→
Fix
Decide design vs knobs with one question: will payloads keep growing? If yes, implement claim-check now — upload the blob to object storage, send bucket/key/size/checksum in a sub-KB event, and verify with a consumer that fetches plus checksum-verifies. One afternoon of plumbing beats quarterly limit raises that slow the whole cluster.
RecordTooLarge: Diagnose at a Glance
Root CauseHow to ConfirmFixPrevention
Record exceeds max.message.bytesBroker log shows MESSAGE_TOO_LARGE on produce; describe shows topic/broker capsChunk payload or store reference; raise all four knobs only as a setProducer-side size validation with a clear reject error
Batch exceeds max.request.sizeSingle records pass but batches throw RecordTooLargeLower batch.size / linger.ms or raise max.request.size with broker capsLoad-test producers with production-shaped batches
Replica fetch cap too smallFollower can't replicate; under-replicated partitions grow after raising produce capsRaise replica.fetch.max.bytes and max.message.bytes togetherChange the four knobs as one reviewed unit
Consumer fetch cap too smallConsumer stuck at one offset, FetchResponse too large errors in logRaise fetch.max.bytes / max.partition.fetch.bytes to real maximumCI test consumes the largest production record
⚙ Quick Reference
4 commands from this guide
FileCommand / CodePurpose
size_chain_audit.shgrep -E 'max.message.bytes|message.max.bytes|replica.fetch.max.bytes' /etc/kafka...The Four-Knob Size Chain, End to End
topic_override.shbin/kafka-configs.sh --bootstrap-server $BROKERS \Broker vs Topic Config
chunked_producer.pyfrom kafka import KafkaProducerChunking Large Payloads Without Breaking Gates
claim_check.pyfrom kafka import KafkaProducer, KafkaConsumerStoring References Instead of Blobs

Key takeaways

1
Four knobs form one chain
the smallest setting wins, so check all four before changing any.
2
max.request.size caps the whole batch, so legal-size records can still fail as a group.
3
Raising limits taxes every topic; chunking or claim-check fixes the payload instead.
4
Topic-level overrides beat broker defaults
isolate big payloads to their own topics.
5
Test consumers against the largest real record, or one big message wedges the fetch loop forever.
6
Validate size at the producer and route oversize payloads to the reference path automatically.

Common mistakes to avoid

5 patterns
×

Raising max.message.bytes alone and calling it fixed

Symptom
Produces succeed but replicas can't fetch (replica.fetch.max.bytes) or consumers choke (fetch.max.bytes) — the error just moves downstream.
Fix
Raise all four knobs together as one tested change, or better, chunk the payload. A chain is only as wide as its narrowest setting.
×

Forgetting max.request.size on the producer

Symptom
Every single record passes size checks, yet the producer throws RecordTooLarge — the batch envelope, not the record, is over the limit.
Fix
Keep batch.size under max.request.size and test with production-shaped records. A batch of small records can exceed the request cap even when each record is tiny.
×

Letting any producer send unbounded payloads

Symptom
One team's 40 MB export crashes ingestion for everyone. The topic's size config becomes whoever's biggest payload, ratcheted up forever.
Fix
Cap record size at the producer with validation and route oversize payloads to the claim-check path automatically. Reject with a clear error, not a silent truncation.
×

Raising limits to 50 MB instead of fixing the payload

Symptom
Brokers slow down for all topics (big records hog fetch and replication threads), consumers OOM, and the next payload is 60 MB anyway.
Fix
Split into chunks with sequence metadata or store the blob externally and send a reference. Reserve big-payload topics for the rare cases that truly need them.
×

Fixing produce but never testing the consumer fetch path

Symptom
The pipeline produces fine for weeks, then one big record wedges the consumer — it fetches, fails, retries the same offset forever.
Fix
Set fetch.max.bytes and max.partition.fetch.bytes to match the topic's real maximum, and test consumers against the largest production record in CI.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What does RecordTooLargeException mean?
Q02SENIOR
Name the four knobs in the size chain.
Q03SENIOR
How do you debug a RecordTooLarge error step by step?
Q04SENIOR
Why does raising limits hurt the cluster?
Q05SENIOR
Design claim-check for 50 MB PDFs on a 1 MB-capped topic.
Q01 of 05JUNIOR

What does RecordTooLargeException mean?

ANSWER
It means a single record (or batch) exceeded a size limit somewhere in the produce path. The fix isn't just raising one knob — four settings form a chain and the smallest one wins, so you check each link.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Is max.message.bytes the only producer limit?
02
What's Kafka's default max record size?
03
Can I set different limits per topic?
04
Why is claim-check better than big records?
05
How do I chunk a 20 MB payload safely?
06
What happens when a consumer meets an oversize record?
N
Naren Founder & Principal Engineer

20+ years shipping production backend systems. Notes here come from systems that actually shipped.

Follow
✓ Verified
production tested
September 27, 2026
last updated
2,085
articles · all by Naren
🔥

That's Kafka. Mark it forged?

5 min read · try the examples if you haven't

←
Previous
Kafka Leader Not Available After Broker Restart
3 / 4 · Kafka
Next
Kafka Exactly-Once Semantics Broken by Producer Retries
→