Skip to content

Translate transient WriteConflict to ConcurrencyFailureException#5195

Open
seonwooj0810 wants to merge 1 commit into
spring-projects:mainfrom
seonwooj0810:issue/5150
Open

Translate transient WriteConflict to ConcurrencyFailureException#5195
seonwooj0810 wants to merge 1 commit into
spring-projects:mainfrom
seonwooj0810:issue/5150

Conversation

@seonwooj0810

Copy link
Copy Markdown

WriteConflict (code 112) returned with a `TransientTransactionError` label is
a retryable concurrency failure, but `MongoExceptionTranslator` mapped it to
`DataIntegrityViolationException` — a `NonTransientDataAccessException` —
which silently breaks retry policies keyed on Spring's exception hierarchy.

This change checks `isTransientFailure(ex)` before falling through to
`DataIntegrityViolationException` and returns `ConcurrencyFailureException`
(a `TransientDataAccessException`) instead. The check is applied in both
the `DATA_INTEGRITY_EXCEPTIONS` branch (real `MongoWriteException`) and the
`isDataIntegrityViolationError` fall-through branch (plain `MongoException`
carrying a data-integrity error code).

Scope note: `MongoBulkWriteException` continues to return
`BulkOperationException` because bulk failures require per-write inspection
and were deemed out of scope for this fix.

Closes #5150

When a MongoException carries a TransientTransactionError label, return
ConcurrencyFailureException (a TransientDataAccessException) instead of
DataIntegrityViolationException so retry policies keyed on Spring's
exception hierarchy can react accordingly. Applied in both the
DATA_INTEGRITY_EXCEPTIONS branch and the isDataIntegrityViolationError
fall-through branch.

Closes spring-projects#5150

Signed-off-by: seonwooj0810 <seonwooj0810@gmail.com>
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label May 26, 2026
@mp911de mp911de self-assigned this Jun 29, 2026
}
}

if (isTransientFailure(ex)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure this is the best approach. We would not catch all exceptions and I would like to explore a broader wrapping, maybe even introducing a TransactionalMongoExceptionTranslator variant that wraps retryable exceptions in ConcurrencyFailureException.

In combination with MongoTransactionManager and Spring Framework 7 RetryOperations this could result in a good change to opt in to retries along with a signal for retryable operations.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @mp911de — that's a fair critique, and I agree the change as it stands is narrower than the problem deserves.

You're right that gating on isTransientFailure(...) inside the two data-integrity branches only rescues the WriteConflict-shaped path; any other retryable transient error that surfaces through a different branch still lands on a non-transient DataAccessException, so it's a partial signal at best.

The TransactionalMongoExceptionTranslator variant you're describing sounds like the right shape. Instead of sprinkling per-branch isTransientFailure checks, do a single up-front pass that maps anything carrying TransientTransactionError (and, on the commit side, UnknownTransactionCommitResult) to ConcurrencyFailureException, then delegate everything else to the standard translation. Keeping it as a separate opt-in translator rather than changing the default MongoExceptionTranslator behavior avoids surprising users who rely on today's mapping, and it composes cleanly: wire it through MongoTransactionManager, and Spring Framework 7 RetryOperations gets a consistent transient signal to key retries on.

A couple of things I'd like your steer on before I rework the PR:

  1. Opt-in surface — should the transactional translator be selected automatically when a MongoTransactionManager is in play, or exposed as an explicit configuration knob?
  2. Scope of "retryable" — beyond the TransientTransactionError label, should it also treat UnknownTransactionCommitResult as retryable, and should the decision lean on the error labels only, or also on an explicit code set (112 WriteConflict, 24 LockTimeout, …)?

Happy to take a first pass at TransactionalMongoExceptionTranslator along these lines if that direction works for you.

@mp911de mp911de added status: pending-design-work Needs design work before any code can be developed type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: pending-design-work Needs design work before any code can be developed type: enhancement A general enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

3 participants