From 682c2e389a9e506cc7873cb142a54f2671ba1e30 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Thu, 19 Mar 2026 13:54:00 +0000 Subject: [PATCH 1/3] Normalize single write handling of Write Concern errors JAVA-6111 --- .../com/mongodb/MongoServerException.java | 15 ++++ .../mongodb/MongoWriteConcernException.java | 18 +++++ .../MongoBulkWriteExceptionHelper.java | 71 +++++++++++++++++++ .../internal/MongoOperationPublisher.java | 37 +--------- .../client/internal/MongoCollectionImpl.java | 35 +-------- 5 files changed, 109 insertions(+), 67 deletions(-) create mode 100644 driver-core/src/main/com/mongodb/internal/operation/MongoBulkWriteExceptionHelper.java diff --git a/driver-core/src/main/com/mongodb/MongoServerException.java b/driver-core/src/main/com/mongodb/MongoServerException.java index a981dc1c923..12ba91372c4 100644 --- a/driver-core/src/main/com/mongodb/MongoServerException.java +++ b/driver-core/src/main/com/mongodb/MongoServerException.java @@ -71,6 +71,21 @@ public MongoServerException(final int code, @Nullable final String errorCodeName this.serverAddress = serverAddress; } + /** + * Construct a new instance. + * + * @param code the error code from the server + * @param message the message from the server + * @param t the throwable cause + * @param serverAddress the address of the server + * @since 5.7 + */ + public MongoServerException(final int code, final String message, final Throwable t, final ServerAddress serverAddress) { + super(code, message, t); + this.errorCodeName = null; + this.serverAddress = serverAddress; + } + /** * Gets the address of the server. * diff --git a/driver-core/src/main/com/mongodb/MongoWriteConcernException.java b/driver-core/src/main/com/mongodb/MongoWriteConcernException.java index 77aca03e02a..f4e1b81f94c 100644 --- a/driver-core/src/main/com/mongodb/MongoWriteConcernException.java +++ b/driver-core/src/main/com/mongodb/MongoWriteConcernException.java @@ -82,6 +82,24 @@ public MongoWriteConcernException(final WriteConcernError writeConcernError, @Nu addLabels(errorLabels); } + /** + * Construct an instance. + * + * @param writeConcernError the non-null write concern error + * @param writeConcernResult the write result + * @param serverAddress the non-null server address + * @param errorLabels the server errorLabels + * @param t the throwable cause + * @since 5.7 + */ + public MongoWriteConcernException(final WriteConcernError writeConcernError, @Nullable final WriteConcernResult writeConcernResult, + final ServerAddress serverAddress, final Collection errorLabels, final Throwable t) { + super(writeConcernError.getCode(), writeConcernError.getMessage(), t, serverAddress); + this.writeConcernResult = writeConcernResult; + this.writeConcernError = notNull("writeConcernError", writeConcernError); + addLabels(errorLabels); + } + /** * Gets the write concern error. diff --git a/driver-core/src/main/com/mongodb/internal/operation/MongoBulkWriteExceptionHelper.java b/driver-core/src/main/com/mongodb/internal/operation/MongoBulkWriteExceptionHelper.java new file mode 100644 index 00000000000..02fe054c0ef --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/operation/MongoBulkWriteExceptionHelper.java @@ -0,0 +1,71 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mongodb.internal.operation; + +import com.mongodb.MongoBulkWriteException; +import com.mongodb.MongoException; +import com.mongodb.MongoInternalException; +import com.mongodb.MongoWriteConcernException; +import com.mongodb.MongoWriteException; +import com.mongodb.WriteConcernResult; +import com.mongodb.WriteError; +import com.mongodb.bulk.BulkWriteResult; +import com.mongodb.internal.bulk.WriteRequest; +import org.bson.BsonDocument; + +/** + *

This class is not part of the public API and may be removed or changed at any time

+ */ +public class MongoBulkWriteExceptionHelper { + + public static MongoException translateSingleOperationBulkWriteResultException( + final WriteRequest.Type type, final MongoBulkWriteException e) { + MongoException exception; + if (e.getWriteConcernError() != null) { + exception = new MongoWriteConcernException(e.getWriteConcernError(), + translateBulkWriteResult(type, e.getWriteResult()), e.getServerAddress(), e.getErrorLabels(), e); + } else if (!e.getWriteErrors().isEmpty()) { + exception = new MongoWriteException(new WriteError(e.getWriteErrors().get(0)), e.getServerAddress(), + e.getErrorLabels()); + } else { + exception = new MongoWriteException(new WriteError(-1, "Unknown write error", new BsonDocument()), + e.getServerAddress(), e.getErrorLabels()); + } + + return exception; + } + + private static WriteConcernResult translateBulkWriteResult(final WriteRequest.Type type, final BulkWriteResult writeResult) { + switch (type) { + case INSERT: + return WriteConcernResult.acknowledged(writeResult.getInsertedCount(), false, null); + case DELETE: + return WriteConcernResult.acknowledged(writeResult.getDeletedCount(), false, null); + case UPDATE: + case REPLACE: + return WriteConcernResult.acknowledged(writeResult.getMatchedCount() + writeResult.getUpserts().size(), + writeResult.getMatchedCount() > 0, + writeResult.getUpserts().isEmpty() + ? null : writeResult.getUpserts().get(0).getId()); + default: + throw new MongoInternalException("Unhandled write request type: " + type); + } + } + + + private MongoBulkWriteExceptionHelper() { + } +} diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java index 84c810f1b5e..ebba5349c7d 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoOperationPublisher.java @@ -18,17 +18,11 @@ import com.mongodb.AutoEncryptionSettings; import com.mongodb.MongoBulkWriteException; import com.mongodb.MongoClientException; -import com.mongodb.MongoException; import com.mongodb.MongoNamespace; -import com.mongodb.MongoWriteConcernException; -import com.mongodb.MongoWriteException; import com.mongodb.ReadConcern; import com.mongodb.ReadPreference; import com.mongodb.WriteConcern; -import com.mongodb.WriteConcernResult; -import com.mongodb.WriteError; import com.mongodb.bulk.BulkWriteResult; -import com.mongodb.bulk.WriteConcernError; import com.mongodb.client.model.BulkWriteOptions; import com.mongodb.client.model.CountOptions; import com.mongodb.client.model.CreateCollectionOptions; @@ -66,7 +60,6 @@ import com.mongodb.internal.operation.WriteOperation; import com.mongodb.lang.Nullable; import com.mongodb.reactivestreams.client.ClientSession; -import org.bson.BsonDocument; import org.bson.BsonValue; import org.bson.UuidRepresentation; import org.bson.codecs.configuration.CodecRegistry; @@ -86,6 +79,7 @@ import static com.mongodb.assertions.Assertions.assertNotNull; import static com.mongodb.assertions.Assertions.isTrue; import static com.mongodb.assertions.Assertions.notNull; +import static com.mongodb.internal.operation.MongoBulkWriteExceptionHelper.translateSingleOperationBulkWriteResultException; import static java.util.Collections.singletonList; import static org.bson.codecs.configuration.CodecRegistries.withUuidRepresentation; @@ -530,34 +524,7 @@ private Mono createSingleWriteRequestMono( @Nullable final ClientSession clientSession, final WriteRequest.Type type) { return createWriteOperationMono(operations::getTimeoutSettings, operation, clientSession) - .onErrorMap(MongoBulkWriteException.class, e -> { - MongoException exception; - WriteConcernError writeConcernError = e.getWriteConcernError(); - if (e.getWriteErrors().isEmpty() && writeConcernError != null) { - WriteConcernResult writeConcernResult; - if (type == WriteRequest.Type.INSERT) { - writeConcernResult = WriteConcernResult.acknowledged(e.getWriteResult().getInsertedCount(), false, null); - } else if (type == WriteRequest.Type.DELETE) { - writeConcernResult = WriteConcernResult.acknowledged(e.getWriteResult().getDeletedCount(), false, null); - } else { - writeConcernResult = WriteConcernResult - .acknowledged(e.getWriteResult().getMatchedCount() + e.getWriteResult().getUpserts().size(), - e.getWriteResult().getMatchedCount() > 0, - e.getWriteResult().getUpserts().isEmpty() - ? null : e.getWriteResult().getUpserts().get(0).getId()); - } - exception = new MongoWriteConcernException(writeConcernError, writeConcernResult, e.getServerAddress(), - e.getErrorLabels()); - } else if (!e.getWriteErrors().isEmpty()) { - exception = new MongoWriteException(new WriteError(e.getWriteErrors().get(0)), e.getServerAddress(), - e.getErrorLabels()); - } else { - exception = new MongoWriteException(new WriteError(-1, "Unknown write error", new BsonDocument()), - e.getServerAddress(), e.getErrorLabels()); - } - - return exception; - }); + .onErrorMap(MongoBulkWriteException.class, e -> translateSingleOperationBulkWriteResultException(type, e)); } private OperationExecutor getExecutor(final TimeoutSettings timeoutSettings) { diff --git a/driver-sync/src/main/com/mongodb/client/internal/MongoCollectionImpl.java b/driver-sync/src/main/com/mongodb/client/internal/MongoCollectionImpl.java index 736e1541212..1a730ca461a 100755 --- a/driver-sync/src/main/com/mongodb/client/internal/MongoCollectionImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/MongoCollectionImpl.java @@ -18,15 +18,10 @@ import com.mongodb.AutoEncryptionSettings; import com.mongodb.MongoBulkWriteException; -import com.mongodb.MongoInternalException; import com.mongodb.MongoNamespace; -import com.mongodb.MongoWriteConcernException; -import com.mongodb.MongoWriteException; import com.mongodb.ReadConcern; import com.mongodb.ReadPreference; import com.mongodb.WriteConcern; -import com.mongodb.WriteConcernResult; -import com.mongodb.WriteError; import com.mongodb.bulk.BulkWriteResult; import com.mongodb.client.AggregateIterable; import com.mongodb.client.ChangeStreamIterable; @@ -85,6 +80,7 @@ import static com.mongodb.internal.bulk.WriteRequest.Type.INSERT; import static com.mongodb.internal.bulk.WriteRequest.Type.REPLACE; import static com.mongodb.internal.bulk.WriteRequest.Type.UPDATE; +import static com.mongodb.internal.operation.MongoBulkWriteExceptionHelper.translateSingleOperationBulkWriteResultException; import static java.util.Collections.singletonList; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.bson.codecs.configuration.CodecRegistries.withUuidRepresentation; @@ -1113,34 +1109,9 @@ private BulkWriteResult executeSingleWriteRequest(@Nullable final ClientSession final WriteOperation writeOperation, final WriteRequest.Type type) { try { - return getExecutor(timeoutSettings) - .execute(writeOperation, readConcern, clientSession); + return getExecutor(timeoutSettings).execute(writeOperation, readConcern, clientSession); } catch (MongoBulkWriteException e) { - if (e.getWriteErrors().isEmpty()) { - throw new MongoWriteConcernException(e.getWriteConcernError(), - translateBulkWriteResult(type, e.getWriteResult()), - e.getServerAddress(), e.getErrorLabels()); - } else { - throw new MongoWriteException(new WriteError(e.getWriteErrors().get(0)), e.getServerAddress(), e.getErrorLabels()); - } - - } - } - - private WriteConcernResult translateBulkWriteResult(final WriteRequest.Type type, final BulkWriteResult writeResult) { - switch (type) { - case INSERT: - return WriteConcernResult.acknowledged(writeResult.getInsertedCount(), false, null); - case DELETE: - return WriteConcernResult.acknowledged(writeResult.getDeletedCount(), false, null); - case UPDATE: - case REPLACE: - return WriteConcernResult.acknowledged(writeResult.getMatchedCount() + writeResult.getUpserts().size(), - writeResult.getMatchedCount() > 0, - writeResult.getUpserts().isEmpty() - ? null : writeResult.getUpserts().get(0).getId()); - default: - throw new MongoInternalException("Unhandled write request type: " + type); + throw translateSingleOperationBulkWriteResultException(type, e); } } From 64c4460b10d129a7aba86d0d75c4d84af78734af Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Thu, 19 Mar 2026 14:49:21 +0000 Subject: [PATCH 2/3] Make the helper final --- .../internal/operation/MongoBulkWriteExceptionHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-core/src/main/com/mongodb/internal/operation/MongoBulkWriteExceptionHelper.java b/driver-core/src/main/com/mongodb/internal/operation/MongoBulkWriteExceptionHelper.java index 02fe054c0ef..769bb1ddbea 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/MongoBulkWriteExceptionHelper.java +++ b/driver-core/src/main/com/mongodb/internal/operation/MongoBulkWriteExceptionHelper.java @@ -29,7 +29,7 @@ /** *

This class is not part of the public API and may be removed or changed at any time

*/ -public class MongoBulkWriteExceptionHelper { +public final class MongoBulkWriteExceptionHelper { public static MongoException translateSingleOperationBulkWriteResultException( final WriteRequest.Type type, final MongoBulkWriteException e) { From 1eb1e2eb97d2aa41792c5d7777562081f6c9dbcf Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Thu, 19 Mar 2026 16:50:12 +0000 Subject: [PATCH 3/3] Fix spotbugs / static checks --- .../src/main/com/mongodb/MongoWriteConcernException.java | 8 ++++---- .../internal/operation/MongoBulkWriteExceptionHelper.java | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/driver-core/src/main/com/mongodb/MongoWriteConcernException.java b/driver-core/src/main/com/mongodb/MongoWriteConcernException.java index f4e1b81f94c..9ce38660a0f 100644 --- a/driver-core/src/main/com/mongodb/MongoWriteConcernException.java +++ b/driver-core/src/main/com/mongodb/MongoWriteConcernException.java @@ -76,9 +76,9 @@ public MongoWriteConcernException(final WriteConcernError writeConcernError, @Nu */ public MongoWriteConcernException(final WriteConcernError writeConcernError, @Nullable final WriteConcernResult writeConcernResult, final ServerAddress serverAddress, final Collection errorLabels) { - super(writeConcernError.getCode(), writeConcernError.getMessage(), serverAddress); + super(notNull("writeConcernError", writeConcernError).getCode(), writeConcernError.getMessage(), serverAddress); this.writeConcernResult = writeConcernResult; - this.writeConcernError = notNull("writeConcernError", writeConcernError); + this.writeConcernError = writeConcernError; addLabels(errorLabels); } @@ -94,9 +94,9 @@ public MongoWriteConcernException(final WriteConcernError writeConcernError, @Nu */ public MongoWriteConcernException(final WriteConcernError writeConcernError, @Nullable final WriteConcernResult writeConcernResult, final ServerAddress serverAddress, final Collection errorLabels, final Throwable t) { - super(writeConcernError.getCode(), writeConcernError.getMessage(), t, serverAddress); + super(notNull("writeConcernError", writeConcernError).getCode(), writeConcernError.getMessage(), t, serverAddress); this.writeConcernResult = writeConcernResult; - this.writeConcernError = notNull("writeConcernError", writeConcernError); + this.writeConcernError = writeConcernError; addLabels(errorLabels); } diff --git a/driver-core/src/main/com/mongodb/internal/operation/MongoBulkWriteExceptionHelper.java b/driver-core/src/main/com/mongodb/internal/operation/MongoBulkWriteExceptionHelper.java index 769bb1ddbea..81bf43e1f79 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/MongoBulkWriteExceptionHelper.java +++ b/driver-core/src/main/com/mongodb/internal/operation/MongoBulkWriteExceptionHelper.java @@ -23,6 +23,7 @@ import com.mongodb.WriteConcernResult; import com.mongodb.WriteError; import com.mongodb.bulk.BulkWriteResult; +import com.mongodb.bulk.WriteConcernError; import com.mongodb.internal.bulk.WriteRequest; import org.bson.BsonDocument; @@ -34,8 +35,9 @@ public final class MongoBulkWriteExceptionHelper { public static MongoException translateSingleOperationBulkWriteResultException( final WriteRequest.Type type, final MongoBulkWriteException e) { MongoException exception; - if (e.getWriteConcernError() != null) { - exception = new MongoWriteConcernException(e.getWriteConcernError(), + WriteConcernError writeConcernError = e.getWriteConcernError(); + if (writeConcernError != null) { + exception = new MongoWriteConcernException(writeConcernError, translateBulkWriteResult(type, e.getWriteResult()), e.getServerAddress(), e.getErrorLabels(), e); } else if (!e.getWriteErrors().isEmpty()) { exception = new MongoWriteException(new WriteError(e.getWriteErrors().get(0)), e.getServerAddress(), @@ -44,7 +46,6 @@ public static MongoException translateSingleOperationBulkWriteResultException( exception = new MongoWriteException(new WriteError(-1, "Unknown write error", new BsonDocument()), e.getServerAddress(), e.getErrorLabels()); } - return exception; }