Starter for building WebFlux applications using Spring Framework's Reactive Web support
—
Spring Boot WebFlux provides comprehensive server configuration supporting multiple embedded servers (Netty, Tomcat, Jetty, Undertow) with extensive customization options for production deployments. The reactive server architecture enables high-concurrency applications with efficient resource utilization.
public interface ReactiveWebServerFactory {
WebServer getWebServer(HttpHandler httpHandler);
}
public interface ConfigurableReactiveWebServerFactory extends ReactiveWebServerFactory {
void setPort(int port);
void setAddress(InetAddress address);
void setErrorPages(Set<? extends ErrorPage> errorPages);
void setSsl(Ssl ssl);
void setSslStoreProvider(SslStoreProvider sslStoreProvider);
void setHttp2(Http2 http2);
void setCompression(Compression compression);
void setServerHeader(String serverHeader);
void setShutdown(Shutdown shutdown);
}
public abstract class AbstractReactiveWebServerFactory implements ConfigurableReactiveWebServerFactory {
public void setPort(int port);
public void setAddress(InetAddress address);
public void setErrorPages(Set<? extends ErrorPage> errorPages);
public void setSsl(Ssl ssl);
public void setSslStoreProvider(SslStoreProvider sslStoreProvider);
public void setHttp2(Http2 http2);
public void setCompression(Compression compression);
public void setServerHeader(String serverHeader);
public void setShutdown(Shutdown shutdown);
protected final void configureWebServer(WebServer webServer, ConfigurableWebServerFactory factory);
}public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFactory
implements ReactiveWebServerFactory, ResourceLoaderAware {
public NettyReactiveWebServerFactory();
public NettyReactiveWebServerFactory(int port);
public void setResourceLoader(ResourceLoader resourceLoader);
public void setUseForwardHeaders(boolean useForwardHeaders);
public void addServerCustomizers(NettyServerCustomizer... customizers);
public void setServerCustomizers(Collection<? extends NettyServerCustomizer> customizers);
public Collection<NettyServerCustomizer> getServerCustomizers();
@Override
public WebServer getWebServer(HttpHandler httpHandler);
}
@FunctionalInterface
public interface NettyServerCustomizer {
HttpServer apply(HttpServer server);
}public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFactory
implements ResourceLoaderAware {
public TomcatReactiveWebServerFactory();
public TomcatReactiveWebServerFactory(int port);
public void setResourceLoader(ResourceLoader resourceLoader);
public void setBaseDirectory(File baseDirectory);
public void setBackgroundProcessorDelay(int delay);
public void addConnectorCustomizers(TomcatConnectorCustomizer... customizers);
public void setConnectorCustomizers(Collection<? extends TomcatConnectorCustomizer> customizers);
public void addContextCustomizers(TomcatContextCustomizer... customizers);
public void setContextCustomizers(Collection<? extends TomcatContextCustomizer> customizers);
public void addProtocolHandlerCustomizers(TomcatProtocolHandlerCustomizer<?>... customizers);
public void setProtocolHandlerCustomizers(Collection<? extends TomcatProtocolHandlerCustomizer<?>> customizers);
@Override
public WebServer getWebServer(HttpHandler httpHandler);
}public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFactory
implements ResourceLoaderAware {
public JettyReactiveWebServerFactory();
public JettyReactiveWebServerFactory(int port);
public void setResourceLoader(ResourceLoader resourceLoader);
public void setUseForwardHeaders(boolean useForwardHeaders);
public void setAcceptors(Integer acceptors);
public void setSelectors(Integer selectors);
public void addServerCustomizers(JettyServerCustomizer... customizers);
public void setServerCustomizers(Collection<? extends JettyServerCustomizer> customizers);
public void setThreadPool(ThreadPool threadPool);
@Override
public WebServer getWebServer(HttpHandler httpHandler);
}
@FunctionalInterface
public interface JettyServerCustomizer {
void customize(Server server);
}public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerFactory
implements ResourceLoaderAware {
public UndertowReactiveWebServerFactory();
public UndertowReactiveWebServerFactory(int port);
public void setResourceLoader(ResourceLoader resourceLoader);
public void setBufferSize(Integer bufferSize);
public void setIoThreads(Integer ioThreads);
public void setWorkerThreads(Integer workerThreads);
public void setDirectBuffers(Boolean directBuffers);
public void setAccessLogEnabled(boolean accessLogEnabled);
public void setAccessLogPattern(String accessLogPattern);
public void setAccessLogPrefix(String accessLogPrefix);
public void setAccessLogSuffix(String accessLogSuffix);
public void setAccessLogDirectory(File accessLogDirectory);
public void setAccessLogWriter(AccessLogWriter accessLogWriter);
public void setUseForwardHeaders(boolean useForwardHeaders);
public void addBuilderCustomizers(UndertowBuilderCustomizer... customizers);
public void setBuilderCustomizers(Collection<? extends UndertowBuilderCustomizer> customizers);
public void addDeploymentInfoCustomizers(UndertowDeploymentInfoCustomizer... customizers);
public void setDeploymentInfoCustomizers(Collection<? extends UndertowDeploymentInfoCustomizer> customizers);
@Override
public WebServer getWebServer(HttpHandler httpHandler);
}@ConfigurationProperties(prefix = "server")
public class ServerProperties {
private Integer port;
private InetAddress address;
private final ErrorProperties error = new ErrorProperties();
private final Ssl ssl = new Ssl();
private final Compression compression = new Compression();
private final Http2 http2 = new Http2();
private String serverHeader;
private Shutdown shutdown = Shutdown.IMMEDIATE;
private final Netty netty = new Netty();
private final Tomcat tomcat = new Tomcat();
private final Jetty jetty = new Jetty();
private final Undertow undertow = new Undertow();
public static class Ssl {
private boolean enabled = true;
private String[] ciphers;
private String[] enabledProtocols;
private String keyAlias;
private String keyPassword;
private String keyStore;
private String keyStorePassword;
private String keyStoreType;
private String keyStoreProvider;
private String trustStore;
private String trustStorePassword;
private String trustStoreType;
private String trustStoreProvider;
private String protocol = "TLS";
private ClientAuth clientAuth;
}
public static class Http2 {
private boolean enabled = false;
}
public static class Compression {
private boolean enabled = false;
private String[] mimeTypes = new String[] { "text/html", "text/xml", "text/plain",
"text/css", "text/javascript", "application/javascript", "application/json",
"application/xml" };
private String[] excludedUserAgents;
private DataSize minResponseSize = DataSize.ofKilobytes(2);
}
}public static class Netty {
private Duration connectionTimeout;
private final H2c h2c = new H2c();
private Duration idleTimeout;
private Integer maxKeepAliveRequests;
public static class H2c {
private boolean enabled = false;
}
}public static class Tomcat {
private Charset uriEncoding = StandardCharsets.UTF_8;
private boolean redirectContextRoot = true;
private boolean useRelativeRedirects;
private int maxConnections = 8192;
private int acceptCount = 100;
private Duration processorCache = Duration.ofSeconds(200);
private int maxHttpHeaderSize = DataSize.ofKilobytes(8).toBytes();
private boolean rejectIllegalHeader = true;
private final Accesslog accesslog = new Accesslog();
private final Threads threads = new Threads();
private final Resource resource = new Resource();
private final Mbeanregistry mbeanregistry = new Mbeanregistry();
public static class Accesslog {
private boolean enabled = false;
private boolean checkExists = false;
private String pattern = "common";
private String directory = "logs";
private String prefix = "access_log";
private String suffix = ".log";
private String encoding;
private String locale;
private boolean requestAttributesEnabled = false;
private boolean rotate = true;
private boolean renameOnRotate = false;
private int maxDays = -1;
private String fileDateFormat = ".yyyy-MM-dd";
private boolean ipv6Canonical = false;
}
}@Configuration
public class SslConfiguration {
@Bean
public TomcatServletWebServerFactory tomcatFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addConnectorCustomizers(connector -> {
connector.setScheme("https");
connector.setSecure(true);
connector.setPort(8443);
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
protocol.setSSLEnabled(true);
protocol.setKeystoreFile("/path/to/keystore.p12");
protocol.setKeystoreType("PKCS12");
protocol.setKeystorePass("password");
protocol.setKeyAlias("tomcat");
});
return factory;
}
@Bean
public NettyReactiveWebServerFactory nettyFactory() {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory();
factory.addServerCustomizers(httpServer -> {
SslContext sslContext = SslContextBuilder.forServer(certificateChainFile, privateKeyFile)
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build();
return httpServer.secure(sslSpec -> sslSpec.sslContext(sslContext));
});
return factory;
}
}@Configuration
public class ProgrammaticSslConfiguration {
@Bean
public ReactiveWebServerFactory webServerFactory() {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory();
Ssl ssl = new Ssl();
ssl.setEnabled(true);
ssl.setKeyStore("classpath:keystore.p12");
ssl.setKeyStorePassword("password");
ssl.setKeyStoreType("PKCS12");
ssl.setKeyAlias("spring");
ssl.setProtocol("TLS");
ssl.setEnabledProtocols(new String[]{"TLSv1.2", "TLSv1.3"});
factory.setSsl(ssl);
factory.setPort(8443);
return factory;
}
}@Configuration
public class NettyPerformanceConfiguration {
@Bean
public NettyReactiveWebServerFactory nettyServerFactory() {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory();
factory.addServerCustomizers(httpServer -> {
return httpServer
.option(ChannelOption.SO_BACKLOG, 1024)
.option(ChannelOption.SO_REUSEADDR, true)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_RCVBUF, 32 * 1024)
.childOption(ChannelOption.SO_SNDBUF, 32 * 1024)
.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK,
new WriteBufferWaterMark(8 * 1024, 32 * 1024))
.handle((inbound, outbound) -> {
// Custom handler logic
return outbound.sendString(Mono.just("Hello World"));
});
});
return factory;
}
}@Configuration
public class ConnectionPoolConfiguration {
@Bean
public ConnectionProvider connectionProvider() {
return ConnectionProvider.builder("custom")
.maxConnections(100)
.maxIdleTime(Duration.ofSeconds(20))
.maxLifeTime(Duration.ofSeconds(60))
.pendingAcquireTimeout(Duration.ofSeconds(60))
.evictInBackground(Duration.ofSeconds(120))
.build();
}
@Bean
public ReactorResourceFactory resourceFactory() {
ReactorResourceFactory factory = new ReactorResourceFactory();
factory.setUseGlobalResources(false);
factory.setConnectionProvider(connectionProvider());
factory.setLoopResources(LoopResources.create("http-nio", 4, true));
return factory;
}
}@Configuration
public class ErrorPageConfiguration {
@Bean
public WebServerFactoryCustomizer<ConfigurableReactiveWebServerFactory> webServerCustomizer() {
return factory -> {
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/error/404");
ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500");
ErrorPage errorPage = new ErrorPage("/error");
factory.setErrorPages(Set.of(error404Page, error500Page, errorPage));
};
}
}@Configuration
public class MultiPortConfiguration {
@Bean
public NettyReactiveWebServerFactory httpServerFactory() {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory();
factory.setPort(8080);
return factory;
}
@Bean
public NettyReactiveWebServerFactory httpsServerFactory() {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory();
factory.setPort(8443);
Ssl ssl = new Ssl();
ssl.setEnabled(true);
ssl.setKeyStore("classpath:keystore.p12");
ssl.setKeyStorePassword("password");
factory.setSsl(ssl);
return factory;
}
@Bean
public TomcatReactiveWebServerFactory additionalTomcatFactory() {
TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory();
factory.setPort(9090);
factory.addConnectorCustomizers(connector -> {
connector.setMaxPostSize(1024 * 1024); // 1MB
connector.setMaxSavePostSize(1024 * 1024);
});
return factory;
}
}@Configuration
public class CompressionConfiguration {
@Bean
public WebServerFactoryCustomizer<ConfigurableReactiveWebServerFactory> compressionCustomizer() {
return factory -> {
Compression compression = new Compression();
compression.setEnabled(true);
compression.setMimeTypes(new String[]{
"text/html", "text/xml", "text/plain", "text/css", "text/javascript",
"application/javascript", "application/json", "application/xml"
});
compression.setMinResponseSize(DataSize.of(1024)); // 1KB minimum
factory.setCompression(compression);
};
}
}@Configuration
public class ServerMonitoringConfiguration {
@Bean
public NettyReactiveWebServerFactory monitoredNettyFactory() {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory();
factory.addServerCustomizers(httpServer -> {
return httpServer
.metrics(true, Function.identity()) // Enable metrics
.doOnConnection(connection -> {
log.info("New connection established: {}",
connection.channel().remoteAddress());
connection.onDispose(() ->
log.info("Connection closed: {}",
connection.channel().remoteAddress()));
})
.accessLog(true) // Enable access logging
.wiretap(true); // Enable wire-level logging
});
return factory;
}
@Bean
public TomcatReactiveWebServerFactory monitoredTomcatFactory() {
TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory();
factory.addConnectorCustomizers(connector -> {
connector.setAttribute("server.tomcat.accesslog.enabled", true);
connector.setAttribute("server.tomcat.accesslog.pattern",
"%h %l %u %t \"%r\" %s %b %D");
});
return factory;
}
}@Configuration
public class GracefulShutdownConfiguration {
@Bean
public WebServerFactoryCustomizer<ConfigurableReactiveWebServerFactory> gracefulShutdownCustomizer() {
return factory -> {
factory.setShutdown(Shutdown.GRACEFUL);
};
}
@Bean
public GracefulShutdown gracefulShutdown() {
return new GracefulShutdown();
}
@EventListener
public void handleContextClose(ContextClosedEvent event) {
log.info("Application context is closing, initiating graceful shutdown");
}
@PreDestroy
public void onDestroy() throws InterruptedException {
log.info("Waiting for active requests to complete...");
Thread.sleep(5000); // Wait 5 seconds for requests to complete
log.info("Graceful shutdown completed");
}
}@RestController
public class HealthController {
private final ReactiveHealthIndicator customHealthIndicator;
public HealthController(ReactiveHealthIndicator customHealthIndicator) {
this.customHealthIndicator = customHealthIndicator;
}
@GetMapping("/health")
public Mono<ResponseEntity<Map<String, Object>>> health() {
return customHealthIndicator.getHealth(true)
.map(health -> {
HttpStatus status = health.getStatus() == Status.UP ?
HttpStatus.OK : HttpStatus.SERVICE_UNAVAILABLE;
return ResponseEntity.status(status).body(health.getDetails());
});
}
@GetMapping("/readiness")
public Mono<ResponseEntity<String>> readiness() {
return Mono.fromCallable(() -> {
// Check if application is ready to serve traffic
boolean ready = checkDatabaseConnection() && checkExternalServices();
return ready ?
ResponseEntity.ok("READY") :
ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("NOT_READY");
});
}
@GetMapping("/liveness")
public Mono<ResponseEntity<String>> liveness() {
return Mono.just(ResponseEntity.ok("ALIVE"));
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-springframework-boot--spring-boot-starter-webflux