java.lang.ClassCastException: class javax.persistence.RollbackException cannot be cast to class java.sql.SQLException

Hello everyone!

I’ve encountered a bug in the implementation of org.camunda.bpm.engine.spring.SpringTransactionInterceptor

My Delegate code results in a Rollback Exception. The Interceptor, in an attempt to handle commit retry for CockroachDB casts RollbackException to the SQLException. We use Spring Data JPA (Hibernate) on our end to communicate with DB

I didn’t find any mention of such behaviour from this class

Spring Boot: 2.7.10
Camunda Version: 7.19.0
DBMS: PostgreSQL

Without digging depper I can understand that the matter can be fixed with a single instanceof

Original:

SpringTransactionInterceptor#66

  public <T> T execute(final Command<T> command) {
    TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
    transactionTemplate.setPropagationBehavior(transactionPropagation);
    try {
      // don't use lambdas here => CAM-12810
      return (T) transactionTemplate.execute((TransactionCallback) status -> next.execute(command));
    } catch (TransactionSystemException ex) {
      // When CockroachDB is used, a CRDB concurrency error may occur on transaction commit.
      // To ensure that these errors are still detected as OLEs, we must catch them and wrap
      // them in a CrdbTransactionRetryException
      SQLException sqlException = (SQLException) ex.getCause(); // <<-- Class Cast Exception Occurs Here
      if (processEngineConfiguration != null
          && DbSqlSession.isCrdbConcurrencyConflictOnCommit(sqlException, processEngineConfiguration)) {
        throw ProcessEngineLogger.PERSISTENCE_LOGGER.crdbTransactionRetryExceptionOnCommit(ex);
      } else {
        throw ex;
      }
    }

Proposed:

  public <T> T execute(final Command<T> command) {
    TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
    transactionTemplate.setPropagationBehavior(transactionPropagation);
    try {
      // don't use lambdas here => CAM-12810
      return (T) transactionTemplate.execute((TransactionCallback) status -> next.execute(command));
    } catch (TransactionSystemException ex) {
      Throwable cause = ex.getCause();
       
      if (cause instanceof SQLException) {
        handleCrdbConcurrencyError((SQLException) cause);
      } else {
        throw ex;
      }
    }
    
    /**
      * When CockroachDB is used, a CRDB concurrency error may occur on transaction commit.
      * To ensure that these errors are still detected as OLEs, we must catch them and wrap
      * them in a CrdbTransactionRetryException
      */  
    private void handleCrdbConcurrencyError(SQLException sqlException) {
         if (processEngineConfiguration != null
             && DbSqlSession.isCrdbConcurrencyConflictOnCommit(sqlException, processEngineConfiguration)) {
           throw ProcessEngineLogger.PERSISTENCE_LOGGER.crdbTransactionRetryExceptionOnCommit(ex);
         }
    }

Thanks for any feedback.

1 Like

Will be fixed with the next alpha release. Thank you for your contribution :slightly_smiling_face: :+1: