VOOZH about

URL: https://dev.to/nitish_95c3a169c005f1d4fe/spring-boot-3-and-jvm-resilience-in-api-development-avoiding-javascripts-2014-pitfalls-4dl4

⇱ Spring Boot 3 and JVM Resilience in API Development: Avoiding JavaScript’s 2014 Pitfalls - DEV Community


Spring Boot 3 and JVM Resilience in API Development: Avoiding JavaScript’s 2014 Pitfalls

Without proper thread pooling and structured error handling, your production APIs might face cascading failures under load—like Node.js applications did during JavaScript’s scalability debates in 2014—while Spring Boot’s thread-per-request model remains unmonitored and unoptimized.

Prerequisites

  • Java 17 or later
  • Spring Boot 3.2.4
  • Spring Web and Spring Data JPA dependencies
  • Docker 24.0+ and PostgreSQL 15 image
  • Postman or curl for API testing

Configuring Spring Boot for Concurrent Request Handling

Spring Boot’s default Tomcat configuration handles 100 simultaneous requests, but tuning is critical for high-throughput APIs. Add these dependencies to optimize thread pools and metrics:

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
server.tomcat.threads.max=200
server.tomcat.threads.min-spare=20
management.endpoints.web.exposure.include=health,metrics,threaddump
  • server.tomcat.threads.max: Maximum worker threads for concurrent requests
  • management.endpoints.web.exposure.include: Enables actuator metrics for monitoring thread usage

Implementing Resilient Order Processing with @RestController

This REST controller uses Spring’s thread-per-request model to avoid JavaScript-style event-loop blocking, with explicit error handling for database failures:

package com.example.orderservice;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.dao.DataAccessException;

@RestController
@RequestMapping("/orders")
public class OrderController {

 @PostMapping
 @ResponseStatus(HttpStatus.CREATED)
 public String createOrder(@RequestBody OrderRequest request) {
 try {
 // Simulate database operation
 processOrder(request);
 return "Order processed";
 } catch (DataAccessException e) {
 throw new ServiceUnavailableException("Database write failed");
 }
 }

 private void processOrder(OrderRequest request) {
 // Business logic here
 }
}

@ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
class ServiceUnavailableException extends RuntimeException {
 ServiceUnavailableException(String message) {
 super(message);
 }
}

Test with curl -X POST http://localhost:8080/orders -H "Content-Type: application/json" -d '{"productId": 123}'. Verify thread usage via /actuator/metrics/tomcat.threads.busy.

Dockerizing PostgreSQL for Spring Data JPA

# docker-compose.yml
version: '3.8'
services:
 postgres:
 image: postgres:15-alpine
 environment:
 POSTGRES_DB: orders
 POSTGRES_USER: spring
 POSTGRES_PASSWORD: secure
 ports:
 - "5432:5432"

Run docker-compose up before starting the Spring Boot app to ensure database connectivity.

Common Mistakes

Mistake 1: Blocking the main thread with synchronous I/O

// Wrong: Synchronous HTTP call in controller
String result = restTemplate.getForObject("https://slow-api", String.class);
// Fix: Offload to @Async service
@Async
public CompletableFuture<String> fetchExternalData() {
 return CompletableFuture.completedFuture(restTemplate.getForObject(...));
}

Synchronous calls in controllers exhaust worker threads. Use @Async with a configured task executor.

Mistake 2: Missing retries for transient database errors

// Wrong: No retry for JPA operations
orderRepository.save(order);
// Fix: Spring Retry with exponential backoff
@Retryable(retryFor = DataAccessException.class, maxAttempts = 3)
public void saveOrder(Order order) {
 orderRepository.save(order);
}

Add @EnableRetry and spring-retry dependency to recover from transient database issues.

Mistake 3: Overlooking connection pool limits

# Wrong: Default Hikari pool size
spring.datasource.hikari.maximum-pool-size=10
# Fix: Match pool to thread capacity
spring.datasource.hikari.maximum-pool-size=200

Too few database connections cause thread starvation. Align Hikari pool size to Tomcat’s threads.max.

Summary

  • Configure server.tomcat.threads.max to match expected concurrent users and monitor via /actuator/metrics
  • Use @Async with a custom ThreadPoolTaskExecutor for I/O-bound operations to prevent thread blocking
  • Set spring.datasource.hikari.maximum-pool-size equal to Tomcat threads to avoid database connection starvation

The author publishes Spring Boot starter templates at https://gumroad.com