Seven Common Spring Boot Performance Optimization Techniques

This article presents seven practical Spring Boot performance optimization methods—including asynchronous execution, increasing embedded Tomcat connection limits, component scanning, switching to Undertow, using BufferedWriter, DeferredResult, and AsyncHandlerInterceptor—each illustrated with code samples and explanations for improving Java backend applications.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Seven Common Spring Boot Performance Optimization Techniques

Hello everyone, I'm Chen. This article introduces seven common Spring Boot performance optimization directions.

1. Asynchronous Execution

Two implementation ways:

Use the asynchronous annotation @Async and enable it with @EnableAsync on the main class.

Leverage JDK 8's CompletableFuture class.

@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();
        // Simulate a long‑running computation
        Thread.sleep(1000);
        // Notify completion
        future.complete(60);
    }
}

In this example a thread is started; the AskThread object initially lacks the data it needs, so the expression myRe = re.get() * re.get() blocks until the future is completed.

After a simulated 1‑second delay, the result is set on the future, allowing the AskThread to continue.

public class Calc {
    public static Integer calc(Integer para) {
        try {
            // Simulate a long‑running execution
            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 CompletableFuture.supplyAsync method creates a CompletableFuture that runs the supplied task in a new thread and returns immediately, providing a contract for obtaining the result later.

When a return value is needed, CompletableFuture is used; for fire‑and‑forget scenarios, runAsync(Runnable) is preferred, especially when optimizing controllers.

Both methods, if no custom thread pool is specified, execute in the ForkJoinPool.common daemon thread pool, which terminates when the main thread ends.

Core code example:

CompletableFuture.runAsync(() ->
    this.afterBetProcessor(betRequest, betDetailResult, appUser, id)
);

Asynchronous calls can also be implemented with Callable:

@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();
    }
    /**
     * Asynchronous RESTful call. When the controller returns a Callable, Spring MVC hands it to a TaskExecutor.
     */
    @GetMapping("/hello")
    public Callable<String> helloController() {
        logger.info(Thread.currentThread().getName() + " entering helloController method");
        Callable<String> callable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                logger.info(Thread.currentThread().getName() + " entering call method");
                String say = hello.sayHello();
                logger.info(Thread.currentThread().getName() + " returned from helloService method");
                return say;
            }
        };
        logger.info(Thread.currentThread().getName() + " returning from helloController method");
        return callable;
    }
}

Using WebAsyncTask for asynchronous calls with timeout handling:

@RestController
public class HelloController {
    private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
    @Autowired
    private HelloService hello;
    /** Timeout‑aware asynchronous request */
    @GetMapping("/world")
    public WebAsyncTask<String> worldController() {
        logger.info(Thread.currentThread().getName() + " entering helloController method");
        WebAsyncTask<String> webAsyncTask = new WebAsyncTask<>(3000, new Callable<String>() {
            @Override
            public String call() throws Exception {
                logger.info(Thread.currentThread().getName() + " entering call method");
                String say = hello.sayHello();
                logger.info(Thread.currentThread().getName() + " returned from helloService method");
                return say;
            }
        });
        logger.info(Thread.currentThread().getName() + " returning from helloController method");
        webAsyncTask.onCompletion(() -> logger.info(Thread.currentThread().getName() + " completed"));
        webAsyncTask.onTimeout(() -> { throw new TimeoutException("call timeout"); });
        return webAsyncTask;
    }
    /** Asynchronous call with exception handling */
    @GetMapping("/exception")
    public WebAsyncTask<String> exceptionController() {
        logger.info(Thread.currentThread().getName() + " entering helloController method");
        Callable<String> callable = () -> { throw new TimeoutException("call timeout!"); };
        logger.info(Thread.currentThread().getName() + " returning from helloController method");
        return new WebAsyncTask<>(20000, callable);
    }
}

2. Increase Embedded Tomcat Maximum Connections

Configuration example:

@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();
            // Set maximum connections
            protocol.setMaxConnections(20000);
            // Set maximum threads
            protocol.setMaxThreads(2000);
            protocol.setConnectionTimeout(30000);
        }
    }
}

3. Use @ComponentScan()

Applying @ComponentScan() can scan packages faster than using @SpringBootApplication alone.

4. Switch Default Tomcat Container to Undertow

Undertow offers higher throughput (e.g., 8000 vs. 5000 for Tomcat).

<exclusions>
  <exclusion>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
  </exclusion>
</exclusions>

Replace with:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

5. Use BufferedWriter for Buffering

Readers can try this technique on their own; no code example is provided.

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 method");
        DeferredResult<String> deferredResult = new DeferredResult<>();
        // Invoke long‑running task
        taskService.execute(deferredResult);
        logger.info(Thread.currentThread().getName() + " returning from executeSlowTask method");
        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 {
        // Replace original response with custom content
        String resp = "my name is chhliu!";
        response.setContentLength(resp.length());
        response.getOutputStream().write(resp.getBytes());
        logger.info(Thread.currentThread().getName() + " entering afterConcurrentHandlingStarted method");
    }
}

The remainder of the original article contains promotional links and social media calls to action, which are omitted from this technical summary.

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.

JavaPerformance OptimizationAsynchronousSpring BootTomcat
Code Ape Tech Column
Written by

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

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.