Distributed Systems & Enterprise ResiliencePlaybook3 min readUpdated September 2026

Catching a Breaking API Change Before It Ships, Not After

A service can pass every one of its own tests and still break three consumers the moment it deploys, because none of those tests actually checked what the consumers depend on. Contract testing closes that specific gap: verifying a change against what's actually relied on, not just what the producer thinks it's promising.

This is how to set it up without turning every deploy into a slow, brittle, cross-team coordination exercise.

Consumer-driven contracts flip who defines the test

In a traditional integration test, the producer decides what to verify, which means it tests what the producer assumes matters, not necessarily what consumers actually use. A consumer-driven contract has each consuming service publish exactly what it depends on, specific fields, specific response shapes, and the producer's CI checks changes against that published expectation.

This catches a class of break that producer-only testing structurally misses: a field the producer considered unimportant enough to remove, that one consumer was quietly relying on the whole time.

Run contract checks in CI, before the deploy, not after

A contract test that runs after deployment is really just fast incident detection, which is better than nothing but still means the break already happened. Running the check in CI, against the producer's proposed change before it merges, catches the break at the cheapest possible point, before it ever reaches a shared environment.

This requires the consumer's contract to be accessible to the producer's pipeline, typically through a shared contract broker or repository, so a producer's CI run can pull in what every known consumer expects without manual coordination.

Keep contracts narrow so they don't become brittle

A contract that asserts the entire response shape, every field, every value, breaks on any unrelated change and quickly becomes something engineers route around rather than trust. A contract that asserts only what the consumer actually uses, the three fields it reads, not the ones it ignores, stays stable through unrelated changes and only fails when something the consumer genuinely depends on actually changes.

This discipline is what keeps contract testing sustainable at scale. A team that over-specifies its contracts ends up with tests that fail constantly for reasons unrelated to real breaks, which trains everyone to ignore the failures entirely.

A worked example: a field removal caught before it shipped

Say an inventory service plans to remove a deprecated `legacy_sku` field from its product response, confirmed unused by every consumer the team could think of. Running the change against published contracts in CI surfaces one consumer, a reporting job nobody thought to ask, that still reads that exact field for a legacy report.

The change is held, the reporting job is updated to read the replacement field, and the field removal ships cleanly a week later with zero surprised consumers. Without contract testing, this same change would likely have shipped and broken the reporting job silently, discovered only when someone noticed a report was missing data.

Where contract testing setups fall short

  • Contracts written once at setup and never updated as a consumer's actual usage changes
  • Overly broad contracts that assert fields the consumer doesn't actually use, causing brittle false failures
  • No shared broker, so producers have no reliable way to discover what consumers currently expect
  • Contract checks that exist for some service pairs and were never extended to newer ones

Start with your highest-risk service pair, not everywhere at once

Rolling out contract testing across every service pair simultaneously is a slow, resource-heavy project that often stalls before it's useful anywhere. Picking the one producer-consumer relationship that's broken the most in the past, and proving the pattern works there first, builds the case and the internal expertise to extend it elsewhere.

A working contract test on your riskiest integration is worth more than a half-finished rollout attempt across twenty service pairs that never quite gets past the initial setup.

Treat a contract test failure as useful signal, not noise to silence

The first instinct when a contract test blocks a merge is often to see it as friction getting in the way of shipping, especially under deadline pressure. Every time that instinct wins and the check gets skipped or the contract loosened just to unblock a release, the whole practice loses credibility for the next failure too.

Treating a failure as real information, either the change genuinely breaks a consumer and needs rework, or the contract is stale and needs updating, keeps the practice trustworthy. The fastest way to kill a contract testing program is letting it become something people route around instead of listen to.

Executive Capability Standard

What Good Looks Like

Effective contract testing has each consumer publish exactly what it depends on, runs those checks in CI before a producer's change merges, and keeps contracts narrow enough to stay stable through unrelated changes.

Building The Capability (5-Stage Skill Ladder)

1. Learn:Identify your riskiest producer-consumer relationship, the one that's broken a consumer the most times in the past year.
2. Do Manually:Write a contract by hand for that one relationship, covering only the specific fields the consumer actually uses.
3. Delegate:Give the consuming team ownership of keeping its own contract current as its actual usage of the producer's API changes.
4. Automate:Wire contract checks into the producer's CI pipeline so a breaking change fails the build before it ever merges.
5. Buy:Bring in a platform engineering specialist or a contract testing tool once you're extending the pattern across many service pairs at once.

How to Get Started

Frequently Asked Questions

Does contract testing replace integration testing entirely?

No, they check different things. Contract testing verifies a producer's change doesn't break a known consumer's specific expectations; integration testing verifies the whole flow works end to end. Most mature setups use both, with contract tests catching the faster, cheaper signal earlier in the pipeline.

How do we keep contract tests from slowing down every deploy?

Keep contracts narrow, asserting only what each consumer actually uses, and run them in parallel with other CI checks rather than sequentially. A well-scoped contract test suite adds seconds to a pipeline, not minutes, and catches real breaks worth that small cost.

What if a consumer's contract is out of date and no longer reflects what it actually uses?

This is the main maintenance cost of the pattern: contracts need an owner who updates them as usage changes, ideally the consuming team itself, as part of any change to what fields it reads. A stale contract either causes false failures or, worse, stops catching real breaks.

Who should write the initial contract for an existing, long-running integration?

The consuming team, working from what it actually reads in the producer's current response, not from documentation that may already be out of date. Writing contracts from real observed usage, rather than from a spec, is what keeps them accurate from day one.

About the numbers

This guide doesn't quote a sourced benchmark. Figures in it are estimates or general guidance, so check them against your own numbers.

Related Guides