Developer Toolkit IconDeveloper Toolkit
All Articles

How to Catch Breaking API Changes Before Your Users Do. A JSON Diffing Strategy for Backend Teams

A practical workflow article for teams maintaining versioned APIs, showing how structured JSON diffing integrates into code review and CI pipelines to detect breaking changes automatically.

~6 min read
How to Catch Breaking API Changes Before Your Users Do. A JSON Diffing Strategy for Backend Teams

A backend engineer renamed a response field. The old name was userId. The new name was user_id, consistent with the rest of the API. The pull request passed review. The tests passed. The deploy was clean.

Three partner companies had built integrations against the old field name six months earlier. After the deploy, none of them received an error. Their code read userId, got undefined, and continued executing. Dashboards showed zero users across all three integrations. No alerts fired. The first signal was a support escalation two weeks later.

The rename was a breaking change. Nothing in the development process surfaced it. No JSON diff was run comparing the before and after response. No snapshot existed to compare against. The change looked like any other field rename in a serializer.

What Makes a Change Breaking

API changes fall into three categories, and the boundaries matter.

Safe additions. A new optional field in a response, a new optional parameter in a request, a new endpoint. Well-written clients ignore what they don't know about. Adding fields without removing or renaming existing ones doesn't break anything.

Dangerous but technically compatible changes. Changing a numeric field from integer to float. Changing a string format without changing the field name. Adding a new value to an enum that clients might handle with a switch statement and a default case. None of these are breaking by strict definition, but typed clients fail on the type change, and clients that enumerate expected enum values will mishandle the new one.

Breaking changes. Renaming a field. Removing a field. Making an optional parameter required. Changing the structural shape of a nested object. Any change that a client can't adapt to by simply ignoring something unfamiliar.

The problem is that all three categories look similar in a code diff. A field rename in a serializer is a two-line change. The downstream impact is invisible unless you compare the actual JSON output, not the code that produces it.

Snapshot Testing for API Responses

The most reliable automated catch for breaking changes is response snapshot testing. The setup is straightforward.

In your integration test suite, make a set of representative API requests against a known fixture state. Record the full response bodies as JSON files. Commit those fixtures to the repository alongside the tests.

When the tests run, replay those requests against the current codebase and compare the response body to the stored fixture. If the structure differs, the test fails.

A new field in the response appears as an addition in the diff. You can configure that to auto-update or require explicit review, depending on how strictly you want to gate additions. A renamed field shows as a deletion and an addition. A removed field shows as a deletion. Both require a developer to look at the diff and decide whether it's intentional and safe before updating the snapshot.

The critical property of this approach is that the snapshot update is a deliberate, reviewable commit. The diff between old and new snapshot tells exactly what changed in the API contract, visible in a pull request before anything ships.

json
// Before (stored snapshot)
{
  "userId": 42,
  "email": "[email protected]"
}

// After (current response — test fails)
{
  "user_id": 42,
  "email": "[email protected]"
}

The test failure surfaces the rename before it reaches any consumer. The developer either reverts, versions, or explicitly marks the snapshot update as a breaking change with a migration plan.

Reading Diffs During Code Review

Snapshot testing catches changes automatically. It doesn't help with the code review that happens before tests are run, or for teams that don't yet have snapshot coverage.

For any pull request that modifies API serialization, the fastest manual check is to compare an actual response from before and after the change. Not the serializer code. The actual JSON response body. The code tells you what changed structurally, but the response body tells you what the consumer receives.

The JSON Diff tool takes two JSON objects and highlights every difference: additions, deletions, and modified values. Paste the response captured before the PR into one panel and the response after into the other. The diff highlights exactly what changed without requiring you to mentally reconstruct the response from the serializer code.

The field rename shows as a deletion of userId and an addition of user_id. That's the trigger for the right question: do we have consumers reading this field by name? If yes, this needs a versioning or deprecation plan before it merges. If no, it can proceed with a note in the changelog.

That review takes 30 seconds. The support escalation in the opening story took two weeks to resolve and required three partner deployments.

Versioning Strategies

Snapshot testing and JSON diffing catch breaking changes. What to do with them is a process decision.

URL versioning deploys a new API version at a new path alongside the old: /api/v1/ and /api/v2/. The old version stays live for a deprecation window. Consumers migrate on their own timeline. This is explicit, easy to reason about, and the safest approach for external consumers. The cost is maintaining two versions simultaneously.

Additive-only schema. Treat the response schema as append-only. New fields can be added. Fields can't be renamed or removed. If a field needs to change, a new field is added with the new name, the old field is deprecated with a sunset date, and the old field is removed after consumers have migrated. This avoids versioning overhead but accumulates dead fields in the schema over time.

Header-based versioning. Clients specify the response version via an Accept or custom header. The server returns the appropriate shape. More flexible, harder to cache at the edge, and easy to get complicated when routing logic grows.

For internal APIs where you control every consumer, a breaking change with a migration window and direct communication is often the right call. For public APIs with consumers you can't reach, additive-only with explicit versioning for breaking changes is the safer default.

The Pre-Merge Checklist

Before merging any pull request that touches API serialization, response structure, or field names:

Run the before and after response through JSON Diff. Look for any deletion. Look for type changes on existing fields. Look for structural changes where a top-level field becomes nested or vice versa.

Check who reads this response. Internal services you can update together. Partner integrations you need to notify with a migration timeline. Public consumers you can't contact directly.

If the change is breaking and you don't have a versioning plan, that decision belongs before the merge. The support escalation in the story at the top of this article required three partner emergency deploys and two weeks of back-and-forth. A 30-second diff check at review time changes the conversation.

Free Developer Tools

Put the knowledge to work.

40 browser-based tools. No account. No data sent to a server.