Seven Common Spring Boot Performance Optimization Techniques
This article presents seven practical Spring Boot performance optimization methods—including asynchronous execution, increasing Tomcat connection limits, component scanning, switching to Undertow, buffered I/O, DeferredResult handling, and AsyncHandlerInterceptor usage—each illustrated with clear code examples and explanations.
Hello everyone, I am Chen. This article introduces seven common Spring Boot performance optimization directions.
1. Asynchronous Execution
Two implementation methods are provided:
Use the asynchronous annotation @Async and add @EnableAsync to the startup class.
Leverage JDK 8's CompletableFuture class.
Example using a custom thread with CompletableFuture:
@AllArgsConstructor
public class AskThread implements Runnable {
private CompletableFuture<Integer> re = null;
public void run() {
int myRe = 0;
try {
myRe = re.get() * re.get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(myRe);
}
public static void main(String[] args) throws InterruptedException {
final CompletableFuture<Integer> future = new CompletableFuture<>();
new Thread(new AskThread(future)).start();
Thread.sleep(1000);
future.complete(60);
}
}In this example, the AskThread starts before its data is ready, causing the call to myRe = re.get() * re.get() to block. After a simulated 1‑second delay, the result is supplied to the future, allowing the thread to continue.
Another example demonstrates CompletableFuture.supplyAsync to run a slow calc() method in a new thread while immediately returning a CompletableFuture that later provides the result:
public class Calc {
public static Integer calc(Integer para) {
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
return para * para;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
final CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> calc(50))
.thenApply(i -> Integer.toString(i))
.thenApply(str -> "\"" + str + "\"")
.thenAccept(System.out::println);
future.get();
}
}The supplyAsync method creates a CompletableFuture that runs calc() in a new thread, returning immediately. The future acts as a contract for obtaining the eventual result.
When a controller method returns a Callable, Spring MVC dispatches it to a TaskExecutor for asynchronous processing, keeping the response open until the callable completes.
@RestController
public class HelloController {
private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
@Autowired private HelloService hello;
@GetMapping("/helloworld")
public String helloWorldController() { return hello.sayHello(); }
@GetMapping("/hello")
public Callable<String> helloController() {
logger.info(Thread.currentThread().getName() + " entering helloController");
Callable<String> callable = new Callable<String>() {
@Override public String call() throws Exception {
logger.info(Thread.currentThread().getName() + " entering call");
String say = hello.sayHello();
logger.info(Thread.currentThread().getName() + " from helloService");
return say;
}
};
logger.info(Thread.currentThread().getName() + " returning from helloController");
return callable;
}
}Asynchronous calls can also be handled with WebAsyncTask, allowing custom timeout handling:
@RestController
public class HelloController {
private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
@Autowired private HelloService hello;
@GetMapping("/world")
public WebAsyncTask<String> worldController() {
logger.info(Thread.currentThread().getName() + " entering helloController");
WebAsyncTask<String> webAsyncTask = new WebAsyncTask<>(3000, new Callable<String>() {
@Override public String call() throws Exception {
logger.info(Thread.currentThread().getName() + " entering call");
String say = hello.sayHello();
logger.info(Thread.currentThread().getName() + " from helloService");
return say;
}
});
webAsyncTask.onCompletion(() -> logger.info(Thread.currentThread().getName() + " completed"));
webAsyncTask.onTimeout(() -> { logger.info(Thread.currentThread().getName() + " onTimeout"); throw new TimeoutException("call timeout"); });
return webAsyncTask;
}
@GetMapping("/exception")
public WebAsyncTask<String> exceptionController() {
Callable<String> callable = () -> { logger.info(Thread.currentThread().getName() + " entering call"); throw new TimeoutException("call timeout!"); };
return new WebAsyncTask<>(20000, callable);
}
}2. Increase Embedded Tomcat Maximum Connections
Configuration example to raise connection and thread limits:
@Configuration
public class TomcatConfig {
@Bean
public ConfigurableServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory tomcatFactory = new TomcatServletWebServerFactory();
tomcatFactory.addConnectorCustomizers(new MyTomcatConnectorCustomizer());
tomcatFactory.setPort(8005);
tomcatFactory.setContextPath("/api-g");
return tomcatFactory;
}
class MyTomcatConnectorCustomizer implements TomcatConnectorCustomizer {
@Override public void customize(Connector connector) {
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
protocol.setMaxConnections(20000);
protocol.setMaxThreads(2000);
protocol.setConnectionTimeout(30000);
}
}
}3. Use @ComponentScan()
Applying @ComponentScan() can scan packages faster than the default @SpringBootApplication annotation.
4. Switch Default Tomcat Container to Undertow
Replace the Tomcat starter with Undertow in Maven:
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>Change to:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>5. Use BufferedWriter for Buffering
Encourage using BufferedWriter to improve I/O efficiency (code omitted for brevity).
6. DeferredResult for Asynchronous Calls
@RestController
public class AsyncDeferredController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final LongTimeTask taskService;
@Autowired public AsyncDeferredController(LongTimeTask taskService) { this.taskService = taskService; }
@GetMapping("/deferred")
public DeferredResult<String> executeSlowTask() {
logger.info(Thread.currentThread().getName() + " entering executeSlowTask");
DeferredResult<String> deferredResult = new DeferredResult<>();
taskService.execute(deferredResult);
logger.info(Thread.currentThread().getName() + " returning from executeSlowTask");
deferredResult.onTimeout(() -> { logger.info(Thread.currentThread().getName() + " onTimeout"); deferredResult.setErrorResult("time out!"); });
deferredResult.onCompletion(() -> logger.info(Thread.currentThread().getName() + " onCompletion"));
return deferredResult;
}
}7. AsyncHandlerInterceptor for Intercepting Asynchronous Calls
@Component
public class MyAsyncHandlerInterceptor implements AsyncHandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(MyAsyncHandlerInterceptor.class);
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; }
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.info(Thread.currentThread().getName() + " service completed, returning result to client");
}
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
if (ex != null) { System.out.println("Exception occurred:" + ex.getMessage()); }
}
@Override public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String resp = "my name is chhliu!";
response.setContentLength(resp.length());
response.getOutputStream().write(resp.getBytes());
logger.info(Thread.currentThread().getName() + " entering afterConcurrentHandlingStarted");
}
}Finally, the article invites readers to join a backend‑focused technical community for knowledge sharing and networking.
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.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.
