Skip to main content

Verifying API Compatibility Between Services

When code changes in a microservice, the most dangerous regressions happen not inside the service but in how it communicates with other services. A service that compiles, starts, and passes its unit tests can still break production by sending a different payload, dropping a header, or changing an error response format.

BitDive detects these regressions because execution traces capture the full HTTP exchange between services: not just the fact that a call happened, but exactly what was sent and what was received.


The Problem: Silent API Drift

Traditional testing is structurally blind to inter-service API drift:

Testing approachWhat it catchesWhat it misses
Unit testsLogic errors inside the serviceWhether the outgoing HTTP request is still compatible
Contract tests (Pact)Predefined contract violationsChanges not covered by examples (serialization, headers, error bodies)
Integration testsFull internal chain behaviorDownstream API compatibility after infrastructure changes
OpenAPI validationSchema violationsActual runtime serialization differences

The regressions that slip through usually have the same pattern: the service still works locally, but the actual bytes it sends over HTTP have changed.

Common causes:

  • Jackson or ObjectMapper configuration change
  • DTO field rename or removal
  • Include.NON_NULL toggled
  • WRITE_DATES_AS_TIMESTAMPS enabled
  • MapStruct or ModelMapper update
  • Spring Boot version bump changing default serialization
  • Feign or WebClient interceptor modification
  • Auth header propagation logic change

What BitDive Captures in Each HTTP Exchange

When a service makes an outgoing HTTP call during a traced execution, BitDive records:

LayerCaptured data
EndpointHTTP method, full URL, path variables, query parameters
Request headersAll headers including Authorization, Content-Type, X-Correlation-Id, tenant headers, feature flags
Request bodyThe actual serialized JSON/XML as sent over the wire
Response statusHTTP status code
Response headersAll response headers
Response bodyThe actual response payload as received
TimingDuration, timestamp
Error detailsException class, message, stack trace (if the call failed)

The key point: this is the actual runtime exchange, not the object before serialization or a theoretical schema. If Jackson serializes a LocalDate as "2026-02-28" in the baseline but as 1740700800000 after a config change, that difference is visible in the trace.


How Before/After Comparison Detects API Drift

BitDive's core mechanism for detecting API regression is the same before/after trace comparison used for all deterministic verification:

Step 1: Establish a Baseline

Trigger a business scenario via a real API call (curl, Postman, your frontend, an automated script). BitDive's agent captures a trace that includes all outgoing HTTP calls with full payload details.

This trace is the runtime API contract: it represents how the service actually interacted with its dependencies at this point in time.

Step 2: Make a Code Change

Refactor business logic, upgrade a library, update a DTO, change ObjectMapper settings, bump Spring Boot version.

Step 3: Trigger the Same Scenario Again

Call the same endpoint on the updated service. BitDive's agent captures a new trace.

Step 4: Compare

BitDive performs a diff across the two traces, comparing every layer of each outgoing HTTP call:

  • Endpoint diff: Did the URL, method, or query parameters change?
  • Header diff: Were headers added, removed, or modified?
  • Request body diff: Did the serialized JSON change? Which fields are different?
  • Response status diff: Did the downstream return a different status code?
  • Response body diff: Did the response structure or content change?
  • Sequence diff: Did the order, count, or presence of API calls change?

Examples of Regressions BitDive Detects

Serialization Change After Jackson Upgrade

Before:

{
"createdDate": "2026-02-28",
"status": "ACTIVE",
"amount": "19.99"
}

After (Jackson config changed):

{
"createdDate": 1740700800000,
"status": 0,
"amount": 19.99
}

Unit tests pass. Contract test passes (it only checked that status field exists). BitDive flags the diff: date format changed, enum serialization changed, number type changed.

Missing Header After Interceptor Refactoring

Before: Outgoing request includes X-Correlation-Id: abc-123 and Authorization: Bearer ...

After: Interceptor was refactored. X-Correlation-Id is still sent, but Authorization header is missing for one specific downstream call.

No test covers this header on this specific call path. BitDive shows the header diff immediately.

Changed Error Contract

Before: Downstream returns 404 with body:

{"code": "CUSTOMER_NOT_FOUND", "message": "Customer 42 not found"}

After: Downstream returns 500 with plain text: Internal Server Error

The upstream service's error handler expects a JSON body with a code field. It now throws a deserialization exception. BitDive detects both the status code change and the response body format change.

Altered Call Sequence

Before: The service calls GET /accounts/42 first, then POST /transactions with account data.

After refactoring: The service calls POST /transactions without calling /accounts/42 first.

Contract tests for each endpoint pass individually. BitDive detects that the call sequence changed: one call disappeared and the remaining call now sends different data.


Capturing Traces for API Verification

API verification requires two traces captured from real API calls: one before the change, one after.

The typical workflow:

  1. Run the service with BitDive agent attached.
  2. Trigger the business scenario (e.g. curl -X POST /api/orders -d '{...}').
  3. BitDive captures the baseline trace.
  4. Make the code change and restart the service.
  5. Trigger the same scenario again.
  6. BitDive captures a new trace.
  7. Compare the two traces to detect API drift.

This is different from integration testing with ReplayTestBase, which replays recorded dependencies inside JUnit tests. API verification compares traces captured from live service execution, so it operates on actual serialized HTTP exchanges.

For a real example of before/after trace comparison, see the Interactive Demo with Cursor (video), where an AI agent compares two traces layer-by-layer after a code change.


What Counts as a Breaking API Change

Not every difference in a trace is a regression. BitDive distinguishes between expected and unexpected changes:

Change typeTypically acceptableTypically breaking
New field in response✅ Optional field added❌ Required field removed
Header change✅ New trace header added❌ Auth or content-type header removed
Status code❌ Any status code change (200→404, 404→500)
Serialization format❌ Date, enum, or number format changed
Request body✅ New optional field added❌ Required field missing or renamed
Call sequence✅ Same calls, minor reordering❌ Call removed or new unexpected call
Timestamp/UUID✅ Expected to differ per execution

Fields like requestId, timestamp, traceId, and UUID are automatically treated as noise and excluded from comparison. See the Configuration Guide for custom masking and comparison policies.


CI/CD Integration

API verification fits naturally into the delivery pipeline:

  1. Baseline traces exist from captured production or staging scenarios.
  2. Developer pushes a change.
  3. mvn test runs the replay tests (which include API verification).
  4. If an outgoing HTTP exchange differs from the baseline, the test fails.
  5. CI reports which endpoint, header, or payload changed.
  6. Team decides: expected change (update baseline) or regression (fix the code).

This is especially effective during:

  • Library upgrades (Jackson, Spring Boot, MapStruct)
  • Refactoring integration layers
  • DTO and mapper changes
  • AI-assisted code modifications verified via MCP