spring-projects / spring-projects/spring-data-mongodb

MongoDB error code 112 (WriteConflict) is translated to DataIntegrityViolationException, even when the driver marks it as retryable.

Open
#5,150 7 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

type: enhancement
Dominant language
Java
Stars
1.7k
Forks
1.1k
PR merge metrics
No merged PRs in 30d

Description

MongoDB returns code=112 (WriteConflict) with errorLabels=["TransientTransactionError"] and a retry message.
Spring Data MongoDB translates this to DataIntegrityViolationException (non-transient), which breaks retry policies that rely on Spring exception types.

Spring Data MongoDB: 5.0.1

Observed behavior

MongoDB response:

  • code: 112 (WriteConflict)
  • errorLabels: ["TransientTransactionError"]
  • errmsg contains: Please retry your operation or multi-document transaction.

MongoExceptionTranslator translates this to org.springframework.dao.DataIntegrityViolationException

Impact

WriteConflict is a concurrency conflict and MongoDB marks it retryable.
Mapping it to DataIntegrityViolationException classifies it as non-transient in Spring and can prevent retries.

Reproducer: https://github.com/banseok1216/spring-batch/blob/mongodb-writeConflict/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java

./mvnw -pl spring-batch-core -Dtest=MongoDBJobRepositoryIntegrationTests#testParallelJobExecution test

Full stack trace

Expand full stack trace
org.springframework.dao.DataIntegrityViolationException: Command execution failed on MongoDB server with error 112 (WriteConflict): 'Caused by :: Write conflict during plan execution and yielding is disabled. :: Please retry your operation or multi-document transaction.' on server localhost:32796. The full response is {"errorLabels": ["TransientTransactionError"], "ok": 0.0, "errmsg": "Caused by :: Write conflict during plan execution and yielding is disabled. :: Please retry your operation or multi-document transaction.", "code": 112, "codeName": "WriteConflict", "$clusterTime": {"clusterTime": {"$timestamp": {"t": 1768999881, "i": 7}}, "signature": {"hash": {"$binary": {"base64": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=", "subType": "00"}}, "keyId": 0}}, "operationTime": {"$timestamp": {"t": 1768999881, "i": 7}}}

	at org.springframework.data.mongodb.core.MongoExceptionTranslator.doTranslateException(MongoExceptionTranslator.java:140)
	at org.springframework.data.mongodb.core.MongoExceptionTranslator.translateExceptionIfPossible(MongoExceptionTranslator.java:72)
	at org.springframework.data.mongodb.core.MongoTemplate.potentiallyConvertRuntimeException(MongoTemplate.java:3175)
	at org.springframework.data.mongodb.core.MongoTemplate.executeFindOneInternal(MongoTemplate.java:3044)
	at org.springframework.data.mongodb.core.MongoTemplate.doFindAndReplace(MongoTemplate.java:2976)
	at org.springframework.data.mongodb.core.MongoTemplate.findAndReplace(MongoTemplate.java:1215)
	at org.springframework.data.mongodb.core.MongoTemplate.findAndReplace(MongoTemplate.java:1183)
	at org.springframework.data.mongodb.core.MongoOperations.findAndReplace(MongoOperations.java:1171)
	at org.springframework.data.mongodb.core.MongoOperations.findAndReplace(MongoOperations.java:1147)
	at org.springframework.data.mongodb.core.MongoOperations.findAndReplace(MongoOperations.java:1102)
	at org.springframework.batch.core.repository.dao.mongodb.MongoJobExecutionDao.updateJobExecution(MongoJobExecutionDao.java:92)
	at org.springframework.batch.core.repository.support.SimpleJobRepository.update(SimpleJobRepository.java:152)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:158)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:370)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:222)
	at jdk.proxy2/jdk.proxy2.$Proxy80.update(Unknown Source)
	at org.springframework.batch.core.launch.support.TaskExecutorJobLauncher.launchJobExecution(TaskExecutorJobLauncher.java:259)
	at org.springframework.batch.core.launch.support.TaskExecutorJobLauncher.run(TaskExecutorJobLauncher.java:109)
	at org.springframework.batch.core.launch.support.SimpleJobOperator.start(SimpleJobOperator.java:201)
	at org.springframework.batch.core.launch.support.TaskExecutorJobOperator.start(TaskExecutorJobOperator.java:117)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:158)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:370)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:222)
	at jdk.proxy2/jdk.proxy2.$Proxy126.start(Unknown Source)
	at org.springframework.batch.core.repository.support.MongoDBAsyncJobRepositoryIntegrationTests.testJobExecution(MongoDBAsyncJobRepositoryIntegrationTests.java:72)
Caused by: com.mongodb.MongoCommandException: Command execution failed on MongoDB server with error 112 (WriteConflict): 'Caused by :: Write conflict during plan execution and yielding is disabled. :: Please retry your operation or multi-document transaction.' on server localhost:32796. The full response is {"errorLabels": ["TransientTransactionError"], "ok": 0.0, "errmsg": "Caused by :: Write conflict during plan execution and yielding is disabled. :: Please retry your operation or multi-document transaction.", "code": 112, "codeName": "WriteConflict", "$clusterTime": {"clusterTime": {"$timestamp": {"t": 1768999881, "i": 7}}, "signature": {"hash": {"$binary": {"base64": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=", "subType": "00"}}, "keyId": 0}}, "operationTime": {"$timestamp": {"t": 1768999881, "i": 7}}}
	at com.mongodb.internal.connection.ProtocolHelper.getCommandFailureException(ProtocolHelper.java:210)
	at com.mongodb.internal.connection.InternalStreamConnection.receiveCommandMessageResponse(InternalStreamConnection.java:520)
	at com.mongodb.internal.connection.InternalStreamConnection.sendAndReceiveInternal(InternalStreamConnection.java:448)
	at com.mongodb.internal.connection.InternalStreamConnection.lambda$sendAndReceive$0(InternalStreamConnection.java:375)
	at com.mongodb.internal.connection.InternalStreamConnection.sendAndReceive(InternalStreamConnection.java:378)
	at com.mongodb.internal.connection.UsageTrackingInternalConnection.sendAndReceive(UsageTrackingInternalConnection.java:111)
	at com.mongodb.internal.connection.DefaultConnectionPool$PooledConnection.sendAndReceive(DefaultConnectionPool.java:757)
	at com.mongodb.internal.connection.CommandProtocolImpl.execute(CommandProtocolImpl.java:60)
	at com.mongodb.internal.connection.DefaultServer$DefaultServerProtocolExecutor.execute(DefaultServer.java:207)
	at com.mongodb.internal.connection.DefaultServerConnection.executeProtocol(DefaultServerConnection.java:112)
	at com.mongodb.internal.connection.DefaultServerConnection.command(DefaultServerConnection.java:82)
	at com.mongodb.internal.connection.DefaultServerConnection.command(DefaultServerConnection.java:74)
	at com.mongodb.internal.connection.DefaultServer$OperationCountTrackingConnection.command(DefaultServer.java:297)
	at com.mongodb.internal.operation.SyncOperationHelper.lambda$executeRetryableWrite$10(SyncOperationHelper.java:267)
	at com.mongodb.internal.operation.SyncOperationHelper.lambda$withSourceAndConnection$0(SyncOperationHelper.java:131)
	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:156)
	at com.mongodb.internal.operation.SyncOperationHelper.lambda$withSourceAndConnection$1(SyncOperationHelper.java:130)
	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:156)
	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:129)
	at com.mongodb.internal.operation.SyncOperationHelper.lambda$executeRetryableWrite$11(SyncOperationHelper.java:252)
	at com.mongodb.internal.operation.SyncOperationHelper.lambda$decorateWriteWithRetries$12(SyncOperationHelper.java:308)
	at com.mongodb.internal.async.function.RetryingSyncSupplier.get(RetryingSyncSupplier.java:67)
	at com.mongodb.internal.operation.SyncOperationHelper.executeRetryableWrite(SyncOperationHelper.java:279)
	at com.mongodb.internal.operation.BaseFindAndModifyOperation.execute(BaseFindAndModifyOperation.java:80)
	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:448)
	at com.mongodb.client.internal.MongoCollectionImpl.executeFindOneAndReplace(MongoCollectionImpl.java:754)
	at com.mongodb.client.internal.MongoCollectionImpl.findOneAndReplace(MongoCollectionImpl.java:747)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:281)
	at org.springframework.data.mongodb.SessionAwareMethodInterceptor.invoke(SessionAwareMethodInterceptor.java:118)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:222)
	at jdk.proxy2/jdk.proxy2.$Proxy130.findOneAndReplace(Unknown Source)
	at org.springframework.data.mongodb.core.MongoTemplate$FindAndReplaceCallback.doInCollection(MongoTemplate.java:3420)
	at org.springframework.data.mongodb.core.MongoTemplate$FindAndReplaceCallback.doInCollection(MongoTemplate.java:3382)
	at org.springframework.data.mongodb.core.MongoTemplate.executeFindOneInternal(MongoTemplate.java:3041)
	... 33 more

Expected

When MongoDB marks a failure retryable (e.g. TransientTransactionError), translate it to a transient Spring exception
(e.g. ConcurrencyFailureException / TransientDataAccessException) instead of DataIntegrityViolationException.

Open Question

Should the translator use:

  • errorLabels (primary), and optionally code=112, or
  • code=112 alone

to decide transient vs non-transient for WriteConflict?

Relates to: https://github.com/spring-projects/spring-batch/pull/5145

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with MongoExceptionTranslator.java, especially doTranslateException, and review the MongoDB error-label and code handling described in the report. Run the linked MongoDBJobRepositoryIntegrationTests#testParallelJobExecution reproducer with the provided Maven command. Done means WriteConflict failures marked TransientTransactionError are translated to a transient Spring exception, with tests covering the chosen errorLabels versus code=112 behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, mongodb, spring
Domain
backend, database
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.