Avoid Hidden SpringBoot Pitfalls: Optimize Default Settings for Production
This article examines common default configuration issues in SpringBoot—such as Tomcat connection limits, HikariCP pool size, JPA lazy loading, Jackson timezone handling, logging, caching, file upload limits, async execution, and transaction timeouts—and provides practical adjustments to improve performance and reliability in production environments.
Introduction
When SpringBoot first emerged, its "convention over configuration" approach attracted many developers, promising "out‑of‑the‑box" simplicity. However, many default settings hide assumptions that can cause serious problems in real‑world deployments.
Tomcat Connection Pool
SpringBoot uses Tomcat as the default web container, but the default maximum connections and threads are only 200. In high‑concurrency scenarios this quickly becomes a bottleneck.
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: 20000The default timeout is unlimited, causing connections to linger and exhaust resources.
Database Connection Pool
SpringBoot defaults to HikariCP with a maximum pool size of 10, which is insufficient for most production workloads.
spring:
datasource:
hikari:
maximum-pool-size: 50
minimum-idle: 10
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
leak-detection-threshold: 60000Pay special attention to leak-detection-threshold; it is disabled by default, so connection leaks may go unnoticed.
JPA Lazy Loading
SpringBoot enables lazy loading by default, which can lead to N+1 query problems.
@Entity
public class User {
@Id
private Long id;
@OneToMany(fetch = FetchType.LAZY)
private List<Order> orders;
}When fetching a list of users, each access to orders triggers an additional query. Solutions include using @EntityGraph, JOIN FETCH, or adjusting fetch strategies.
Jackson Timezone Serialization
Jackson uses the system timezone by default, causing inconsistent timestamps across distributed deployments.
spring:
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
serialization:
write-dates-as-timestamps: falseExplicitly setting the timezone and disabling timestamp serialization avoids this issue.
Log Configuration
The default Logback configuration lacks file rotation and size limits, leading to ever‑growing log files.
logging:
file:
name: app.log
logback:
rollingpolicy:
max-file-size: 100MB
max-history: 30
total-size-cap: 3GBAdjust log levels to reduce noise in production.
Cache Configuration
The default @Cacheable implementation uses a plain ConcurrentHashMap without expiration or size limits, which can cause memory overflow under load. Switching to Caffeine provides better eviction policies.
spring:
cache:
type: caffeine
caffeine:
spec: maximumSize=10000,expireAfterWrite=600sMonitoring Endpoints
SpringBoot Actuator exposes many endpoints by default, which may leak sensitive information. Expose only necessary endpoints and secure them.
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: when-authorizedFile Upload Limits
The default upload limits (1 MB per file, 10 MB total) are often too low for real applications.
spring:
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
file-size-threshold: 2KB
location: /tmp
resolve-lazily: falseAdjust file-size-threshold appropriately to balance memory and disk usage.
Async Thread Pool
Using @Async without a proper thread pool defaults to SimpleAsyncTaskExecutor, which creates a new thread for each task and can exhaust resources.
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-Configure core and max sizes based on CPU‑bound or I/O‑bound workloads.
Static Resource Caching
SpringBoot does not set HTTP cache headers for static assets by default, causing browsers to re‑download unchanged files.
spring:
web:
resources:
cache:
cachecontrol:
max-age: 365d
cache-public: true
strategy:
content:
enabled: true
paths: /**
cache: true
static-locations: classpath:/static/Enabling content‑based versioning (e.g., style-abc123.css) ensures browsers cache unchanged resources.
Database Transaction Timeout
Without a timeout, long‑running transactions hold database locks, leading to contention.
@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.
Conclusion
SpringBoot’s conventions save development effort but can hide assumptions that don’t fit every production scenario. Proactively reviewing and tuning defaults—connection pools, caching, logging, async execution, and transaction settings—helps avoid costly incidents and ensures a robust, performant system.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
