CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-boot--spring-boot-starter-jooq

Starter for using jOOQ to access SQL databases with JDBC, providing auto-configuration and Spring integration

Pending
Overview
Eval results
Files

exception-handling.mddocs/

Exception Handling

Exception translation system that converts jOOQ and JDBC exceptions to Spring's DataAccessException hierarchy for consistent error handling across your application.

Capabilities

ExceptionTranslatorExecuteListener

Interface for execute listeners that translate jOOQ exceptions to Spring exceptions during query execution.

/**
 * ExecuteListener interface for translating exceptions in the ExecuteContext
 * Most commonly used to translate SQLExceptions to Spring DataAccessExceptions
 */
public interface ExceptionTranslatorExecuteListener extends ExecuteListener {
    
    /**
     * Default implementation suitable for most applications
     * Uses Spring's standard SQL exception translation
     */
    ExceptionTranslatorExecuteListener DEFAULT = new DefaultExceptionTranslatorExecuteListener();
    
    /**
     * Creates a new ExceptionTranslatorExecuteListener with custom translator factory
     * @param translatorFactory factory function to create SQLExceptionTranslator from ExecuteContext
     * @return new ExceptionTranslatorExecuteListener instance
     */
    static ExceptionTranslatorExecuteListener of(
        Function<ExecuteContext, SQLExceptionTranslator> translatorFactory);
}

DefaultExceptionTranslatorExecuteListener

Default implementation that delegates exception translation to Spring's SQLExceptionTranslator.

/**
 * Default implementation of ExceptionTranslatorExecuteListener
 * Delegates to SQLExceptionTranslator for consistent Spring exception handling
 */
final class DefaultExceptionTranslatorExecuteListener implements ExceptionTranslatorExecuteListener {
    
    /**
     * Create with default translator factory
     * Uses SQLErrorCodeSQLExceptionTranslator or SQLExceptionSubclassTranslator
     */
    DefaultExceptionTranslatorExecuteListener();
    
    /**
     * Create with custom translator factory
     * @param translatorFactory function to create SQLExceptionTranslator from ExecuteContext
     */
    DefaultExceptionTranslatorExecuteListener(
        Function<ExecuteContext, SQLExceptionTranslator> translatorFactory);
    
    /**
     * Handle exceptions during jOOQ execution
     * Translates SQLExceptions to Spring DataAccessExceptions
     * @param context the execution context containing exception information
     */
    @Override
    public void exception(ExecuteContext context);
}

Failure Analyzers

Diagnostic classes that provide helpful error messages when common configuration issues occur.

/**
 * Failure analyzer for when DSLContext bean is missing
 * Provides helpful suggestions for resolving configuration issues
 */
class NoDslContextBeanFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchBeanDefinitionException> 
    implements Ordered;

/**
 * Exception thrown when JAXB is not available for XML config parsing
 */
class JaxbNotAvailableException extends RuntimeException;

/**
 * Failure analyzer for JAXB availability issues
 * Provides guidance on adding JAXB dependencies when using XML configuration
 */
class JaxbNotAvailableExceptionFailureAnalyzer extends AbstractFailureAnalyzer<JaxbNotAvailableException>;

Usage Examples:

Exception Translation in Action

@Service
public class ProductService {
    
    @Autowired
    private DSLContext dsl;
    
    public Product findById(Long id) {
        try {
            return dsl.selectFrom(PRODUCT)
                      .where(PRODUCT.ID.eq(id))
                      .fetchOneInto(Product.class);
        } catch (DataAccessException e) {
            // All JDBC/SQL exceptions are translated to Spring exceptions
            // Can handle them consistently with other Spring Data access
            handleDataAccessException(e);
            throw e;
        }
    }
    
    public void createProduct(Product product) {
        try {
            dsl.insertInto(PRODUCT)
               .set(PRODUCT.NAME, product.getName())
               .set(PRODUCT.PRICE, product.getPrice())
               .execute();
        } catch (DuplicateKeyException e) {
            // Specific Spring exception for constraint violations
            throw new ProductAlreadyExistsException("Product with name already exists", e);
        } catch (DataIntegrityViolationException e) {
            // General integrity violations
            throw new InvalidProductDataException("Product data violates constraints", e);
        }
    }
    
    private void handleDataAccessException(DataAccessException e) {
        if (e instanceof EmptyResultDataAccessException) {
            // No results found
        } else if (e instanceof IncorrectResultSizeDataAccessException) {
            // Multiple results when one expected
        } else if (e instanceof DataIntegrityViolationException) {
            // Constraint violations
        } else if (e instanceof DataAccessResourceFailureException) {
            // Connection/resource issues
        }
    }
}

Custom Exception Translation

@Component
public class CustomExceptionTranslatorConfiguration {
    
    @Bean
    public ExceptionTranslatorExecuteListener customExceptionTranslator() {
        return ExceptionTranslatorExecuteListener.of(context -> {
            // Custom translator based on context
            SQLDialect dialect = context.configuration().dialect();
            
            if (dialect == SQLDialect.MYSQL) {
                return new MySQLSpecificExceptionTranslator();
            } else if (dialect == SQLDialect.POSTGRES) {
                return new PostgreSQLSpecificExceptionTranslator();
            } else {
                return new SQLExceptionSubclassTranslator();
            }
        });
    }
}

public class MySQLSpecificExceptionTranslator implements SQLExceptionTranslator {
    
    @Override
    public DataAccessException translate(String task, String sql, SQLException ex) {
        // MySQL-specific error code handling
        switch (ex.getErrorCode()) {
            case 1062: // Duplicate entry
                return new DuplicateKeyException("MySQL duplicate key: " + ex.getMessage(), ex);
            case 1048: // Column cannot be null  
                return new DataIntegrityViolationException("MySQL null constraint: " + ex.getMessage(), ex);
            case 1451: // Foreign key constraint
                return new DataIntegrityViolationException("MySQL foreign key: " + ex.getMessage(), ex);
            default:
                return new UncategorizedSQLException(task, sql, ex);
        }
    }
}

Global Exception Handling

@ControllerAdvice
public class DatabaseExceptionHandler {
    
    @ExceptionHandler(DuplicateKeyException.class)
    @ResponseStatus(HttpStatus.CONFLICT)
    public ResponseEntity<ErrorResponse> handleDuplicateKey(DuplicateKeyException e) {
        return ResponseEntity.status(HttpStatus.CONFLICT)
                           .body(new ErrorResponse("DUPLICATE_ENTRY", e.getMessage()));
    }
    
    @ExceptionHandler(DataIntegrityViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseEntity<ErrorResponse> handleDataIntegrity(DataIntegrityViolationException e) {
        return ResponseEntity.badRequest()
                           .body(new ErrorResponse("DATA_INTEGRITY", "Invalid data provided"));
    }
    
    @ExceptionHandler(DataAccessResourceFailureException.class)
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ResponseEntity<ErrorResponse> handleResourceFailure(DataAccessResourceFailureException e) {
        return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
                           .body(new ErrorResponse("DATABASE_UNAVAILABLE", "Database temporarily unavailable"));
    }
    
    @ExceptionHandler(DataAccessException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseEntity<ErrorResponse> handleGenericDataAccess(DataAccessException e) {
        // Log detailed error for debugging
        log.error("Database operation failed", e);
        
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                           .body(new ErrorResponse("DATABASE_ERROR", "An error occurred while accessing the database"));
    }
}

Testing Exception Translation

@SpringBootTest
class ExceptionTranslationTest {
    
    @Autowired
    private DSLContext dsl;
    
    @Test
    void shouldTranslateDuplicateKeyException() {
        // Setup: Insert initial record
        dsl.insertInto(USER)
           .set(USER.EMAIL, "test@example.com")
           .set(USER.NAME, "Test User")
           .execute();
        
        // Test: Attempt duplicate insert
        assertThatThrownBy(() -> 
            dsl.insertInto(USER)
               .set(USER.EMAIL, "test@example.com")  // Duplicate email
               .set(USER.NAME, "Another User")
               .execute()
        ).isInstanceOf(DuplicateKeyException.class)
         .hasMessageContaining("test@example.com");
    }
    
    @Test
    void shouldTranslateConstraintViolation() {
        assertThatThrownBy(() ->
            dsl.insertInto(USER)
               .set(USER.EMAIL, "invalid-email")  // Violates check constraint
               .set(USER.NAME, "Test User")
               .execute()
        ).isInstanceOf(DataIntegrityViolationException.class);
    }
}

Exception Translation Hierarchy

Common Spring DataAccessException Types

  • DuplicateKeyException - Unique constraint violations
  • DataIntegrityViolationException - Check constraints, foreign key violations
  • EmptyResultDataAccessException - Expected one result, got none
  • IncorrectResultSizeDataAccessException - Wrong number of results
  • DataAccessResourceFailureException - Connection failures, timeouts
  • UncategorizedSQLException - SQL exceptions not fitting other categories
  • BadSqlGrammarException - Syntax errors, invalid table/column names
  • DataRetrievalFailureException - Failures during data retrieval

Database-Specific Translation

The translation system automatically adapts to your database:

  • Uses SQLErrorCodeSQLExceptionTranslator when database-specific error codes are available
  • Falls back to SQLExceptionSubclassTranslator for generic JDBC 4.0+ exception classes
  • Supports all major databases (MySQL, PostgreSQL, Oracle, SQL Server, H2, etc.)

Configuration Integration

Exception translation is automatically configured and requires no additional setup:

  • Enabled by default when using the starter
  • Integrates with Spring Boot's failure analysis system
  • Provides helpful error messages during development
  • Works seamlessly with Spring's @Transactional rollback rules

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework-boot--spring-boot-starter-jooq

docs

auto-configuration.md

configuration.md

exception-handling.md

index.md

testing-integration.md

transaction-integration.md

tile.json