Azure / Azure/azure-sdk-for-java
[Cosmos] Fix ClassCastException when building CosmosException from an empty error response body
- Dominant language
- Java
- Stars
- 2.6k
- Forks
- 2.2k
- Avg merge
- 2d 8h
- Merged PRs (30d)
- 178
Description
**Describe the bug**
`JsonSerializable.fromJson(String, ObjectMapper)` performs an unchecked cast of the parsed tree to `ObjectNode`:
```java
return (ObjectNode) objectMapper.readTree(json);
```
When `json` is empty or blank, `ObjectMapper.readTree(...)` returns a `MissingNode` (not an `ObjectNode`), so the cast throws `ClassCastException: class ...MissingNode cannot be cast to class ...ObjectNode`.
This is reached on the error-handling path: when a request gets an error HTTP response **with an empty body**, `HttpClientUtils.createDocumentClientException` builds the exception from the body, defaulting an empty body to `StringUtils.EMPTY` and then calling `new CosmosError(body)`:
- https://github.com/Azure/azure-sdk-for-java/blob/57ee4199571b130745ee6e31d46ad5e1a62530b9/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/directconnectivity/HttpClientUtils.java#L42-L45
- `new CosmosError(String)` → `JsonSerializable(String)` → `fromJson(...)`:
- https://github.com/Azure/azure-sdk-for-java/blob/57ee4199571b130745ee6e31d46ad5e1a62530b9/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/CosmosError.java#L34
- https://github.com/Azure/azure-sdk-for-java/blob/57ee4199571b130745ee6e31d46ad5e1a62530b9/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/JsonSerializable.java#L92
- https://github.com/Azure/azure-sdk-for-java/blob/57ee4199571b130745ee6e31d46ad5e1a62530b9/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/JsonSerializable.java#L618
***Exception or Stack Trace***
Observed in production on `azure-cosmos:4.80.0` (manifests as a `CosmosException` with `statusCode 0`):
```
Caused by: java.lang.ClassCastException: class com.fasterxml.jackson.databind.node.MissingNode cannot be cast to class com.fasterxml.jackson.databind.node.ObjectNode
at com.azure.cosmos.implementation.JsonSerializable.fromJson(JsonSerializable.java:618)
at com.azure.cosmos.implementation.JsonSerializable.(JsonSerializable.java:93)
at com.azure.cosmos.implementation.CosmosError.(CosmosError.java:35)
at com.azure.cosmos.implementation.directconnectivity.HttpClientUtils.lambda$createDocumentClientException$2(HttpClientUtils.java:45)
at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:106)
... 58 more
```
The enclosing `CosmosException` is created here, carrying `statusCode 0` and an empty `responseHeaders {}`:
```
at com.azure.cosmos.BridgeInternal.createCosmosException(BridgeInternal.java:416)
at com.azure.cosmos.implementation.directconnectivity.GatewayAddressCache.lambda$getServerAddressesViaGatewayInternalAsync$11(GatewayAddressCache.java:514)
```
**To Reproduce**
The crash happens inside the SDK's own exception construction, so it can be reproduced deterministically by exercising the exact call the SDK makes for an empty error body — `new CosmosError()`:
1. Add `com.azure:azure-cosmos:4.80.0` to the classpath.
2. Construct a `CosmosError` from an empty (or whitespace-only) body, as `HttpClientUtils.createDocumentClientException` does when an error response has no body.
3. Observe `ClassCastException` instead of a usable `CosmosError`.
(In a live service this occurs when a Gateway/server error response is returned with an empty body — e.g. transient connectivity errors during address resolution.)
***Code Snippet***
Minimal reproduction (throws on 4.80.0):
```java
import com.azure.cosmos.implementation.CosmosError;
// Both throw: java.lang.ClassCastException: MissingNode cannot be cast to ObjectNode
new CosmosError("");
new CosmosError(" ");
// Control: a valid JSON object works fine
new CosmosError("{}"); // OK
```
Proposed regression test (TestNG, `unit` group), alongside the existing `JsonSerializableTest`:
```java
@Test(groups = {"unit"})
public void instantiateFromEmptyJsonStringDoesNotThrow() {
// An error HTTP response with an empty/blank body becomes a CosmosError via
// HttpClientUtils.createDocumentClientException -> new CosmosError(body).
for (String emptyBody : Arrays.asList("", " ")) {
CosmosError error = new CosmosError(emptyBody);
assertThat(error).isNotNull();
assertThat(error.getCode()).isNull();
assertThat(error.getMessage()).isNull();
}
}
```
**Expected behavior**
Constructing a `CosmosError`/`JsonSerializable` from an empty or blank body should not throw `ClassCastException`. An empty body should yield an empty error object.
**Screenshots**
N/A
**Setup (please complete the following information):**
N/A
**Additional context**
N/a
**Information Checklist**
- [x] Bug Description Added
- [x] Repro Steps Added
- [x] Setup information Added
Contributor guide
Assessment
This issue has not been assessed yet.