Avoid Hidden Performance Landmines: 7 Common Spring Boot Production Pitfalls

The article identifies seven common performance killers in Spring Boot production—N+1 queries, unbounded thread pools, excessive logging, oversized response bodies, missing indexes, synchronous external API calls, and serial API invocations—and provides concrete code‑level solutions such as eager fetching, custom thread pools, lazy logging, DTOs, index creation, timeout configuration, and CompletableFuture parallelism.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Avoid Hidden Performance Landmines: 7 Common Spring Boot Production Pitfalls

In daily development, writing code that merely runs is easy, but achieving high performance is difficult. When traffic spikes, hidden performance "landmines" can cause the system to freeze or crash.

1. N+1 Query Problem

This issue appears with JPA/Hibernate relationships. Fetching all users and then accessing each user's orders triggers a separate SELECT per user, resulting in 1 + N queries.

@Entity
public class User {
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private List<Order> orders = new ArrayList<>();
}

@Entity
public class Order {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
}

Example usage that produces the N+1 queries:

List<User> users = userRepository.findAll();
for (User user : users) {
    System.out.println(user.getOrders().size());
}

Generated SQL (for 100 users) includes one query for users and 100 separate queries for orders, overloading the database and slowing the API.

Solutions:

@Query("SELECT u FROM User u JOIN FETCH u.orders")
List<User> findAllUsers();

@EntityGraph(attributePaths = {"orders"})
List<User> findAll();

2. Default Thread Pool Misconfiguration

Enabling async with @EnableAsync and @Async uses SimpleAsyncTaskExecutor, an unbounded thread pool. Under high concurrency this leads to excessive memory usage, CPU spikes, and possible server crashes.

Custom thread pool recommendation:

@Bean
public Executor executor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(10);
    executor.setMaxPoolSize(50);
    executor.setQueueCapacity(100);
    return executor;
}

3. Excessive Logging in Production

Logging statements execute even when the log level is disabled, wasting CPU. Example of a costly log statement:

log.info("User data {}", objectMapper.writeValueAsString(user));

Better approaches:

log.debug("User Id {}", user.getId());
log.atDebug().addArgument(() -> objectMapper.writeValueAsString(user)).log("User full:{}");

4. Large Response Body

Returning full entity objects serializes unnecessary fields, increasing serialization time, network bandwidth, and response latency.

Use DTOs to return only required data:

public record UserDTO(Long id, String name) {}

return users.stream()
    .map(u -> new UserDTO(u.getId(), u.getName()))
    .toList();

Enable GZIP compression to reduce payload size:

server:
  compression:
    enabled: true
    min-response-size: 1024
    mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml

5. Missing Database Indexes

Querying by a column without an index forces a full table scan. With millions of rows, performance degrades sharply.

Create an index and examine the execution plan:

CREATE INDEX idx_email ON users(email);
EXPLAIN ANALYZE SELECT * FROM users WHERE email='[email protected]';

6. Synchronous External API Calls

Calling external services without timeout blocks threads when the service is slow. Configure timeouts for RestTemplate or WebClient:

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
    return builder
        .connectTimeout(Duration.ofSeconds(3))
        .readTimeout(Duration.ofSeconds(5))
        .build();
}

// WebClient example (recommended)
WebClient.builder().clientConnector(
    new ReactorClientHttpConnector(HttpClient.create().responseTimeout(Duration.ofSeconds(3))))
    .build();

7. Serial Calls to Multiple External APIs

Calling several external APIs sequentially adds up their latencies, increasing overall response time.

Serial example:

public String serialQuery() throws InterruptedException {
    long start = System.currentTimeMillis();
    String resA = callApiA();
    String resB = callApiB();
    String resC = callApiC();
    long cost = System.currentTimeMillis() - start;
    return String.format("串行结果:%s、%s、%s,耗时:%dms", resA, resB, resC, cost);
}

Parallel version using CompletableFuture:

public String parallelQuery() {
    long start = System.currentTimeMillis();
    CompletableFuture<String> futureA = CompletableFuture.supplyAsync(this::callApiA);
    CompletableFuture<String> futureB = CompletableFuture.supplyAsync(this::callApiB);
    CompletableFuture<String> futureC = CompletableFuture.supplyAsync(this::callApiC);
    CompletableFuture.allOf(futureA, futureB, futureC).join();
    String resA = futureA.join();
    String resB = futureB.join();
    String resC = futureC.join();
    long cost = System.currentTimeMillis() - start;
    return String.format("并行结果:%s、%s、%s,耗时:%dms", resA, resB, resC, cost);
}

Parallel execution shortens overall RT, increases server throughput, and improves CPU utilization. Use a dedicated thread pool for CompletableFuture to avoid exhausting the common ForkJoinPool.

Addressing these seven pitfalls—optimizing queries, configuring bounded thread pools, reducing logging overhead, limiting response payloads, adding appropriate indexes, setting request timeouts, and parallelizing external calls—helps Spring Boot applications avoid performance landmines and remain stable in production environments.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

PerformanceDTOCompletableFutureLoggingSpring BootresttemplateThreadPoolTaskExecutorN+1 query
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.