Skip to content

Commit ef7f46e

Browse files
authored
Merge pull request #42 from AzureCosmosDB/copilot/fix-npe-reactive-streams
Add Reactor NPE warning to sdk-java-content-response rule
2 parents d162cdc + 39e838a commit ef7f46e

2 files changed

Lines changed: 86 additions & 6 deletions

File tree

skills/cosmosdb-best-practices/AGENTS.md

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3726,12 +3726,51 @@ public interface OrderRepository extends CosmosRepository<Order, String> {
37263726
Order savedOrder = orderRepository.save(newOrder); // ✅ Returns saved document
37273727
```
37283728

3729+
**⚠️ Reactor / reactive streams — never set `contentResponseOnWriteEnabled(false)` on `CosmosAsyncClient`:**
3730+
3731+
When using `CosmosAsyncClient` with Project Reactor, setting `contentResponseOnWriteEnabled(false)` causes `CosmosItemResponse.getItem()` to return `null`. Reactor does not allow `null` signals in its pipeline (Reactive Streams Specification, Rule 2.13), so any downstream `.map(CosmosItemResponse::getItem)` or similar operator throws a `NullPointerException` from inside Reactor internals — not from your code — making the root cause very hard to diagnose.
3732+
3733+
```java
3734+
// ❌ Causes NPE in reactive stream — never do this with CosmosAsyncClient
3735+
CosmosAsyncClient asyncClient = new CosmosClientBuilder()
3736+
.endpoint(endpoint)
3737+
.key(key)
3738+
.contentResponseOnWriteEnabled(false)
3739+
.buildAsyncClient();
3740+
3741+
container.upsertItem(item)
3742+
.map(CosmosItemResponse::getItem) // ❌ getItem() returns null → NPE
3743+
.block();
3744+
```
3745+
3746+
```java
3747+
// ✅ Option 1 (recommended): Keep content response enabled for async clients
3748+
CosmosAsyncClient asyncClient = new CosmosClientBuilder()
3749+
.endpoint(endpoint)
3750+
.key(key)
3751+
.contentResponseOnWriteEnabled(true)
3752+
.buildAsyncClient();
3753+
3754+
container.upsertItem(item)
3755+
.map(CosmosItemResponse::getItem) // ✅ Non-null, safe in Reactor
3756+
.block();
3757+
```
3758+
3759+
```java
3760+
// ✅ Option 2: If you must suppress content, guard against null before mapping
3761+
container.upsertItem(item)
3762+
.flatMap(response -> {
3763+
MyItem result = response.getItem();
3764+
return result != null ? Mono.just(result) : Mono.empty();
3765+
});
3766+
```
3767+
37293768
**When NOT to enable content response:**
37303769

3731-
If you don't need the created document (fire-and-forget writes), leave it disabled to save bandwidth:
3770+
If you don't need the created document (fire-and-forget writes) **and you are using the synchronous `CosmosClient`**, leave it disabled to save bandwidth:
37323771

37333772
```java
3734-
// High-throughput ingestion - don't need response content
3773+
// High-throughput ingestion with synchronous client - don't need response content
37353774
CosmosItemRequestOptions options = new CosmosItemRequestOptions();
37363775
options.setContentResponseOnWriteEnabled(false); // Default, saves bandwidth
37373776

@@ -3751,7 +3790,8 @@ Enabling content response does NOT increase RU cost - the document is already fe
37513790
- Java SDK returns null from `getItem()` by default for created/upserted items — enable `contentResponseOnWriteEnabled(true)` to get documents back after writes
37523791
- Can be set at client level (all operations) or per-request
37533792
- Spring Data Cosmos handles both unwrapping and content response automatically
3754-
- Disable content response for high-throughput scenarios where response content is not needed
3793+
- **Never set `contentResponseOnWriteEnabled(false)` with `CosmosAsyncClient` / reactive streams** — it causes `NullPointerException` in the Reactor pipeline
3794+
- Only disable content response for high-throughput fire-and-forget writes with the synchronous `CosmosClient`
37553795

37563796
Reference: [Azure Cosmos DB Java SDK best practices](https://learn.microsoft.com/azure/cosmos-db/nosql/best-practice-java)
37573797

skills/cosmosdb-best-practices/rules/sdk-java-content-response.md

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,51 @@ public interface OrderRepository extends CosmosRepository<Order, String> {
138138
Order savedOrder = orderRepository.save(newOrder); // ✅ Returns saved document
139139
```
140140

141+
**⚠️ Reactor / reactive streams — never set `contentResponseOnWriteEnabled(false)` on `CosmosAsyncClient`:**
142+
143+
When using `CosmosAsyncClient` with Project Reactor, setting `contentResponseOnWriteEnabled(false)` causes `CosmosItemResponse.getItem()` to return `null`. Reactor does not allow `null` signals in its pipeline (Reactive Streams Specification, Rule 2.13), so any downstream `.map(CosmosItemResponse::getItem)` or similar operator throws a `NullPointerException` from inside Reactor internals — not from your code — making the root cause very hard to diagnose.
144+
145+
```java
146+
// ❌ Causes NPE in reactive stream — never do this with CosmosAsyncClient
147+
CosmosAsyncClient asyncClient = new CosmosClientBuilder()
148+
.endpoint(endpoint)
149+
.key(key)
150+
.contentResponseOnWriteEnabled(false)
151+
.buildAsyncClient();
152+
153+
container.upsertItem(item)
154+
.map(CosmosItemResponse::getItem) // ❌ getItem() returns null → NPE
155+
.block();
156+
```
157+
158+
```java
159+
// ✅ Option 1 (recommended): Keep content response enabled for async clients
160+
CosmosAsyncClient asyncClient = new CosmosClientBuilder()
161+
.endpoint(endpoint)
162+
.key(key)
163+
.contentResponseOnWriteEnabled(true)
164+
.buildAsyncClient();
165+
166+
container.upsertItem(item)
167+
.map(CosmosItemResponse::getItem) // ✅ Non-null, safe in Reactor
168+
.block();
169+
```
170+
171+
```java
172+
// ✅ Option 2: If you must suppress content, guard against null before mapping
173+
container.upsertItem(item)
174+
.flatMap(response -> {
175+
MyItem result = response.getItem();
176+
return result != null ? Mono.just(result) : Mono.empty();
177+
});
178+
```
179+
141180
**When NOT to enable content response:**
142181

143-
If you don't need the created document (fire-and-forget writes), leave it disabled to save bandwidth:
182+
If you don't need the created document (fire-and-forget writes) **and you are using the synchronous `CosmosClient`**, leave it disabled to save bandwidth:
144183

145184
```java
146-
// High-throughput ingestion - don't need response content
185+
// High-throughput ingestion with synchronous client - don't need response content
147186
CosmosItemRequestOptions options = new CosmosItemRequestOptions();
148187
options.setContentResponseOnWriteEnabled(false); // Default, saves bandwidth
149188

@@ -163,6 +202,7 @@ Enabling content response does NOT increase RU cost - the document is already fe
163202
- Java SDK returns null from `getItem()` by default for created/upserted items — enable `contentResponseOnWriteEnabled(true)` to get documents back after writes
164203
- Can be set at client level (all operations) or per-request
165204
- Spring Data Cosmos handles both unwrapping and content response automatically
166-
- Disable content response for high-throughput scenarios where response content is not needed
205+
- **Never set `contentResponseOnWriteEnabled(false)` with `CosmosAsyncClient` / reactive streams** — it causes `NullPointerException` in the Reactor pipeline
206+
- Only disable content response for high-throughput fire-and-forget writes with the synchronous `CosmosClient`
167207

168208
Reference: [Azure Cosmos DB Java SDK best practices](https://learn.microsoft.com/azure/cosmos-db/nosql/best-practice-java)

0 commit comments

Comments
 (0)