Stackbook Logo
distribution-communicationestablished · low operational burden

Tolerant Reader

Also known as: robustness-principle, lenient-consumer

Intent

Let consumers read only what they need and ignore unknown data, so providers can evolve without breaking them.

Problem

Strict consumers that validate entire payloads break whenever a provider adds a field or reorders data. This forces lockstep deployments between services that should evolve independently.

Forces

  • •Providers add fields over time
  • •Consumers deploy on different schedules
  • •Strictness catches errors but blocks evolution
  • •Leniency can hide real contract violations

Solution

Overview

The Tolerant Reader pattern applies Postel's law to integration: be conservative in what you send and liberal in what you accept. A consumer reads the minimum it needs and ignores everything else, which makes additive changes safe.

How it works

Instead of deserialising a response into a class mirroring the entire provider schema, the consumer picks fields.

// strict: fails on any unknown property
Order o = mapper.readValue(json, Order.class);

// tolerant: select what is needed
id     = json.path("id").asText();
amount = json.path("total").path("amount").asLong();
status = parseStatus(json.path("status").asText("UNKNOWN"));

With JSON binding libraries, disable fail-on-unknown-properties. With Protobuf and Avro, rely on built-in unknown-field handling and schema resolution. For enums, map unrecognised values to UNKNOWN and choose a safe behaviour. Consumer-driven contracts, for example Pact, record the exact fields each consumer uses so providers see what is safe to change.

Pitfalls

  • Defaults that mask absence. Distinguish optional from required fields and fail loudly when a required one is missing.
  • Crashing on new enum values. Always include a default branch.
  • Tolerating changes in meaning. A field changing units is a breaking change no reader can absorb.
  • Applying tolerance to untrusted input. Inbound requests to your own service should still be validated strictly.
  • Skipping contract tests, leaving real dependencies unrecorded.

Key takeaways

  • Read only needed fields; ignore the rest.
  • Handle unknown enums and absent optionals safely.
  • Be strict about what you require, lenient about what you do not.
  • Back it with consumer-driven contract tests.

✓ When to Use

  • •Independently deployed services sharing JSON or Protobuf contracts
  • •Public API clients and SDKs
  • •Event consumers reading evolving schemas

✗ When Not to Use

  • •Security-sensitive input where unexpected data must be rejected
  • •Provider-side validation of incoming requests
  • •Regulated formats needing strict conformance

Pros

  • +Additive provider changes do not break consumers
  • +Reduces coordinated releases
  • +Smaller coupling surface

Cons

  • −Typos and silent drops can hide bugs
  • −Requires discipline in every consumer
  • −Unknown enum values need explicit handling

Cost Profile

Infrastructure

None - coding practice

Operational

Low - contract tests

Cognitive

Low - simple principle

Failure Modes

  • Missing required field silently becomes a default and corrupts data

  • Unknown enum value crashes a switch statement

  • Leniency masks a genuinely broken contract

Real-World Examples

Alternatives

    Related Patterns

      Competency Domains

      distribution communicationeconomics evolution