A REST API that works correctly but responds slowly is often more damaging than an API that fails immediately. Users expect modern applications to respond within milliseconds, and even a delay of a few seconds can lead to poor user experience, abandoned requests, and increased infrastructure costs.
In Spring Boot applications, slow API responses are rarely caused by a single issue. Instead, they are usually the result of multiple bottlenecks such as inefficient database queries, blocking I/O operations, excessive object serialization, poor thread management, unnecessary network calls, or incorrect application configuration.
The challenge is that the client only notices the symptom—a slow response—while the actual bottleneck may be hidden several layers deeper in the application.
In this guide, you’ll learn how to systematically identify and eliminate performance bottlenecks in Spring Boot REST APIs. We’ll examine the complete request lifecycle, analyse common production issues, explore monitoring tools, and implement proven optimisation techniques used in real-world applications.
Prerequisites
To get the most from this guide, you should be familiar with:
- Java fundamentals
- Spring Boot basics
- Spring MVC and REST APIs
- Spring Data JPA
- SQL fundamentals
If you’re new to these topics, read these articles first:
- REST API Basics in Spring Boot (Production Ready Guide)
- Request Lifecycle in Spring Boot – From Client to Response
- Dependency Injection in Spring Boot – Production Grade Explanation
- How Spring Boot Creates Beans – Bean Lifecycle Simplified
- How to Enable Debug Logging in Spring Boot for Production Issues
Learning Objectives
After reading this guide, you’ll be able to:
- Identify the root cause of slow REST API responses.
- Measure application performance using Spring Boot Actuator.
- Optimise database queries and connection pools.
- Reduce response latency through caching and asynchronous processing.
- Configure thread pools effectively.
- Monitor JVM and application metrics.
- Apply production-ready performance tuning techniques.
Table of Contents
- Understanding API Response Time
- The Complete Request Lifecycle
- Common Causes of Slow REST APIs
- Database Performance Issues
- N+1 Query Problem
- Thread Pool Exhaustion
- Blocking Operations
- Large JSON Responses
- External API Bottlenecks
- Monitoring and Profiling
- Performance Optimisation Techniques
- Production Checklist
- FAQs
What Is a Slow REST API?
A REST API is considered slow when it consistently takes longer than expected to process client requests.
While acceptable response times vary depending on the use case, a common guideline is:
| Response Time | User Experience |
|---|---|
| < 100 ms | Excellent |
| 100–300 ms | Very Good |
| 300–1000 ms | Acceptable |
| 1–3 seconds | Slow |
| > 3 seconds | Poor |
| > 10 seconds | Timeout Risk |
It’s important to distinguish between occasional slow requests and consistently high latency. Occasional delays may be caused by garbage collection or temporary load spikes, whereas consistently slow responses usually indicate an architectural or implementation issue.
Understanding the Request Lifecycle
Before optimising performance, you need to understand where time is spent during request processing.
Client
│
DispatcherServlet
│
Controller
│
Service
│
Repository
│
Database
│
External Services (Optional)
│
JSON Serialization
│
HTTP Response
A delay at any stage contributes to the total response time.
For a detailed explanation of each step, refer to Request Lifecycle in Spring Boot – From Client to Response.
Common Causes of Slow REST APIs
In production environments, the most common performance bottlenecks include:
- Slow database queries.
- N+1 query problems.
- Connection pool exhaustion.
- Blocking synchronous operations.
- Excessive object serialization.
- Large response payloads.
- Repeated external API calls.
- Thread pool saturation.
- Inefficient business logic.
- Excessive logging.
- Garbage collection pauses.
- Poor application configuration.
Rather than guessing, the goal is to measure each stage and identify the actual bottleneck before making changes.
1. Slow Database Queries
In most production applications, the database is the primary bottleneck. Developers often optimise Java code while overlooking inefficient SQL queries, missing indexes, or unnecessary database round trips.
Consider the following service method:
@GetMapping("/users")
public List<User> getUsers() {
return userRepository.findAll();
}
At first glance, this appears harmless. However, if the User table contains millions of records, retrieving everything at once can consume excessive memory, increase response time, and overload the database.
Symptoms
- API response time gradually increases.
- High database CPU utilisation.
- Slow SQL execution.
- Increased application memory usage.
- HTTP request timeouts during peak traffic.
How to Identify Slow Queries
Enable SQL logging temporarily while troubleshooting:
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.orm.jdbc.bind=TRACE
Analyse execution time using your database’s query analysis tools:
- MySQL –
EXPLAIN - PostgreSQL –
EXPLAIN ANALYZE - Oracle – Execution Plans
- SQL Server – Query Execution Plan
For example:
EXPLAIN
SELECT *
FROM users
WHERE email = 'john@example.com';
If the execution plan shows a full table scan instead of an index lookup, you’ve likely found a performance bottleneck.
Best Practices
- Select only required columns.
- Create indexes for frequently searched fields.
- Avoid
SELECT *in production. - Implement pagination for large datasets.
- Cache frequently accessed reference data.
Related Reading
- How to Fix Spring Boot JPA Table Not Created Automatically
- REST API Basics in Spring Boot
2. The N+1 Query Problem
The N+1 Query Problem is one of the most common performance issues in Spring Boot applications using JPA and Hibernate.
Consider these entities:
@Entity
public class Department {
@OneToMany(mappedBy = "department")
private List<Employee> employees;
}
@Entity
public class Employee {
@ManyToOne
private Department department;
}
Now suppose you retrieve all departments:
departmentRepository.findAll();
Hibernate may execute:
1 Query → Load Departments
↓
100 Queries → Load Employees
↓
Total = 101 Queries
Even though your code executed a single repository method, the database processed 101 SQL statements.
Why It’s Dangerous
The issue may not appear during development with small datasets, but in production it can:
- Increase response time dramatically.
- Overload the database.
- Consume unnecessary network bandwidth.
- Reduce application throughput.
Solutions
- Use
JOIN FETCHwhere appropriate. - Use Entity Graphs.
- Return DTO projections instead of entities.
- Review generated SQL during development.
Example:
@Query("""
SELECT d
FROM Department d
JOIN FETCH d.employees
""")
List<Department> findAllDepartments();
Always validate that optimisation reduces the number of SQL statements rather than assuming it has.
3. Missing Database Indexes
Even well-written queries become slow without proper indexing.
Example:
SELECT *
FROM orders
WHERE customer_id = 1001;
Without an index, the database scans every row.
With an index:
CREATE INDEX idx_customer
ON orders(customer_id);
The database can locate matching rows much faster.
Fields Commonly Indexed
- Username
- Customer ID
- Order Number
- Status
- Created Date
Avoid adding indexes indiscriminately, as they also increase write overhead. Profile your workload before adding them.
4. Connection Pool Exhaustion
Spring Boot uses HikariCP as the default connection pool because it offers excellent performance.
A typical configuration might look like:
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
If every connection is busy, new requests must wait for a free connection.
Logs often show:
HikariPool-1
Connection is not available
Request timed out after 30000ms
Common Causes
- Long-running SQL queries.
- Transactions that remain open unnecessarily.
- Connection leaks.
- Pool size too small for production traffic.
Recommendations
- Keep transactions short.
- Close resources promptly.
- Monitor active and idle connections using Actuator.
- Increase pool size only after identifying the bottleneck.
5. Blocking External API Calls
Many APIs depend on third-party services.
Example:
@GetMapping("/payment")
public PaymentResponse pay() {
return paymentClient.process();
}
If the payment service takes five seconds to respond, your API also waits five seconds.
Production Symptoms
- Slow but otherwise healthy application.
- High response time despite low CPU usage.
- Increased request timeouts.
Best Practices
Configure timeouts explicitly.
spring.web.client.connect-timeout=3s
spring.web.client.read-timeout=5s
Implement resilience patterns such as:
- Retry
- Circuit Breaker
- Bulkhead
- Fallback responses
These techniques prevent downstream failures from cascading through your application.
6. Excessive Logging
Logging is essential, but excessive synchronous logging can become a bottleneck.
Avoid:
log.info("User Details {}", user);
if user contains a large object graph or sensitive information.
Also avoid writing thousands of log entries inside loops.
Instead:
- Log only useful diagnostic information.
- Use asynchronous appenders where appropriate.
- Adjust log levels for production (
INFOorWARNby default). - Enable
DEBUGonly during troubleshooting.
This reduces I/O overhead and keeps logs meaningful.
7. Thread Pool Exhaustion
Spring Boot processes incoming HTTP requests using a limited number of server threads. When these threads become occupied with long-running tasks, new requests are forced to wait, resulting in increased response times or request timeouts.
A common misconception is that high CPU usage is always the cause of slow APIs. In many production systems, CPU utilisation remains low while request latency increases because all request-handling threads are blocked.
How It Happens
Imagine your application receives 300 concurrent requests, but Tomcat has only 200 request-processing threads.
Client Requests (300)
│
▼
Tomcat Thread Pool (200 Threads)
│
├── 200 Requests Processing
└── 100 Requests Waiting
Those waiting requests experience higher latency even though the server still has available CPU resources.
Default Tomcat Configuration
server.tomcat.threads.max=200
server.tomcat.accept-count=100
These values are suitable for many applications but should not be increased blindly.
Incorrect Approach
Many developers simply increase:
server.tomcat.threads.max=500
This often makes performance worse by increasing:
- Context switching
- Memory consumption
- Database contention
- Lock contention
Instead, identify why threads remain busy.
8. Blocking Operations
Blocking operations are one of the biggest causes of poor REST API performance.
Example:
@GetMapping("/report")
public Report generateReport() throws InterruptedException {
Thread.sleep(5000);
return reportService.generate();
}
Every request now occupies one Tomcat thread for five seconds.
With hundreds of concurrent requests, the application quickly runs out of available threads.
Common blocking operations include:
- Slow database queries
- External REST calls
- File processing
- PDF generation
- Email sending
- Large Excel exports
Better Solution
Move long-running work to asynchronous processing.
Example:
@Async
public CompletableFuture<Report> generateReport() {
return CompletableFuture.completedFuture(report);
}
Use asynchronous processing only when the business workflow supports it.
9. Large JSON Responses
Returning unnecessary data significantly increases response time.
Poor example:
@GetMapping("/users")
public List<User> getUsers(){
return repository.findAll();
}
If the User entity contains dozens of fields and nested relationships, every request serialises a large object graph.
Example response:
User
↓
Orders
↓
Payments
↓
Invoices
↓
Addresses
↓
Audit History
↓
Roles
A single API call can easily produce several megabytes of JSON.
Better Approach
Return lightweight DTOs.
public record UserSummary(
Long id,
String name,
String email
){}
Benefits:
- Smaller payloads
- Faster serialization
- Lower bandwidth usage
- Better frontend performance
This also improves security by avoiding accidental exposure of internal fields.
10. Missing Pagination
Returning every record is rarely acceptable in production.
Avoid:
return repository.findAll();
Instead:
Page<User> users =
repository.findAll(PageRequest.of(0,20));
Advantages:
- Faster responses
- Reduced memory usage
- Lower database load
- Better user experience
Large enterprise APIs almost always implement pagination.
11. Caching Frequently Accessed Data
Not every request requires a database query.
Suppose your application frequently loads country codes.
Without caching:
Request
↓
Database
↓
Response
Repeated thousands of times.
With caching:
First Request
↓
Database
↓
Cache
↓
Subsequent Requests
↓
Cache
↓
Response
Spring Boot makes this straightforward.
@Cacheable("countries")
public List<Country> getCountries(){
return repository.findAll();
}
Supported cache providers include:
- Caffeine
- Redis
- Ehcache
- Hazelcast
Caching can dramatically reduce response time for read-heavy endpoints.
12. Inefficient Business Logic
Not every performance issue originates from the database.
Example:
for(User user : users){
calculateSalary(user);
generateReport(user);
sendEmail(user);
}
If each method performs expensive processing, overall latency increases significantly.
Recommendations:
- Avoid repeated calculations.
- Cache intermediate results where appropriate.
- Use efficient algorithms and data structures.
- Profile the code to identify hotspots.
Optimising Java logic can sometimes provide greater gains than tuning SQL.
13. Monitoring with Spring Boot Actuator
Performance tuning without metrics is guesswork.
Enable Spring Boot Actuator to gain visibility into your application’s behaviour.
Useful endpoints include:
/actuator/health
/actuator/metrics
/actuator/prometheus
/actuator/threaddump
/actuator/heapdump
/actuator/mappings
Particularly useful metrics:
http.server.requests
↓
Response Time
↓
Request Count
↓
95th Percentile
↓
99th Percentile
These metrics help identify:
- Slow endpoints
- High error rates
- Traffic spikes
- Thread pool saturation
- Memory pressure
14. JVM Memory and Garbage Collection
Excessive object creation can increase garbage collection frequency.
Symptoms include:
- Periodic response spikes
- CPU spikes
- Long GC pauses
- Increased latency
Monitor:
- Heap usage
- Old Generation
- Young Generation
- GC pause duration
Using:
- Java Flight Recorder
- VisualVM
- JConsole
- Prometheus + Grafana
Reducing unnecessary object allocation often improves throughput.
15. Real Production Case Study
Slow Customer Search API
Symptoms
A customer search endpoint consistently required six to eight seconds to respond during business hours.
Investigation
Monitoring showed:
- CPU utilisation: 28%
- Memory usage: Normal
- Thread count: Normal
- Database CPU: 92%
SQL analysis revealed that the query filtered millions of records without an index.
SELECT *
FROM customers
WHERE mobile_number = ?
The mobile_number column was not indexed.
Solution
An index was created:
CREATE INDEX idx_mobile_number
ON customers(mobile_number);
Result
| Metric | Before | After |
|---|---|---|
| Average Response Time | 6.4 s | 95 ms |
| Database CPU | 92% | 38% |
| API Throughput | Low | High |
Lesson: Always profile the database before tuning Java code. A single missing index can have a far greater impact than application-level optimisations.
Performance Tuning Checklist
When a REST API becomes slow in production, avoid randomly changing configurations. Instead, work through the following checklist systematically.
Application Layer
✅ Enable Spring Boot Actuator.
✅ Identify the slow endpoint.
✅ Measure average response time.
✅ Measure the 95th and 99th percentile latency.
✅ Check application logs for long-running requests.
✅ Review thread dumps during peak load.
Database Layer
✅ Analyse slow SQL queries.
✅ Check query execution plans.
✅ Verify indexes.
✅ Identify N+1 query issues.
✅ Enable SQL logging temporarily.
✅ Monitor connection pool usage.
JVM Layer
✅ Check heap utilisation.
✅ Monitor garbage collection frequency.
✅ Review CPU usage.
✅ Check thread count.
✅ Verify memory leaks.
Infrastructure Layer
✅ Verify network latency.
✅ Monitor API Gateway.
✅ Check load balancer health.
✅ Review container resource limits.
✅ Verify Kubernetes resource allocation.
External Dependencies
✅ Check third-party API latency.
✅ Configure request timeouts.
✅ Implement retries.
✅ Configure Circuit Breakers.
✅ Monitor downstream failures.
Decision Tree for Diagnosing Slow REST APIs
Instead of guessing, follow this troubleshooting flow whenever an API becomes slow.
Slow REST API
│
┌──────────────┴──────────────┐
│ │
Is CPU High? CPU Normal?
│ │
Check Threads Check Database
│ │
Thread Pool Full? Slow SQL Queries?
│ │
Increase Capacity? Missing Indexes?
│ │
Long Running Tasks? N+1 Queries?
│ │
External API Calls? Connection Pool?
│ │
Fix Root Cause and Re-Test
This structured approach avoids unnecessary configuration changes and helps isolate the actual bottleneck.
Docker Performance Considerations
Many Spring Boot applications perform well locally but become slow after being containerised.
Common causes include:
- Insufficient CPU allocation.
- Low memory limits.
- Shared Docker storage.
- Container resource contention.
- Missing JVM container awareness.
Example Docker configuration:
services:
app:
image: springboot-app
deploy:
resources:
limits:
cpus: "2"
memory: 2G
Best Practices
- Allocate sufficient CPU and memory.
- Avoid running database and application in the same constrained container.
- Monitor container CPU throttling.
- Use lightweight base images.
Kubernetes Performance Considerations
Running Spring Boot on Kubernetes introduces additional factors affecting response time.
Common issues:
- Frequent Pod restarts.
- CPU throttling.
- Low memory requests.
- High network latency between services.
- Excessive Horizontal Pod Autoscaler scaling delay.
Example resource configuration:
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2"
memory: "2Gi"
Monitor:
- Pod CPU usage
- Memory usage
- Restart count
- Node utilisation
- Network latency
Performance Monitoring Tools
A production-grade Spring Boot application should always be monitored.
Recommended tools:
| Tool | Purpose |
|---|---|
| Spring Boot Actuator | Application metrics |
| Micrometer | Metrics collection |
| Prometheus | Time-series metrics |
| Grafana | Dashboards |
| Java Flight Recorder | JVM profiling |
| VisualVM | Local profiling |
| JProfiler | Memory and CPU analysis |
| YourKit | Advanced profiling |
These tools provide visibility into:
- Slow endpoints
- Memory leaks
- Thread contention
- Database latency
- Garbage collection
- HTTP response times
Common Mistakes
The following mistakes frequently lead to poor API performance:
| Mistake | Why It’s a Problem | Better Approach |
|---|---|---|
| Returning entire entities | Large JSON payloads | Use DTOs |
findAll() without pagination | High memory usage | Use Pageable |
| Missing database indexes | Full table scans | Add indexes based on query patterns |
| Calling external APIs synchronously | Blocks request threads | Configure timeouts and retries |
| Excessive logging | Increased I/O overhead | Log only useful information |
| Creating objects inside loops | Higher GC pressure | Reuse objects where possible |
| Ignoring Actuator metrics | Reactive troubleshooting | Proactive monitoring |
Interview Questions
1. What is the most common cause of slow Spring Boot REST APIs?
In production, the most common causes are inefficient database queries, missing indexes, N+1 query problems, and blocking external service calls.
2. How can you identify the slowest endpoint in a Spring Boot application?
Use Spring Boot Actuator together with Micrometer and Prometheus to monitor http.server.requests metrics and identify endpoints with high latency.
3. What is the N+1 Query Problem?
The N+1 Query Problem occurs when one query retrieves parent entities and additional queries are executed for each related child entity, dramatically increasing database calls and response time.
4. Why shouldn’t Tomcat thread limits simply be increased?
Increasing the thread count without addressing the underlying bottleneck can increase context switching, memory usage, and database contention, often reducing overall performance.
5. How does caching improve API performance?
Caching stores frequently accessed data in memory, reducing repeated database queries and significantly lowering response times for read-heavy operations.
Frequently Asked Questions
Why is my Spring Boot API fast locally but slow in production?
Local environments typically have small datasets, minimal concurrent users, and low network latency. Production systems deal with larger databases, higher traffic, external dependencies, and infrastructure constraints, exposing performance bottlenecks that aren’t visible during development.
How can I measure API response time?
Use Spring Boot Actuator with Micrometer to collect metrics, then visualise them in Prometheus and Grafana. These tools provide average response times as well as 95th and 99th percentile latency.
Is increasing the HikariCP connection pool size always a good solution?
No. Increasing the pool size without investigating the root cause can overwhelm the database. Always analyse slow queries and connection usage before changing pool settings.
Should I return JPA entities directly from REST controllers?
It’s generally better to return DTOs. DTOs reduce payload size, avoid lazy-loading issues, improve security, and give you better control over the API contract.
Can excessive logging slow down a Spring Boot application?
Yes. Writing large volumes of synchronous log entries, especially inside loops or high-traffic endpoints, can increase I/O overhead and negatively affect response time.
Related Articles
To build a complete understanding of Spring Boot performance and troubleshooting, continue with these articles on SpringBootFixes:
- REST API Basics in Spring Boot (Production Ready Guide)
- Request Lifecycle in Spring Boot – From Client to Response
- How to Enable Debug Logging in Spring Boot for Production Issues
- BeanCreationException in Spring Boot – Common Causes and Production Fixes
- Dependency Injection in Spring Boot – Production Grade Explanation
- How Spring Boot Creates Beans – Bean Lifecycle Simplified
- Spring Boot Configuration Loading Order
- Configuration and Bean in Spring Boot
- How to Fix 500 Internal Server Error in Spring Boot
- Fix Request Timeout in Spring Boot REST APIs
Official References
For deeper technical information, refer to the official documentation:
- Spring Boot Reference Documentation: https://docs.spring.io/spring-boot/reference/
- Spring Framework Documentation: https://docs.spring.io/spring-framework/reference/
- Micrometer Documentation: https://micrometer.io/
- Prometheus Documentation: https://prometheus.io/docs/
- Grafana Documentation: https://grafana.com/docs/
Conclusion
A slow REST API is rarely caused by a single issue. Performance problems usually result from a combination of inefficient database access, excessive object serialization, blocking I/O, poor thread management, missing indexes, or unoptimised application logic.
Rather than making random configuration changes, adopt a data-driven approach. Measure response times, analyse SQL queries, monitor JVM metrics, inspect thread pools, and use tools such as Spring Boot Actuator, Micrometer, Prometheus, and Grafana to identify the real bottleneck.
By applying the techniques covered in this guide—optimising database queries, preventing N+1 problems, implementing caching, tuning connection pools, using asynchronous processing where appropriate, and continuously monitoring production metrics—you can build Spring Boot REST APIs that remain fast, scalable, and reliable even under heavy production workloads.
