Why does a deadlock occur on completing an external task? And how to retry it?

We use the Spring Retry logic to have several attempts on OptimisticLockingExceptions and deadlocks before a failure is returned.

Since deadlocks in the database are not thrown as specific exceptions, I figured out to ‘catch’ them within a custom RetryPolicy:

public class NextRetryPolicy extends SimpleRetryPolicy {

	private final static NextLogger LOGGER = NextLogger.RETRY_LOGGER_INSTANCE;

	public NextRetryPolicy(int maxAttempts) {
		// Instantiate super class with OptimisticLockingExceptions as retryable and for counting attempts
		super(maxAttempts, Map.ofEntries(new AbstractMap.SimpleEntry<Class<? extends Throwable>, Boolean>(OptimisticLockingException.class, true)), true);
	}

	@Override
	public boolean canRetry(RetryContext context) {
		// Log first actual retry
		boolean simpleRetry = super.canRetry(context);
		if (simpleRetry) {
			if (context.getLastThrowable() != null) {
				LOGGER.debug("Retry operation on exception [ {} ] with attempt count [ {} ]", context.getLastThrowable().getClass().getName(), context.getRetryCount());
			} else if (context.getRetryCount() > 0) {
				LOGGER.debug("Retry operation with attempt count [ {} ]", context.getRetryCount());
			}
			return true;
		}
		if (context.getRetryCount() >= this.getMaxAttempts()) {
			return false;
		}

		// Check for deadlock exception and allow retry in these cases, too
		int indexOfDbException = ExceptionUtils.indexOfType(context.getLastThrowable(), BatchUpdateException.class);
		if (indexOfDbException > -1) {
			Throwable dbUpdateException = ExceptionUtils.getThrowableList(context.getLastThrowable()).get(indexOfDbException);
			if (dbUpdateException.getMessage().contains("deadlock detected")) {
				LOGGER.debug("Retry operation on deadlock (last exception [ {} ]) with attempt count [ {} ]", context.getLastThrowable().getClass().getName(), context.getRetryCount());
				return true;
			}
		}
		return false;
	}
}