CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-quarkus--quarkus-narayana-jta

JTA transaction support for Quarkus applications with programmatic and declarative transaction management

Pending
Overview
Eval results
Files

declarative-transactions.mddocs/

Declarative Transaction Management

Standard JTA @Transactional annotations with Quarkus-specific enhancements for timeout configuration and comprehensive CDI integration.

Capabilities

@Transactional Annotation

Standard Jakarta Transaction annotation for declarative transaction management.

/**
 * Marks method or class for automatic transaction management
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface Transactional {
    
    /**
     * Transaction propagation behavior
     * @return TxType constant defining transaction semantics
     */
    TxType value() default TxType.REQUIRED;
    
    /**
     * Exception types that trigger rollback
     * @return Array of exception classes
     */
    Class[] rollbackOn() default {};
    
    /**
     * Exception types that do NOT trigger rollback
     * @return Array of exception classes  
     */
    Class[] dontRollbackOn() default {};
}

TxType Values:

enum TxType {
    /** Join existing transaction or create new one (default) */
    REQUIRED,
    
    /** Always create new transaction, suspend existing */
    REQUIRES_NEW,
    
    /** Must run within existing transaction, throw exception if none */
    MANDATORY,
    
    /** Run within transaction if exists, without transaction if none */
    SUPPORTS,
    
    /** Always run without transaction, suspend existing */
    NOT_SUPPORTED,
    
    /** Throw exception if transaction exists */
    NEVER
}

Usage Examples:

import jakarta.transaction.Transactional;
import static jakarta.transaction.Transactional.TxType.*;

@ApplicationScoped
public class UserService {
    
    @Transactional // Uses REQUIRED by default
    public void createUser(User user) {
        validateUser(user);
        userRepository.persist(user);
        // Transaction committed automatically on success
        // Rolled back automatically on RuntimeException
    }
    
    @Transactional(REQUIRES_NEW)
    public void auditUserCreation(String username) {
        // Always runs in new transaction, independent of caller
        auditRepository.log("User created: " + username);
    }
    
    @Transactional(MANDATORY)
    public void updateUserInTransaction(User user) {
        // Must be called within existing transaction
        user.setLastModified(Instant.now());
        userRepository.merge(user);
    }
    
    @Transactional(NOT_SUPPORTED) 
    public void sendEmailNotification(String email, String message) {
        // Runs outside transaction scope
        emailService.send(email, message);
    }
}

Exception Handling in Declarative Transactions

Configure which exceptions cause rollback vs commit.

/**
 * Control transaction rollback based on exception types
 */
@Transactional(
    rollbackOn = {BusinessException.class, ValidationException.class},
    dontRollbackOn = {WarningException.class}
)

Usage Examples:

@ApplicationScoped
public class PaymentService {
    
    // Roll back on any RuntimeException (default behavior)
    @Transactional
    public void processPayment(Payment payment) {
        if (payment.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Invalid amount"); // Triggers rollback
        }
        paymentProcessor.charge(payment);
    }
    
    // Custom rollback behavior
    @Transactional(
        rollbackOn = {InsufficientFundsException.class},
        dontRollbackOn = {PaymentWarningException.class}
    )
    public void processRiskyPayment(Payment payment) {
        try {
            paymentProcessor.processHighRisk(payment);
        } catch (PaymentWarningException e) {
            // Transaction continues and commits
            logger.warn("Payment processed with warning", e);
        } catch (InsufficientFundsException e) {
            // Transaction is rolled back
            throw e;
        }
    }
}

@TransactionConfiguration Annotation

Quarkus-specific annotation for configuring transaction timeouts.

/**
 * Configure transaction timeout at method or class level
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface TransactionConfiguration {
    
    /** Indicates no timeout configured */
    int UNSET_TIMEOUT = -1;
    
    /** Indicates no config property configured */
    String UNSET_TIMEOUT_CONFIG_PROPERTY = "<<unset>>";
    
    /**
     * Transaction timeout in seconds
     * @return Timeout value, UNSET_TIMEOUT for default
     */
    int timeout() default UNSET_TIMEOUT;
    
    /**
     * Configuration property name for timeout value
     * Property value takes precedence over timeout() if both are set
     * @return Property name or UNSET_TIMEOUT_CONFIG_PROPERTY
     */
    String timeoutFromConfigProperty() default UNSET_TIMEOUT_CONFIG_PROPERTY;
}

Usage Examples:

@ApplicationScoped
public class BatchProcessingService {
    
    @Transactional
    @TransactionConfiguration(timeout = 300) // 5 minutes
    public void processLargeBatch(List<BatchItem> items) {
        for (BatchItem item : items) {
            processItem(item);
        }
    }
    
    @Transactional  
    @TransactionConfiguration(timeoutFromConfigProperty = "batch.processing.timeout")
    public void configurableBatchProcess(List<BatchItem> items) {
        // Timeout read from application.properties: batch.processing.timeout=600
        processBatchItems(items);
    }
    
    @Transactional
    @TransactionConfiguration(
        timeout = 120, // Fallback value
        timeoutFromConfigProperty = "critical.operation.timeout"
    )
    public void criticalOperation() {
        // Uses property value if set, otherwise uses 120 seconds
        performCriticalWork();
    }
}

Class-Level Configuration

Apply transaction behavior to all methods in a class.

/**
 * Class-level annotations apply to all @Transactional methods
 * Method-level configuration overrides class-level
 */
@Transactional(REQUIRES_NEW)
@TransactionConfiguration(timeout = 60)
@ApplicationScoped
public class AuditService {
    
    // Inherits REQUIRES_NEW and 60s timeout
    @Transactional
    public void logUserAction(String action) { }
    
    // Overrides to use REQUIRED semantics, keeps 60s timeout
    @Transactional(REQUIRED)
    public void logSystemEvent(String event) { }
    
    // Overrides timeout to 30s, keeps REQUIRES_NEW semantics  
    @TransactionConfiguration(timeout = 30)
    @Transactional
    public void logQuickEvent(String event) { }
}

CDI Integration Patterns

Integration with CDI scopes and lifecycle events.

/**
 * Transaction-scoped CDI beans
 */
@TransactionScoped
public class TransactionScopedAuditLogger {
    
    private List<String> transactionLogs = new ArrayList<>();
    
    @PostConstruct
    void onTransactionBegin() {
        // Called when transaction begins
        transactionLogs.add("Transaction started at " + Instant.now());
    }
    
    @PreDestroy
    void onTransactionEnd() {
        // Called before transaction ends (commit or rollback)
        persistLogs(transactionLogs);
    }
    
    public void log(String message) {
        transactionLogs.add(message);
    }
}

Transaction Lifecycle Events:

@ApplicationScoped
public class TransactionEventObserver {
    
    void onTransactionBegin(@Observes @Initialized(TransactionScoped.class) Object event) {
        logger.info("Transaction scope initialized");
    }
    
    void onBeforeTransactionEnd(@Observes @BeforeDestroyed(TransactionScoped.class) Object event) {
        logger.info("Transaction about to end");
    }
    
    void onTransactionEnd(@Observes @Destroyed(TransactionScoped.class) Object event) {
        logger.info("Transaction scope destroyed");
    }
}

Interceptor Behavior

Understanding how transaction interceptors work with method calls.

@ApplicationScoped
public class OrderService {
    
    @Transactional
    public void processOrder(Order order) {
        validateOrder(order); // Runs in same transaction
        persistOrder(order);  // Runs in same transaction
        notifyCustomer(order); // Runs in same transaction
    }
    
    @Transactional(REQUIRES_NEW)
    private void auditOrderProcessing(Order order) {
        // Private method - interceptor NOT applied!
        // This will NOT start a new transaction
        auditRepository.log("Processing order: " + order.getId());
    }
    
    @Transactional(REQUIRES_NEW)
    public void auditOrderProcessingPublic(Order order) {
        // Public method - interceptor applied
        // This WILL start a new transaction
        auditRepository.log("Processing order: " + order.getId());
    }
}

Best Practices

Method Visibility

// ✅ Correct - public method, interceptor applies
@Transactional
public void processData() { }

// ❌ Wrong - private method, interceptor NOT applied
@Transactional  
private void processDataPrivate() { }

// ✅ Correct - package-private works with CDI
@Transactional
void processDataPackage() { }

Self-Invocation Issues

@ApplicationScoped
public class DocumentService {
    
    @Transactional
    public void processDocument(Document doc) {
        validateDocument(doc);
        // ❌ Self-invocation - transaction interceptor NOT applied
        this.saveDocument(doc);
    }
    
    @Transactional(REQUIRES_NEW)
    public void saveDocument(Document doc) {
        // This will NOT start new transaction when called from processDocument
        documentRepository.save(doc);
    }
}

// ✅ Solution: Inject self or use separate service
@ApplicationScoped  
public class DocumentService {
    
    @Inject
    DocumentService self; // CDI proxy
    
    @Transactional
    public void processDocument(Document doc) {
        validateDocument(doc);
        // ✅ Correct - uses CDI proxy, interceptor applies
        self.saveDocument(doc);
    }
}

Exception Handling Best Practices

@Transactional
public void businessOperation() {
    try {
        riskyOperation();
    } catch (CheckedException e) {
        // Checked exceptions don't trigger rollback by default
        // Convert to runtime exception to trigger rollback
        throw new BusinessException("Operation failed", e);
    }
}

@Transactional(rollbackOn = CheckedException.class)
public void businessOperationWithCheckedRollback() {
    // Now CheckedException will trigger rollback
    riskyOperationThatThrowsCheckedException();
}

Install with Tessl CLI

npx tessl i tessl/maven-io-quarkus--quarkus-narayana-jta

docs

configuration.md

declarative-transactions.md

index.md

programmatic-transactions.md

transaction-semantics.md

tile.json