golang/go · error

zlib.ErrChecksum

zlib.ErrChecksum

Error message

zlib: invalid checksum

What it means

Returned by zlib.Reader.Read when the Adler-32 checksum stored in the zlib trailer does not match the Adler-32 of the bytes the deflate decompressor actually produced. Computed via hash/adler32 incrementally, compared once EOF is reached.

Source

Thrown at src/compress/zlib/reader.go:43

import (
	"bufio"
	"compress/flate"
	"encoding/binary"
	"errors"
	"hash"
	"hash/adler32"
	"io"
)

const (
	zlibDeflate   = 8
	zlibMaxWindow = 7
)

var (
	// ErrChecksum is returned when reading ZLIB data that has an invalid checksum.
	ErrChecksum = errors.New("zlib: invalid checksum")
	// ErrDictionary is returned when reading ZLIB data that has an invalid dictionary.
	ErrDictionary = errors.New("zlib: invalid dictionary")
	// ErrHeader is returned when reading ZLIB data that has an invalid header.
	ErrHeader = errors.New("zlib: invalid header")
)

type reader struct {
	r            flate.Reader
	decompressor io.ReadCloser
	digest       hash.Hash32
	err          error
	scratch      [4]byte
}

// Resetter resets a ReadCloser returned by [NewReader] or [NewReaderDict]
// to switch to a new underlying Reader. This permits reusing a ReadCloser
// instead of allocating a new one.
type Resetter interface {

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Re-fetch the source payload from the origin; truncation is the most common cause.
  2. If you control the producer, ensure deflater.Close() is called so the Adler-32 trailer is written.
  3. Verify with Python's zlib or `openssl zlib` (where available) to confirm the corruption is in the data, not in your reader wiring.
  4. Wrap the transport in TLS so corruption surfaces as a MAC failure with clearer diagnostics.

Example fix

// before
if _, err := io.Copy(out, zr); err != nil { return err } // opaque

// after: distinguish checksum failures
if _, err := io.Copy(out, zr); err != nil {
    if errors.Is(err, zlib.ErrChecksum) {
        return fmt.Errorf("payload corrupt (adler32 mismatch): %w", err)
    }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

import "hash/adler32"

// Full pre-validation requires decompressing. Partial checks:
func likelyZlib(data []byte) error {
    if len(data) < 2 { return errors.New("too short") }
    cmf, flg := data[0], data[1]
    if cmf&0x0f != 8 { return zlib.ErrHeader }
    if (uint16(cmf)<<8 | uint16(flg)) % 31 != 0 { return zlib.ErrHeader }
    return nil
}
// Note: Adler-32 can only be fully validated after decompression.

Type guard

func isZlibChecksumErr(err error) bool {
    return errors.Is(err, zlib.ErrChecksum)
}

Try / catch

if _, err := io.Copy(out, zr); err != nil {
    if errors.Is(err, zlib.ErrChecksum) {
        return corruptStreamError{kind: "zlib-adler32", cause: err}
    }
    return err
}

Prevention

When it happens

Trigger: Reading from a zlib.NewReader where the trailing 4 bytes (Adler-32, big-endian) disagree with the decompressed payload. Surfaced at the moment the trailer is parsed, i.e., after the last deflate byte has been consumed.

Common situations: Truncated HTTP body framed with Content-Encoding: zlib, decompression of a payload that passed through a buggy proxy that re-chunked but did not recompute Adler-32, network byte corruption, or a producer that forgot to flush Close so the trailer is missing.

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/a826152b19a8d3d9. Report an issue: GitHub.