Spring Boot Interview: Transaction Propagation Explained

Master Spring Boot transaction propagation: REQUIRED, REQUIRES_NEW, NESTED and more. 12 interview questions with code examples and common pitfalls.

Spring Boot Transaction Propagation: interview questions and practical examples

Transaction propagation represents a fundamental concept in Spring Boot, regularly evaluated during technical interviews. Understanding how transactions interact between @Transactional annotated methods helps avoid subtle production bugs and enables designing robust architectures.

Preparation Advice

Interviewers test the ability to choose the right propagation level based on business context. Being able to explain why REQUIRES_NEW rather than REQUIRED in a specific case makes the difference.

Transaction Propagation Fundamentals

Question 1: What is transaction propagation in Spring?

Propagation defines the behavior of a transactional method when called within the context of an existing transaction. It answers the question: "What happens when a @Transactional method calls another method that is also annotated?"

OrderService.javajava
// Demonstrating the propagation concept
@Service
public class OrderService {

    private final PaymentService paymentService;
    private final OrderRepository orderRepository;

    // Parent transaction - starts a new transaction
    @Transactional
    public void createOrder(OrderRequest request) {
        // Saves the order in the current transaction
        Order order = orderRepository.save(new Order(request));

        // Calling another @Transactional method
        // Propagation determines: same transaction or new one?
        paymentService.processPayment(order.getId(), request.getAmount());
    }
}

@Service
public class PaymentService {

    // Default propagation: REQUIRED
    // Joins the existing transaction from createOrder()
    @Transactional
    public void processPayment(Long orderId, BigDecimal amount) {
        // Executes in the SAME transaction as createOrder()
        // If this method fails, the order is also rolled back
    }
}

Spring provides seven propagation levels, each suited to specific business needs. The choice directly impacts data consistency and performance.

Question 2: Describe the behavior of REQUIRED (default propagation)

REQUIRED is the default propagation. If a transaction exists, the method joins it. Otherwise, a new transaction is created. This is the most common and intuitive behavior.

UserService.javajava
// REQUIRED: default behavior
@Service
public class UserService {

    private final UserRepository userRepository;
    private final AuditService auditService;

    @Transactional(propagation = Propagation.REQUIRED)
    public void updateUser(Long userId, UserUpdateRequest request) {
        // Starts a transaction if none exists
        User user = userRepository.findById(userId).orElseThrow();
        user.setEmail(request.getEmail());
        userRepository.save(user);

        // Audit joins the same transaction
        auditService.logUpdate(userId, "EMAIL_CHANGED");
    }
}

@Service
public class AuditService {

    private final AuditLogRepository auditLogRepository;

    @Transactional(propagation = Propagation.REQUIRED)
    public void logUpdate(Long userId, String action) {
        // Joins the transaction from updateUser()
        // Commit or rollback together
        auditLogRepository.save(new AuditLog(userId, action, Instant.now()));
    }
}

The diagram below illustrates the transactional flow:

text
updateUser() starts TX-1
    ├── save(user)          → TX-1
    └── logUpdate()         → joins TX-1 (REQUIRED)
           └── save(audit)  → TX-1

If logUpdate() fails → TX-1 rollback → both user AND audit cancelled

Question 3: When to use REQUIRES_NEW instead of REQUIRED?

REQUIRES_NEW suspends the existing transaction and creates a new independent transaction. Useful when an operation must be committed regardless of the parent transaction's outcome.

PaymentService.javajava
// REQUIRES_NEW: independent transaction
@Service
public class PaymentService {

    private final PaymentRepository paymentRepository;
    private final PaymentAuditService auditService;

    @Transactional
    public void processPayment(Long orderId, BigDecimal amount) {
        Payment payment = new Payment(orderId, amount);
        paymentRepository.save(payment);

        // Audit MUST be persisted even if payment fails later
        auditService.logPaymentAttempt(orderId, amount);

        // Simulating an error after audit
        if (amount.compareTo(BigDecimal.ZERO) < 0) {
            throw new InvalidAmountException("Negative amount");
        }
    }
}

@Service
public class PaymentAuditService {

    private final PaymentAuditRepository auditRepository;

    // REQUIRES_NEW: commits independently of parent transaction
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logPaymentAttempt(Long orderId, BigDecimal amount) {
        // New transaction TX-2 created
        // TX-1 (processPayment) is suspended
        auditRepository.save(new PaymentAudit(orderId, amount, "ATTEMPTED"));
        // TX-2 commits here, independently of TX-1
    }
}

The transactional flow with REQUIRES_NEW:

text
processPayment() starts TX-1
    ├── save(payment)              → TX-1
    ├── logPaymentAttempt()        → TX-1 SUSPENDED
    │       └── starts TX-2        → new transaction
    │       └── save(audit)        → TX-2
    │       └── TX-2 COMMIT        → audit persisted
    │       └── TX-1 RESUMES
    └── throw InvalidAmountException
        └── TX-1 ROLLBACK          → payment cancelled, but audit preserved
Watch out for deadlocks

REQUIRES_NEW can cause deadlocks if the new transaction accesses the same resources locked by the suspended transaction. Avoid using REQUIRES_NEW to modify the same tables as the parent transaction.

Question 4: Explain NESTED propagation and how it differs from REQUIRES_NEW

NESTED creates a savepoint within the current transaction. If the method fails, only modifications since the savepoint are rolled back, not the entire parent transaction.

BatchProcessingService.javajava
// NESTED: savepoint within parent transaction
@Service
public class BatchProcessingService {

    private final ItemRepository itemRepository;
    private final ItemProcessor itemProcessor;

    @Transactional
    public BatchResult processBatch(List<Item> items) {
        BatchResult result = new BatchResult();

        for (Item item : items) {
            try {
                // Each item is processed with a savepoint
                itemProcessor.processItem(item);
                result.addSuccess(item.getId());
            } catch (ProcessingException e) {
                // Rollback only this item, not the entire batch
                result.addFailure(item.getId(), e.getMessage());
            }
        }

        return result; // Commit successful items
    }
}

@Service
public class ItemProcessor {

    private final ItemRepository itemRepository;

    // NESTED: creates a savepoint, partial rollback possible
    @Transactional(propagation = Propagation.NESTED)
    public void processItem(Item item) {
        item.setStatus("PROCESSING");
        itemRepository.save(item);

        // Business validation
        if (!isValid(item)) {
            throw new ProcessingException("Invalid item");
            // Rollback to savepoint → this item only
        }

        item.setStatus("COMPLETED");
        itemRepository.save(item);
    }
}

Comparison of NESTED vs REQUIRES_NEW:

text
NESTED:
├── Uses a savepoint within parent TX
├── On failure → rollback to savepoint
├── If parent TX rolls back → NESTED also rolls back
└── More performant (no new connection)

REQUIRES_NEW:
├── Creates a completely independent transaction
├── On failure → rollback child TX only
├── If parent TX rolls back → child TX ALREADY COMMITTED
└── Requires a new connection
NESTED Support

NESTED propagation requires JDBC savepoint support. Most modern databases (PostgreSQL, MySQL, Oracle) support this. Verify compatibility before use.

Advanced Propagation Types

Question 5: When to use SUPPORTS and NOT_SUPPORTED?

SUPPORTS executes within the existing transaction if present, otherwise without a transaction. NOT_SUPPORTED suspends any existing transaction and executes without a transaction.

ReportingService.javajava
// SUPPORTS: optional transaction
@Service
public class ReportingService {

    private final ReportRepository reportRepository;

    // SUPPORTS: works with or without a transaction
    // Useful for reads that don't need transactional guarantees
    @Transactional(propagation = Propagation.SUPPORTS)
    public Report generateReport(Long reportId) {
        // If called from a @Transactional method → uses its TX
        // If called directly → no transaction (read-only OK)
        return reportRepository.generateComplexReport(reportId);
    }
}

@Service
public class ExternalNotificationService {

    private final ExternalApiClient apiClient;

    // NOT_SUPPORTED: never within a transaction
    // Avoids blocking the TX during a slow external call
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void sendExternalNotification(String message) {
        // Parent TX suspended during the call
        apiClient.send(message); // Potentially slow HTTP call
        // Parent TX resumes after
    }
}
OrderService.javajava
// Combined usage example
@Service
public class OrderService {

    private final OrderRepository orderRepository;
    private final ReportingService reportingService;
    private final ExternalNotificationService notificationService;

    @Transactional
    public void completeOrder(Long orderId) {
        Order order = orderRepository.findById(orderId).orElseThrow();
        order.setStatus("COMPLETED");
        orderRepository.save(order);

        // Report generation in the same TX (SUPPORTS)
        Report report = reportingService.generateReport(orderId);

        // External notification OUTSIDE transaction (NOT_SUPPORTED)
        // Avoids TX timeout if external API is slow
        notificationService.sendExternalNotification(
            "Order " + orderId + " completed"
        );
    }
}

Question 6: Explain MANDATORY and NEVER

MANDATORY requires an existing transaction and throws an exception otherwise. NEVER requires the absence of a transaction and throws an exception if one exists.

AuditService.javajava
// MANDATORY: must be called from within a transaction
@Service
public class AuditService {

    private final AuditRepository auditRepository;

    // MANDATORY: refuses to execute without an existing transaction
    // Guarantees that audit is always atomic with the audited operation
    @Transactional(propagation = Propagation.MANDATORY)
    public void logCriticalAction(String action, Long entityId) {
        // If called without a transaction → IllegalTransactionStateException
        auditRepository.save(new AuditLog(action, entityId, Instant.now()));
    }
}

// CacheService.java
// NEVER: must never be within a transaction
@Service
public class CacheService {

    private final CacheManager cacheManager;

    // NEVER: cache should not participate in transactions
    // Avoids inconsistencies between cache and DB after rollback
    @Transactional(propagation = Propagation.NEVER)
    public void invalidateCache(String cacheKey) {
        // If called from a @Transactional → IllegalTransactionStateException
        cacheManager.getCache("entities").evict(cacheKey);
    }
}
SecurityService.javajava
// Correct usage of MANDATORY
@Service
public class SecurityService {

    private final AuditService auditService;

    @Transactional
    public void changeUserPassword(Long userId, String newPassword) {
        // Sensitive operation...
        updatePassword(userId, newPassword);

        // Audit MUST be in the same transaction
        // MANDATORY enforces this architectural constraint
        auditService.logCriticalAction("PASSWORD_CHANGE", userId);
    }

    // ERROR: direct call without transaction
    public void badUsage() {
        // Throws IllegalTransactionStateException because no TX
        auditService.logCriticalAction("TEST", 1L);
    }
}

Ready to ace your Spring Boot interviews?

Practice with our interactive simulators, flashcards, and technical tests.

Common Interview Pitfalls

Question 7: Why does @Transactional not work on internal calls?

One of the most common pitfalls. Internal method calls (self-invocation) bypass the Spring proxy, disabling transaction management.

BrokenService.javajava
// CLASSIC ERROR: self-invocation
@Service
public class BrokenService {

    private final ItemRepository itemRepository;

    public void processItems(List<Long> itemIds) {
        for (Long id : itemIds) {
            // TRAP: internal call → bypasses the proxy
            // @Transactional on processItem() is IGNORED
            processItem(id);
        }
    }

    @Transactional
    public void processItem(Long itemId) {
        // This transaction is NEVER created during internal calls
        Item item = itemRepository.findById(itemId).orElseThrow();
        item.setStatus("PROCESSED");
        itemRepository.save(item);
    }
}

Solutions to avoid this pitfall:

java
// Solution 1: Self-injection
@Service
public class FixedServiceWithSelfInjection {

    private final ItemRepository itemRepository;

    @Lazy
    @Autowired
    private FixedServiceWithSelfInjection self; // Self-injection

    public void processItems(List<Long> itemIds) {
        for (Long id : itemIds) {
            // Call through proxy → @Transactional works
            self.processItem(id);
        }
    }

    @Transactional
    public void processItem(Long itemId) {
        // Transaction correctly created
        Item item = itemRepository.findById(itemId).orElseThrow();
        item.setStatus("PROCESSED");
        itemRepository.save(item);
    }
}

// Solution 2: Separate into two services (recommended)
@Service
public class ItemOrchestrator {

    private final ItemProcessor processor;

    public void processItems(List<Long> itemIds) {
        for (Long id : itemIds) {
            // Call on another bean → proxy works
            processor.processItem(id);
        }
    }
}

@Service
public class ItemProcessor {

    private final ItemRepository itemRepository;

    @Transactional
    public void processItem(Long itemId) {
        // Transaction correctly managed
        Item item = itemRepository.findById(itemId).orElseThrow();
        item.setStatus("PROCESSED");
        itemRepository.save(item);
    }
}

Question 8: How to handle exceptions and rollback?

By default, Spring only rolls back on RuntimeException and Error. Checked exceptions do NOT trigger automatic rollback.

TransactionRollbackDemo.javajava
// Rollback behavior based on exception type
@Service
public class TransactionRollbackDemo {

    private final OrderRepository orderRepository;

    // RuntimeException → automatic ROLLBACK
    @Transactional
    public void methodWithRuntimeException() {
        orderRepository.save(new Order());
        throw new RuntimeException("Error"); // ROLLBACK
    }

    // Checked Exception → NO rollback by default
    @Transactional
    public void methodWithCheckedException() throws IOException {
        orderRepository.save(new Order());
        throw new IOException("File error"); // COMMITS anyway!
    }

    // Force rollback on checked exception
    @Transactional(rollbackFor = IOException.class)
    public void methodWithRollbackFor() throws IOException {
        orderRepository.save(new Order());
        throw new IOException("Error"); // ROLLBACK thanks to rollbackFor
    }

    // Exclude a RuntimeException from rollback
    @Transactional(noRollbackFor = BusinessException.class)
    public void methodWithNoRollbackFor() {
        orderRepository.save(new Order());
        throw new BusinessException("Warning"); // COMMITS despite exception
    }
}

Recommended configuration for business cases:

BaseTransactionalService.javajava
// Consistent transactional configuration
@Service
public abstract class BaseTransactionalService {

    // Rollback on all exceptions (checked and unchecked)
    @Transactional(rollbackFor = Exception.class)
    protected void executeInTransaction(Runnable operation) {
        operation.run();
    }
}

// PaymentService.java
@Service
public class PaymentService extends BaseTransactionalService {

    private final PaymentRepository paymentRepository;

    @Transactional(
        rollbackFor = Exception.class,
        noRollbackFor = InsufficientFundsException.class
    )
    public PaymentResult processPayment(PaymentRequest request) {
        Payment payment = new Payment(request);
        paymentRepository.save(payment);

        if (request.getAmount().compareTo(getBalance()) > 0) {
            // Does NOT rollback - we want to keep the attempt record
            throw new InsufficientFundsException("Insufficient balance");
        }

        return new PaymentResult(payment.getId(), "SUCCESS");
    }
}

Question 9: How does transaction isolation work with propagation?

Isolation and propagation are complementary. Isolation determines data visibility between concurrent transactions.

IsolationDemo.javajava
// Combining isolation + propagation
@Service
public class IsolationDemo {

    private final AccountRepository accountRepository;

    // READ_COMMITTED: sees data committed by other transactions
    @Transactional(
        isolation = Isolation.READ_COMMITTED,
        propagation = Propagation.REQUIRED
    )
    public BigDecimal getAccountBalance(Long accountId) {
        // May see different values if re-read during the transaction
        return accountRepository.findById(accountId)
            .map(Account::getBalance)
            .orElse(BigDecimal.ZERO);
    }

    // REPEATABLE_READ: guarantees the same read during the transaction
    @Transactional(
        isolation = Isolation.REPEATABLE_READ,
        propagation = Propagation.REQUIRED
    )
    public void transferWithConsistentRead(Long fromId, Long toId, BigDecimal amount) {
        Account from = accountRepository.findById(fromId).orElseThrow();
        Account to = accountRepository.findById(toId).orElseThrow();

        // Even if another transaction modifies 'from' in the meantime,
        // we always see the initial value (snapshot)
        from.debit(amount);
        to.credit(amount);

        accountRepository.save(from);
        accountRepository.save(to);
    }

    // SERIALIZABLE: maximum isolation, no concurrency
    @Transactional(
        isolation = Isolation.SERIALIZABLE,
        propagation = Propagation.REQUIRES_NEW
    )
    public void criticalOperation(Long accountId) {
        // Blocks any other transaction on this data
        // Use sparingly - performance impact
        Account account = accountRepository.findById(accountId).orElseThrow();
        account.performCriticalUpdate();
        accountRepository.save(account);
    }
}

Summary table of isolation levels:

text
| Isolation        | Dirty Read | Non-Repeatable | Phantom |
|------------------|------------|----------------|---------|
| READ_UNCOMMITTED | Possible   | Possible       | Possible|
| READ_COMMITTED   | No         | Possible       | Possible|
| REPEATABLE_READ  | No         | No             | Possible|
| SERIALIZABLE     | No         | No             | No      |
Performance Impact

The stricter the isolation, the more performance can be impacted by locks. SERIALIZABLE can cause significant contention in high-traffic production environments.

Advanced Patterns

Question 10: How to implement the "transactional outbox" pattern?

The outbox pattern guarantees consistency between database modifications and message/event sending, even in case of failure.

OutboxService.javajava
// Transactional Outbox Pattern
@Service
public class OutboxService {

    private final OutboxRepository outboxRepository;

    // Saves the event in the same transaction as the business entity
    @Transactional(propagation = Propagation.MANDATORY)
    public void saveEvent(String aggregateType, Long aggregateId, String eventType, String payload) {
        OutboxEvent event = OutboxEvent.builder()
            .aggregateType(aggregateType)
            .aggregateId(aggregateId)
            .eventType(eventType)
            .payload(payload)
            .status("PENDING")
            .createdAt(Instant.now())
            .build();
        outboxRepository.save(event);
    }
}

@Service
public class OrderService {

    private final OrderRepository orderRepository;
    private final OutboxService outboxService;

    @Transactional
    public Order createOrder(OrderRequest request) {
        // Create the order
        Order order = new Order(request);
        orderRepository.save(order);

        // Outbox event in the SAME transaction (MANDATORY)
        // If commit succeeds → both are persisted
        // If rollback → neither is persisted
        outboxService.saveEvent(
            "ORDER",
            order.getId(),
            "ORDER_CREATED",
            toJson(new OrderCreatedEvent(order))
        );

        return order;
    }
}

// OutboxPublisher.java
// Separate process that publishes events
@Service
public class OutboxPublisher {

    private final OutboxRepository outboxRepository;
    private final MessageBroker messageBroker;

    // Independent transaction for each publication
    @Scheduled(fixedDelay = 1000)
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void publishPendingEvents() {
        List<OutboxEvent> events = outboxRepository
            .findByStatusOrderByCreatedAt("PENDING");

        for (OutboxEvent event : events) {
            try {
                messageBroker.publish(event.getEventType(), event.getPayload());
                event.setStatus("PUBLISHED");
                event.setPublishedAt(Instant.now());
            } catch (Exception e) {
                event.setStatus("FAILED");
                event.setError(e.getMessage());
            }
            outboxRepository.save(event);
        }
    }
}

Question 11: How to test different propagation behaviors?

Propagation tests require special attention to verify expected transactional behavior.

TransactionPropagationTest.javajava
// Testing propagation behaviors
@SpringBootTest
@Transactional
class TransactionPropagationTest {

    @Autowired
    private OrderService orderService;

    @Autowired
    private PaymentService paymentService;

    @Autowired
    private TestEntityManager entityManager;

    @Test
    void required_shouldShareTransaction() {
        // Given
        Order order = orderService.createOrder(new OrderRequest());

        // When - payment in the same transaction (REQUIRED)
        paymentService.processPayment(order.getId(), BigDecimal.TEN);

        // Then - both are visible before commit
        entityManager.flush();
        assertThat(entityManager.find(Order.class, order.getId())).isNotNull();
        assertThat(entityManager.find(Payment.class, order.getId())).isNotNull();
    }

    @Test
    void requiresNew_shouldCommitIndependently() {
        // Given
        Long orderId = null;

        try {
            // When - audit in REQUIRES_NEW
            orderId = orderService.createOrderWithAudit(new OrderRequest());
            throw new RuntimeException("Simulated failure after audit");
        } catch (RuntimeException e) {
            // Main transaction rolls back
        }

        // Then - audit (REQUIRES_NEW) is still committed
        assertThat(findAuditLog(orderId)).isNotNull();
        // But the order is rolled back
        assertThat(findOrder(orderId)).isNull();
    }

    @Test
    void mandatory_shouldThrowWithoutTransaction() {
        // Given - no active transaction

        // When/Then - must throw an exception
        assertThatThrownBy(() -> auditService.logCriticalAction("TEST", 1L))
            .isInstanceOf(IllegalTransactionStateException.class)
            .hasMessageContaining("No existing transaction");
    }
}
PropagationIntegrationTest.javajava
// Integration test with actual rollback and commit
@SpringBootTest
class PropagationIntegrationTest {

    @Autowired
    private OrderService orderService;

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private AuditLogRepository auditLogRepository;

    @Test
    void nested_shouldRollbackOnlyNestedOnFailure() {
        // Given
        int initialCount = orderRepository.findAll().size();

        // When - batch processing with NESTED
        BatchResult result = orderService.processBatchWithNested(
            List.of(validItem(), invalidItem(), validItem())
        );

        // Then - only valid items are persisted
        assertThat(result.getSuccessCount()).isEqualTo(2);
        assertThat(result.getFailureCount()).isEqualTo(1);
        assertThat(orderRepository.findAll().size()).isEqualTo(initialCount + 2);
    }
}

Question 12: What are the best practices for transaction configuration?

Consistent transactional configuration at the project level avoids surprises and facilitates maintenance.

TransactionConfig.javajava
// Centralized transaction configuration
@Configuration
@EnableTransactionManagement
public class TransactionConfig {

    // Default timeout for all transactions
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager tm = new DataSourceTransactionManager(dataSource);
        tm.setDefaultTimeout(30); // 30 seconds max per transaction
        return tm;
    }
}

// BaseService.java
// Default annotations for services
@Service
@Transactional(
    readOnly = true, // Read-only by default
    rollbackFor = Exception.class // Rollback on any exception
)
public abstract class BaseService {
    // Read methods inherit readOnly = true
}

// OrderService.java
// Service with consistent configuration
@Service
public class OrderService extends BaseService {

    private final OrderRepository orderRepository;

    // Inherits readOnly = true
    public Order findById(Long id) {
        return orderRepository.findById(id).orElseThrow();
    }

    // Override for writes
    @Transactional(readOnly = false)
    public Order createOrder(OrderRequest request) {
        return orderRepository.save(new Order(request));
    }

    // Explicit configuration for critical cases
    @Transactional(
        readOnly = false,
        propagation = Propagation.REQUIRES_NEW,
        isolation = Isolation.SERIALIZABLE,
        timeout = 10
    )
    public void criticalOperation(Long orderId) {
        // Operation with maximum isolation and short timeout
    }
}

Best practices checklist:

text
Transaction Configuration - Checklist

✅ readOnly = true by default, explicit override for writes
✅ rollbackFor = Exception.class to include checked exceptions
✅ Appropriate timeout based on operation type
✅ Avoid internal calls (@Transactional ignored)
✅ REQUIRES_NEW only when independent commit is necessary
✅ MANDATORY to guarantee transactional context
✅ Explicit tests for rollback behaviors
✅ Monitor long-running transactions
✅ Document non-standard propagation choices

Ready to ace your Spring Boot interviews?

Practice with our interactive simulators, flashcards, and technical tests.

Conclusion

Transaction propagation is a fundamental concept evaluated in Spring Boot interviews. Key points to remember:

Common propagation types:

  • ✅ REQUIRED (default): joins or creates a transaction
  • ✅ REQUIRES_NEW: independent transaction, separate commit
  • ✅ NESTED: savepoint for partial rollback
  • ✅ MANDATORY: requires an existing transaction

Pitfalls to avoid:

  • ✅ Self-invocation: bypasses proxy, @Transactional ignored
  • ✅ Checked exceptions: no rollback by default
  • ✅ REQUIRES_NEW on same data: deadlock risk
  • ✅ Missing timeout: transactions blocked indefinitely

Best practices:

  • ✅ readOnly = true by default
  • ✅ rollbackFor = Exception.class systematically
  • ✅ Separate services to avoid self-invocation
  • ✅ Explicitly test rollback behaviors

Mastering transaction propagation demonstrates deep understanding of Spring and data management. These concepts are essential for designing robust applications and passing technical interviews successfully.

Start practicing!

Test your knowledge with our interview simulators and technical tests.

Tags

#spring boot
#transactions
#propagation
#java
#interview

Share

Related articles