Your API Contract Is a Promise. Stop Breaking It.

by Arif Ikhsanudin, Backend Developer

The Breaking Change Nobody Announced

Your team renamed a JSON field from userId to user_id to match your new naming convention. The change was "internal," and anyway the consumer team would update their code — they were CC'd in the PR. Except they weren't actively monitoring that PR. Their service went to production two days later, calling your API, and started failing silently because userId now returned null. The bug took four hours to trace.

This is a breaking change. Not a controversial one, not an edge case — by any definition of the term. userId existed in your contract and now doesn't. Every caller depending on it is broken.

The fact that it was unintentional doesn't change the consequence.

What Constitutes a Breaking Change

Breaking changes are more numerous than most teams recognize. They are not limited to removing fields or changing endpoints. A breaking change is any change that causes a valid request that previously worked to either fail or produce a different result.

Backward-incompatible changes include:

  • Removing a field from a response
  • Renaming a field (even to a "better" name)
  • Changing a field's type (string to integer, nullable to required)
  • Adding a required field to a request body
  • Changing the semantic meaning of an existing field
  • Removing an enum value that callers may be matching against
  • Tightening validation (rejecting inputs that were previously accepted)
  • Changing error codes that callers may be handling

Backward-compatible changes (safe to make without a version bump):

  • Adding an optional field to a response
  • Adding an optional field to a request body
  • Adding a new endpoint
  • Adding a new enum value (callers must handle unknown values gracefully)
  • Relaxing validation (accepting inputs previously rejected)

Versioning Strategies

There are three common approaches, each with real tradeoffs:

URI versioning (/v1/orders, /v2/orders): Explicit, visible in logs, easy to route. Leads to proliferation of versioned endpoints that need to be maintained in parallel. The most common approach for public APIs.

Header versioning (Accept: application/vnd.myapi.v2+json or a custom API-Version: 2 header): Keeps URIs clean. Harder to test manually (must set headers on every request), less visible in logs. Common in REST-purist environments.

Query parameter versioning (/orders?version=2): Simple to implement. Generally considered a weaker signal of intent — query parameters are supposed to filter, not route.

Pick one and be consistent. Mixing strategies in the same API is worse than any one strategy imperfectly applied.

The Deprecation Protocol That Actually Works

A contract isn't just about new versions — it's about how long old versions live. A deprecation protocol removes ambiguity:

  1. Announce deprecation with a concrete end-of-life date — not "sometime next quarter"
  2. Add a Deprecation response header and a Sunset header (RFC 8594) on every response from the deprecated endpoint: Sunset: Sat, 31 Dec 2026 23:59:59 GMT
  3. Monitor call volume to the deprecated endpoint. If it's still at 10% of its peak at the end-of-life date, the date may need to move
  4. Give consumers a migration guide, not just a changelog

The Sunset header is machine-readable. Clients can programmatically detect that an endpoint is being retired and alert their teams. This is better than email threads.

Consumer-Driven Contract Testing

The most robust technical approach to preventing breaking changes is consumer-driven contract testing with Pact. Here's how it works:

  • Each consumer service defines the requests it makes and the response fields it depends on (not the full response, just the parts it uses)
  • These "pacts" are published to a Pact Broker (a service that stores and manages contracts)
  • The provider's CI pipeline verifies that the current provider code satisfies all published pacts before merging or deploying
Consumer A pact: GET /orders/{id} must return { id, status, totalAmount }
Consumer B pact: GET /orders/{id} must return { id, customerId }
Provider CI: run pact verification -> confirm both contracts are satisfied

If you rename totalAmount to total, Consumer A's pact fails in the provider's CI pipeline before the change reaches production. You catch the breaking change before it breaks anything.

This is the correct answer to "how do we prevent accidental breaking changes in a microservices environment."

The Practical Takeaway

Before your next API change, run through the breaking change checklist above. If any item applies, you have two options: create a new API version or find a backward-compatible way to make the change. "We'll notify them" is not a third option — it's a process that will fail at least once and cause a production incident. The tooling exists to do this correctly. Use it.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

How to Model Relationships in SQL Without Regretting It Later

One-to-many and many-to-many relationships have well-established SQL patterns — the mistakes come from choosing the wrong pattern, modeling implicit relationships without foreign keys, or reaching for polymorphic associations when a concrete schema would serve better.

Read more

Why the Best Technical Contractor Is Not Always the One Who Gets Hired

Hiring decisions rarely come down to who is the most technically skilled. They come down to who the client feels most confident about — and those are very different things.

Read more

HTTP Response Caching in Spring Boot — Cache-Control Headers, ETags, and CDN Integration

Application-layer caching with @Cacheable keeps data out of the database. HTTP caching with Cache-Control and ETags keeps responses out of the application entirely. The two layers serve different purposes and work best together.

Read more

Monitoring Is Not Optional. It Is How You Know Your App Is Alive.

A service without meaningful monitoring is a service you're flying blind on. You don't know if it's working, degrading, or failing — until a user tells you. That is not an acceptable operational posture.

Read more