Azure / Azure/azure-sdk-for-java

[Cosmos] Fix ClassCastException when building CosmosException from an empty error response body

Open
#49,322 1 comment 0 reactions 1 assignee Claimed by @kushagraThapar View on GitHub
bug Client Cosmos customer-reported needs-team-attention Service Attention
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.