Avoid Hidden Spring Boot Pitfalls: Optimize Tomcat, HikariCP, JPA, and More

Spring Boot’s “convention over configuration” hides many default settings that can cause performance bottlenecks and runtime failures, from Tomcat and HikariCP connection limits to JPA lazy loading, timezone serialization, logging, caching, file upload limits, thread pools, and transaction timeouts, and the article shows how to tune each.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Avoid Hidden Spring Boot Pitfalls: Optimize Tomcat, HikariCP, JPA, and More

Spring Boot promotes "convention over configuration", but many defaults are unsuitable for production and can lead to hidden performance problems and runtime errors.

Tomcat Connection Pool

Spring Boot uses Tomcat as the default web container, yet its default connection pool settings become a bottleneck under high concurrency. The default maximum connections and threads are both 200, causing request queuing when traffic exceeds this limit.

server:
  tomcat:
    max-connections: 10000   # maximum connections
    threads:
      max: 800            # maximum worker threads
      min-spare: 100      # minimum idle threads
    accept-count: 100      # queue length
    connection-timeout: 20000

Additionally, the default connection timeout is unlimited, which can keep connections open indefinitely.

Database Connection Pool

Spring Boot defaults to HikariCP for the database pool, but the default maximum pool size of 10 is far too low for most real‑world applications.

spring:
  datasource:
    hikari:
      maximum-pool-size: 50
      minimum-idle: 10
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
      leak-detection-threshold: 60000

Pay special attention to leak-detection-threshold; it is disabled by default, so connection leaks may go unnoticed.

JPA Lazy Loading

When Spring Boot integrates JPA, lazy loading is enabled by default. While useful, it often triggers N+1 query problems.

@Entity
public class User {
    @Id
    private Long id;

    @OneToMany(fetch = FetchType.LAZY) // default is LAZY
    private List<Order> orders;
}

Fetching the orders collection for each user generates a separate SQL query, leading to 101 queries for 100 users. Solutions include using @EntityGraph or a JOIN FETCH query.

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

Jackson Time‑Zone Serialization

Jackson, used by Spring Boot for JSON handling, defaults to the system time‑zone. In distributed deployments this causes inconsistent timestamps.

spring:
  jackson:
    time-zone: GMT+8
    date-format: yyyy-MM-dd HH:mm:ss
    serialization:
      write-dates-as-timestamps: false

If servers run in different zones, the same instant may be serialized differently, which is problematic for internationalized applications.

Log Configuration

Spring Boot ships with Logback, but the default setup lacks log file rotation and cleanup, causing log files to grow indefinitely. The default log level is INFO, which can generate excessive logs in production.

logging:
  file:
    name: app.log
  logback:
    rollingpolicy:
      max-file-size: 100MB
      max-history: 30
      total-size-cap: 3GB

Cache Configuration

The @Cacheable annotation uses a simple ConcurrentHashMap by default, which has no eviction policy or size limit. In high‑traffic scenarios this can lead to unbounded memory growth.

spring:
  cache:
    type: caffeine
    caffeine:
      spec: maximumSize=10000,expireAfterWrite=600s

Monitoring Endpoints

Spring Boot Actuator exposes many endpoints (health, info, metrics, etc.). While useful in development, exposing them in production can leak sensitive information. Only expose necessary endpoints and secure them appropriately.

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
  endpoint:
    health:
      show-details: when-authorized

File Upload Size Limits

The default limits (1 MB per file, 10 MB total request) are often too low for real applications, leading to MaxUploadSizeExceededException errors.

spring:
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB
      file-size-threshold: 2KB
      location: /tmp

The file-size-threshold determines when files are written to disk versus kept in memory; setting it appropriately balances memory usage and I/O performance.

Async Thread‑Pool Configuration

Using @Async defaults to SimpleAsyncTaskExecutor, which creates a new thread for each task, exhausting resources under load. Configure a proper thread pool with sensible core and max sizes.

spring:
  task:
    execution:
      pool:
        core-size: 8
        max-size: 16
        queue-capacity: 100
        keep-alive: 60s
        thread-name-prefix: async-task-
    scheduling:
      pool:
        size: 4
      thread-name-prefix: scheduling-

For CPU‑bound work, thread count should match CPU cores; for I/O‑bound work, a higher multiplier is appropriate.

Static Resource Caching Strategy

Spring Boot does not set HTTP cache headers for static assets by default, causing browsers to re‑download unchanged files on every request.

spring:
  web:
    resources:
      cache:
        cachecontrol:
          max-age: 365d
          cache-public: true
      chain:
        strategy:
          content:
            enabled: true
            paths: /**
      cache: true
      static-locations: classpath:/static/

Enabling content‑based versioning adds a hash to filenames (e.g., style-abc123.css), allowing browsers to cache assets effectively.

Database Transaction Timeout

The @Transactional annotation has no default timeout, so long‑running transactions can hold locks and block other operations.

@Transactional(timeout = 30, rollbackFor = Exception.class)
public void batchProcess(List<Data> dataList) {
    int batchSize = 100;
    for (int i = 0; i < dataList.size(); i += batchSize) {
        List<Data> batch = dataList.subList(i, Math.min(i + batchSize, dataList.size()));
        processBatch(batch);
    }
}

Splitting large jobs into smaller transactions releases locks sooner and improves concurrency; always specify rollbackFor to ensure proper rollback on any exception.

Conclusion

Spring Boot’s “convention over configuration” saves effort but also hides assumptions that may not fit every production scenario. Proactively reviewing and tuning defaults—connection pools, lazy loading, serialization, logging, caching, file limits, thread pools, and transaction settings—prevents many avoidable incidents and leads to more reliable, performant applications.

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.

backendJavaSpring Boot
Architect's Tech Stack
Written by

Architect's Tech Stack

Java backend, microservices, distributed systems, containerized programming, and more.

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.