Your Service Passes All Tests But Breaks Production: Detecting Inter-Service API Regression

TL;DR: The most dangerous bugs in microservices are not inside a service. They are between services. A code change can make a service pass all its local tests while silently altering what it sends to downstream APIs: different payload, missing header, changed error format. These regressions are invisible to unit tests, hard to catch with contract tests, and expensive in production. BitDive detects them by capturing real HTTP exchanges in execution traces and comparing them before and after a code change.
The Bug That All Tests Miss
Here is a scenario every microservices team has experienced at least once:
- A developer updates a shared library or changes a DTO mapping.
- All unit tests pass. The integration test suite is green.
- The service deploys to production.
- Hours later: a downstream service starts throwing deserialization errors, or silently processing wrong data.
The root cause: the service changed how it interacts with other services at the HTTP level. The actual bytes it sends over the wire are different. But no test was checking that.
Common triggers:
- Jackson or ObjectMapper update changed date/enum/null serialization
- DTO field renamed or removed
Include.NON_NULLtoggled on or offWRITE_DATES_AS_TIMESTAMPSenabled- MapStruct/ModelMapper update swapped field mapping
- Spring Boot version bump changed default serialization
- Feign interceptor refactoring dropped an auth header
None of these touch business logic. All of them change the runtime API contract between services.
Why Existing Tests Are Blind Here
Each testing approach has a structural blind spot when it comes to inter-service API compatibility:
Unit tests mock the HTTP client entirely. They verify local logic but never see the actual serialized request.
Contract tests (Pact) verify predefined examples. If an example does not cover the specific field, serialization format, or header, the regression slips through. A Pact contract that checks status exists does not catch "ACTIVE" becoming 0.
OpenAPI validation checks schema compliance but not runtime serialization behavior. The schema says string, but Jackson now serializes the LocalDate as a Unix timestamp.
End-to-end tests can catch the problem, but they are slow, flaky, and cover only a narrow set of scenarios.
The gap: no standard testing approach compares the actual serialized HTTP exchange before and after a code change.
What BitDive Sees That Other Tests Cannot
BitDive captures execution traces from running Java applications. Each trace includes every outgoing HTTP call with full details:
| Layer | What BitDive captures |
|---|---|
| Endpoint | HTTP method, URL, path variables, query parameters |
| Request headers | Authorization, Content-Type, X-Correlation-Id, tenant/feature headers |
| Request body | The actual serialized JSON as sent over the wire |
| Response status | HTTP status code |
| Response body | The actual response payload as received |
| Error details | Exception class, message, stack trace |
The key distinction: this is the real runtime exchange, not the Java object before serialization. If Jackson serializes a BigDecimal as "19.99" in the baseline but as 19.99 after an ObjectMapper change, the difference is visible in the trace.
Four Regressions That Traces Catch
1. Serialization Change After Jackson Upgrade
Before the change:
{
"createdDate": "2026-02-28",
"status": "ACTIVE",
"amount": "19.99"
}
After Jackson config update:
{
"createdDate": 1740700800000,
"status": 0,
"amount": 19.99
}
Unit tests pass. Contract tests pass (they only check field presence). BitDive's before/after trace comparison flags three diffs: date format, enum serialization, number type.
2. Missing Auth Header After Interceptor Refactoring
Before: Every outgoing request includes Authorization: Bearer ... and X-Correlation-Id: abc-123.
After: The interceptor was refactored. X-Correlation-Id is still sent, but Authorization is missing on one specific call path to the payment service.
No test covers this header on this specific route. The payment service returns 401. BitDive shows the header diff immediately in the trace comparison.
3. Changed Error Contract
Before: Downstream returns 404 with:
{"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 an HttpMessageNotReadableException. BitDive detects both the status code change and the response body format change.
4. Silent Call Sequence Change
Before: The service calls GET /accounts/42, then POST /transactions with account data from the response.
After refactoring: The service calls POST /transactions directly, without fetching account data first. It sends a hardcoded default instead of the real account segment.
Contract tests for each endpoint pass individually. The business scenario is broken. BitDive detects that one call disappeared and the remaining call sends different payload data.
How BitDive Detects These Regressions
BitDive uses before/after trace comparison to catch API regressions:
- Capture a baseline trace. Trigger the business scenario via a real API call (curl, Postman, your frontend). BitDive's agent captures a trace with all outgoing HTTP exchanges.
- Make a code change (refactor, library upgrade, DTO update, Jackson config change).
- Trigger the same scenario again. Call the same endpoint on the updated service. BitDive captures a new trace.
- Compare the two traces across every layer: endpoint diff, header diff, request body diff, response diff, status diff, call sequence diff.
Because traces are captured from real API calls, the comparison operates on actual serialized HTTP exchanges, not on mocked data or theoretical schemas. If the code now sends a different payload, drops a header, or changes the call sequence, the diff shows exactly what changed.
Not Every Diff Is a Bug
A useful system must distinguish noise from breakage. BitDive classifies differences:
Expected (not a regression):
- New optional field in the response
- New trace or correlation header
- Different timestamps, UUIDs, request IDs (automatically excluded)
Critical (likely a regression):
- Required field disappeared from request body
- Field type changed (string to number, date format changed)
- Auth header missing
- Error status code changed (404 became 500)
- Call removed or new unexpected call appeared
- Response body structure changed
Fields like requestId, timestamp, traceId, and UUID are automatically masked. Custom masking and comparison policies can be configured in the Configuration Guide.
Where This Matters Most
Inter-service API regression control delivers the most value in systems with:
- Many internal REST APIs between microservices
- Frequent library and framework upgrades
- Strong reliance on DTOs and internal contracts
- Active refactoring of integration layers
- AI-assisted development (where agents may change serialization behavior without understanding downstream impact)
In these environments, the most expensive production bugs are not "the code does not compile." They are "the services no longer interact the same way."
Detect API Regressions from Real Traces
BitDive captures real HTTP exchanges between services and compares them before and after code changes. Serialization drift, missing headers, altered error responses: caught automatically in your existing JUnit tests.
Try BitDive FreeFAQ
How is this different from Pact contract testing?
Pact verifies that a service conforms to predefined contract examples. BitDive verifies that actual runtime API behavior remained the same after a code change. They complement each other. Pact catches explicit contract violations. BitDive catches implicit changes that fall outside the contract scope: serialization drift, header changes, error body mutations. See the full comparison.
Do I need to write separate tests for API regression?
No. BitDive compares traces captured from real API calls. Trigger the same business scenario before and after a code change, and BitDive diffs the outgoing HTTP exchanges automatically. No test code needed for this verification.
What about performance overhead?
BitDive captures traces using a standard Java Agent with 0.5-5% overhead. Trace comparison happens at test time, not at runtime, so there is no production performance impact beyond the capture phase.
Deep Dive Inter-Service API Verification Guide
Comparison BitDive vs. Contract Testing (Pact)
Related Reading
- Spring Boot Integration Testing: Full Context, Stubbed Boundaries -- Full-chain testing with deterministic replay
- Integration Tests with BitDive -- Replay mode for zero-infrastructure testing
- BitDive vs. Speedscale -- Method-level traces vs. network-level replay
- Glossary: Runtime API Contract -- Definition and related terms
