How to Fix “Web Application [ROOT] Failed to Stop Threads” in Spring WebFlux

How to Fix "Web Application [ROOT] Failed to Stop Threads" in Spring WebFlux

If you’re using Spring WebFlux and encountering shutdown warnings like this:

WARN 24696 --- [on(2)-127.0.0.1] o.a.c.loader.WebappClassLoaderBase :
The web application [ROOT] started thread [reactor-http-epoll-1] but failed to stop it.
This is very likely to create a memory leak.

You have a Netty thread shutdown issue, which can lead to memory leaks. This guide will help you resolve it permanently.


Why This Happens

Spring WebFlux relies on Netty for non-blocking I/O. When running your application:

  1. Netty creates event loop threads (reactor-http-epoll-*) to manage requests.
  2. If these threads aren’t explicitly closed during shutdown, they linger, causing memory leaks.

Common Causes:

  • Improper use of multiple WebClient instances.
  • Missing Netty resource cleanup during application shutdown.

Step-by-Step Fix

1. Remove Spring MVC Conflicts

Ensure spring-boot-starter-web is not included in pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Remove spring-boot-starter-web -->

2. Use a Singleton WebClient

Instead of instantiating multiple WebClient objects, define them as Spring beans:

WebClient Configuration:

@Configuration
public class WebClientConfig {

    @Bean
    public WebClient pokemonWebClient() {
        return WebClient.builder()
            .baseUrl("https://pokeapi.co")
            .build();
    }

    @Bean
    public WebClient catWebClient() {
        return WebClient.builder()
            .baseUrl("https://cataas.com")
            .build();
    }
}

Service Implementation:

@Service
public class WebClientService {

    private final WebClient pokemonWebClient;
    private final WebClient catWebClient;

    public WebClientService(
        @Qualifier("pokemonWebClient") WebClient pokemonWebClient,
        @Qualifier("catWebClient") WebClient catWebClient
    ) {
        this.pokemonWebClient = pokemonWebClient;
        this.catWebClient = catWebClient;
    }

    public Mono<String> callApis() {
        return Mono.zip(callPokemonApi(), callCatApi())
            .map(tuple -> tuple.getT1() + " *** " + tuple.getT2());
    }

    private Mono<String> callPokemonApi() {
        return pokemonWebClient.get()
            .uri("/api/v2/pokemon/salamence")
            .retrieve()
            .bodyToMono(String.class);
    }

    private Mono<String> callCatApi() {
        return catWebClient.get()
            .uri("/api/cats")
            .retrieve()
            .bodyToMono(String.class);
    }
}

3. Gracefully Shut Down Netty Threads

Explicitly close Netty’s HttpClient during shutdown:

Netty Shutdown Configuration:

import org.springframework.context.annotation.Configuration;
import javax.annotation.PreDestroy;
import reactor.netty.http.client.HttpClient;

@Configuration
public class NettyShutdownConfig {

    private final HttpClient httpClient;

    public NettyShutdownConfig(
        @Qualifier("pokemonWebClientHttpClient") HttpClient pokemonClient,
        @Qualifier("catWebClientHttpClient") HttpClient catClient
    ) {
        this.httpClient = HttpClient.merge(pokemonClient, catClient);
    }

    @PreDestroy
    public void shutdown() {
        httpClient.dispose();
    }
}

Modify WebClientConfig to Expose HttpClient:

@Bean
public HttpClient pokemonWebClientHttpClient() {
    return HttpClient.create();
}

@Bean
public WebClient pokemonWebClient(HttpClient httpClient) {
    return WebClient.builder()
        .baseUrl("https://pokeapi.co")
        .clientConnector(new ReactorClientHttpConnector(httpClient))
        .build();
}

4. Verify Dependencies

Ensure your dependencies are up-to-date:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.9</version>
</parent>

Why This Works

  • Singleton WebClient: Prevents excessive resource usage and improves efficiency.
  • Explicit Netty Shutdown: Ensures event loop threads are properly closed, avoiding leaks.
  • Dependency Cleanup: Eliminates conflicts between MVC and WebFlux.

Key Takeaways

Use a singleton WebClient to avoid redundant resource allocation.
Shut down Netty’s HttpClient explicitly with @PreDestroy.
Remove unnecessary dependencies that may cause conflicts.

By following these best practices, you can eliminate thread-related warnings and ensure a smooth shutdown for your Spring WebFlux application.


Meta Description: Learn how to fix Spring WebFlux thread leaks with a step-by-step guide. Discover how to properly shut down Netty threads, prevent memory leaks, and optimize WebClient usage.

Tags: Spring WebFlux, Netty thread leak, WebClient shutdown, memory leak fix, reactive programming, Spring Boot, Netty HttpClient, async threads

Keywords: Spring WebFlux stop threads, Netty graceful shutdown, WebClient memory leak, fix reactor-http-epoll warning, Spring Boot WebFlux Netty, dispose HttpClient, Spring reactive thread management

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *