Spring
Async
Integration Test
No Data Found
Testing Issues

Spring Async - no data found in integration test

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

A common Spring integration-test failure with @Async is asserting database state before background work completes. The application works in production because time passes between request and read, but tests run too quickly and observe empty results. Reliable async testing requires explicit synchronization and correct transaction boundaries.

Why Data Is Missing in Async Tests

@Async methods run on another thread. Your test thread may call repository assertions immediately, before async insert or update commits.

A second cause is transaction scope. If the async method runs in a different thread, it does not share the caller transaction unless explicitly designed. Test-level rollback and async commit timing can create confusing outcomes.

Minimal Async Setup in Spring

First ensure async execution is enabled and executor behavior is explicit.

java
1import org.springframework.context.annotation.Bean;
2import org.springframework.context.annotation.Configuration;
3import org.springframework.scheduling.annotation.EnableAsync;
4import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
5
6import java.util.concurrent.Executor;
7
8@Configuration
9@EnableAsync
10public class AsyncConfig {
11
12    @Bean(name = "appExecutor")
13    public Executor appExecutor() {
14        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
15        executor.setCorePoolSize(2);
16        executor.setMaxPoolSize(4);
17        executor.setQueueCapacity(50);
18        executor.setThreadNamePrefix("app-async-");
19        executor.initialize();
20        return executor;
21    }
22}

Then annotate service methods and return CompletableFuture when callers need completion signals.

java
1import org.springframework.scheduling.annotation.Async;
2import org.springframework.stereotype.Service;
3
4import java.util.concurrent.CompletableFuture;
5
6@Service
7public class OrderService {
8
9    private final OrderRepository orderRepository;
10
11    public OrderService(OrderRepository orderRepository) {
12        this.orderRepository = orderRepository;
13    }
14
15    @Async("appExecutor")
16    public CompletableFuture<Void> createOrderAsync(String id) {
17        orderRepository.save(new OrderEntity(id));
18        return CompletableFuture.completedFuture(null);
19    }
20}

Integration Test Pattern That Waits Correctly

In tests, wait until the async side effect is observable. Awaitility makes this readable.

java
1import static org.awaitility.Awaitility.await;
2import static java.util.concurrent.TimeUnit.SECONDS;
3
4import org.junit.jupiter.api.Test;
5import org.springframework.beans.factory.annotation.Autowired;
6import org.springframework.boot.test.context.SpringBootTest;
7
8@SpringBootTest
9class OrderServiceIT {
10
11    @Autowired
12    private OrderService orderService;
13
14    @Autowired
15    private OrderRepository orderRepository;
16
17    @Test
18    void savesOrderAsynchronously() {
19        orderService.createOrderAsync("A-100");
20
21        await()
22            .atMost(5, SECONDS)
23            .untilAsserted(() ->
24                org.assertj.core.api.Assertions.assertThat(
25                    orderRepository.findById("A-100")
26                ).isPresent()
27            );
28    }
29}

This removes timing flakiness without arbitrary Thread.sleep.

Transaction and Rollback Considerations

Many integration tests use @Transactional and rollback. That can hide data from async threads or roll back before async work finishes. Prefer one of these approaches.

  • Avoid test-level @Transactional for async integration tests.
  • Use cleanup scripts or repository deletes in @AfterEach.
  • If transaction management is required, make transaction boundaries explicit in service code.

If async code performs writes, use clear propagation and commit semantics.

java
1import org.springframework.transaction.annotation.Propagation;
2import org.springframework.transaction.annotation.Transactional;
3
4@Transactional(propagation = Propagation.REQUIRES_NEW)
5public void persistInNewTransaction(OrderEntity order) {
6    orderRepository.save(order);
7}

Use this only when it matches your domain requirements.

Optional Test Profile with Synchronous Executor

Some teams keep true async behavior in a dedicated integration suite and run business-logic tests with a synchronous executor profile. This can reduce flakes while still preserving async coverage where needed.

java
1import org.springframework.boot.test.context.TestConfiguration;
2import org.springframework.context.annotation.Bean;
3import org.springframework.core.task.SyncTaskExecutor;
4import org.springframework.core.task.TaskExecutor;
5
6@TestConfiguration
7class SyncAsyncTestConfig {
8    @Bean(name = "appExecutor")
9    TaskExecutor taskExecutor() {
10        return new SyncTaskExecutor();
11    }
12}

This strategy should not replace at least one suite that verifies real threaded behavior.

Debugging Checklist

When results are missing, verify these first.

  • @EnableAsync is present in active configuration.
  • Async method is invoked through Spring proxy, not self-invocation in same class.
  • Executor bean is used as expected and has available threads.
  • Assertions wait for completion condition.
  • Test transaction setup does not roll back before async completion.

Thread name logging often reveals whether code actually ran on async executor.

Common Pitfalls

  • Calling async method and asserting immediately.
  • Using Thread.sleep with arbitrary delays that fail on slower CI workers.
  • Expecting self-invoked @Async methods to run asynchronously.
  • Running async integration tests inside rolled-back transactions.
  • Sharing mutable state between test thread and async thread without synchronization.

Summary

  • Missing data in async integration tests is usually a timing or transaction issue.
  • Return CompletableFuture or wait with Awaitility to synchronize assertions.
  • Keep async executor configuration explicit and test-visible.
  • Be deliberate with transaction boundaries in async write paths.
  • Prefer deterministic wait conditions over fixed sleep calls.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.