75. Embedded Web Servers

Each Spring Boot web application includes an embedded web server. This feature leads to a number of how-to questions, including how to change the embedded server and how to configure the embedded server. This section answers those questions.

75.1 Use Another Web Server

Many Spring Boot starters include default embedded containers. spring-boot-starter-web includes Tomcat by including spring-boot-starter-tomcat, but you can use spring-boot-starter-jetty or spring-boot-starter-undertow instead. spring-boot-starter-webflux includes Reactor Netty by including spring-boot-starter-reactor-netty, but you can use spring-boot-starter-tomcat, spring-boot-starter-jetty, or spring-boot-starter-undertow instead.

If you need to use a different HTTP server, you need to exclude the default dependencies and include the one you need. Spring Boot provides separate starters for HTTP servers to help make this process as easy as possible.

The following Maven example shows how to exclude Tomcat and include Jetty for Spring MVC:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
	<exclusions>
		<!-- Exclude the Tomcat dependency -->
		<exclusion>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
		</exclusion>
	</exclusions>
</dependency>
<!-- Use Jetty instead -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

The following Gradle example shows how to exclude Netty and include Undertow for Spring WebFlux:

configurations {
	// exclude Reactor Netty
	compile.exclude module: 'spring-boot-starter-reactor-netty'
}

dependencies {
	compile 'org.springframework.boot:spring-boot-starter-webflux'
	// Use Undertow instead
	compile 'org.springframework.boot:spring-boot-starter-undertow'
	// ...
}
[Note]Note

spring-boot-starter-reactor-netty is required to use the WebClient class, so you may need to keep a dependency on Netty even when you need to include a different HTTP server.

75.2 Disabling the Web Server

If your classpath contains the necessary bits to start a web server, Spring Boot will automatically start it. To disable this behaviour configure the WebApplicationType in your application.properties, as shown in the following example:

spring.main.web-application-type=none

75.3 Configure Jetty

Generally, you can follow the advice from “Section 74.8, “Discover Built-in Options for External Properties”” about @ConfigurationProperties (ServerProperties is the main one here). However, you should also look at WebServerFactoryCustomizer. The Jetty APIs are quite rich, so, once you have access to the JettyServletWebServerFactory, you can modify it in a number of ways. Alternatively, if you need more control and customization, you can add your own JettyServletWebServerFactory.

75.4 Add a Servlet, Filter, or Listener to an Application

There are two ways to add Servlet, Filter, ServletContextListener, and the other listeners supported by the Servlet spec to your application:

75.4.1 Add a Servlet, Filter, or Listener by Using a Spring Bean

To add a Servlet, Filter, or Servlet *Listener by using a Spring bean, you must provide a @Bean definition for it. Doing so can be very useful when you want to inject configuration or dependencies. However, you must be very careful that they do not cause eager initialization of too many other beans, because they have to be installed in the container very early in the application lifecycle. (For example, it is not a good idea to have them depend on your DataSource or JPA configuration.) You can work around such restrictions by initializing the beans lazily when first used instead of on initialization.

In the case of Filters and Servlets, you can also add mappings and init parameters by adding a FilterRegistrationBean or a ServletRegistrationBean instead of or in addition to the underlying component.

[Note]Note

If no dispatcherType is specified on a filter registration, REQUEST is used. This aligns with the Servlet Specification’s default dispatcher type.

Like any other Spring bean, you can define the order of Servlet filter beans; please make sure to check the “the section called “Registering Servlets, Filters, and Listeners as Spring Beans”” section.

Disable Registration of a Servlet or Filter

As described earlier, any Servlet or Filter beans are registered with the servlet container automatically. To disable registration of a particular Filter or Servlet bean, create a registration bean for it and mark it as disabled, as shown in the following example:

@Bean
public FilterRegistrationBean registration(MyFilter filter) {
	FilterRegistrationBean registration = new FilterRegistrationBean(filter);
	registration.setEnabled(false);
	return registration;
}

75.4.2 Add Servlets, Filters, and Listeners by Using Classpath Scanning

@WebServlet, @WebFilter, and @WebListener annotated classes can be automatically registered with an embedded servlet container by annotating a @Configuration class with @ServletComponentScan and specifying the package(s) containing the components that you want to register. By default, @ServletComponentScan scans from the package of the annotated class.

75.5 Change the HTTP Port

In a standalone application, the main HTTP port defaults to 8080 but can be set with server.port (for example, in application.properties or as a System property). Thanks to relaxed binding of Environment values, you can also use SERVER_PORT (for example, as an OS environment variable).

To switch off the HTTP endpoints completely but still create a WebApplicationContext, use server.port=-1. (Doing so is sometimes useful for testing.)

For more details, see “Section 27.4.4, “Customizing Embedded Servlet Containers”” in the ‘Spring Boot features’ section, or the ServerProperties source code.

75.6 Use a Random Unassigned HTTP Port

To scan for a free port (using OS natives to prevent clashes) use server.port=0.

75.7 Discover the HTTP Port at Runtime

You can access the port the server is running on from log output or from the ServletWebServerApplicationContext through its WebServer. The best way to get that and be sure that it has been initialized is to add a @Bean of type ApplicationListener<ServletWebServerInitializedEvent> and pull the container out of the event when it is published.

Tests that use @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) can also inject the actual port into a field by using the @LocalServerPort annotation, as shown in the following example:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
public class MyWebIntegrationTests {

	@Autowired
	ServletWebServerApplicationContext server;

	@LocalServerPort
	int port;

	// ...

}
[Note]Note

@LocalServerPort is a meta-annotation for @Value("${local.server.port}"). Do not try to inject the port in a regular application. As we just saw, the value is set only after the container has been initialized. Contrary to a test, application code callbacks are processed early (before the value is actually available).

75.8 Configure SSL

SSL can be configured declaratively by setting the various server.ssl.* properties, typically in application.properties or application.yml. The following example shows setting SSL properties in application.properties:

server.port=8443
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=secret
server.ssl.key-password=another-secret

See Ssl for details of all of the supported properties.

Using configuration such as the preceding example means the application no longer supports a plain HTTP connector at port 8080. Spring Boot does not support the configuration of both an HTTP connector and an HTTPS connector through application.properties. If you want to have both, you need to configure one of them programmatically. We recommend using application.properties to configure HTTPS, as the HTTP connector is the easier of the two to configure programmatically. See the spring-boot-sample-tomcat-multi-connectors sample project for an example.

75.9 Configure HTTP/2

You can enable HTTP/2 support in your Spring Boot application with the server.http2.enabled configuration property. This support depends on the chosen web server and the application environment, since that protocol is not supported out-of-the-box by JDK8.

[Note]Note

Spring Boot does not support h2c, the cleartext version of the HTTP/2 protocol. So you must configure SSL first.

75.9.1 HTTP/2 with Undertow

As of Undertow 1.4.0+, HTTP/2 is supported without any additional requirement on JDK8.

75.9.2 HTTP/2 with Jetty

As of Jetty 9.4.8, HTTP/2 is also supported with the Conscrypt library. To enable that support, your application needs to have two additional dependencies: org.eclipse.jetty:jetty-alpn-conscrypt-server and org.eclipse.jetty.http2:http2-server.

75.9.3 HTTP/2 with Tomcat

Spring Boot ships by default with Tomcat 8.5.x. With that version, HTTP/2 is only supported if the libtcnative library and its dependencies are installed on the host operating system.

The library folder must be made available, if not already, to the JVM library path. You can do so with a JVM argument such as -Djava.library.path=/usr/local/opt/tomcat-native/lib. More on this in the official Tomcat documentation.

Starting Tomcat 8.5.x without that native support logs the following error:

ERROR 8787 --- [           main] o.a.coyote.http11.Http11NioProtocol      : The upgrade handler [org.apache.coyote.http2.Http2Protocol] for [h2] only supports upgrade via ALPN but has been configured for the ["https-jsse-nio-8443"] connector that does not support ALPN.

This error is not fatal, and the application still starts with HTTP/1.1 SSL support.

Running your application with Tomcat 9.0.x and JDK9 does not require any native library to be installed. To use Tomcat 9, you can override the tomcat.version build property with the version of your choice.

75.10 Configure Access Logging

Access logs can be configured for Tomcat, Undertow, and Jetty through their respective namespaces.

For instance, the following settings log access on Tomcat with a custom pattern.

server.tomcat.basedir=my-tomcat
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%t %a "%r" %s (%D ms)
[Note]Note

The default location for logs is a logs directory relative to the Tomcat base directory. By default, the logs directory is a temporary directory, so you may want to fix Tomcat’s base directory or use an absolute path for the logs. In the preceding example, the logs are available in my-tomcat/logs relative to the working directory of the application.

Access logging for Undertow can be configured in a similar fashion, as shown in the following example:

server.undertow.accesslog.enabled=true
server.undertow.accesslog.pattern=%t %a "%r" %s (%D ms)

Logs are stored in a logs directory relative to the working directory of the application. You can customize this location by setting the server.undertow.accesslog.directory property.

Finally, access logging for Jetty can also be configured as follows:

server.jetty.accesslog.enabled=true
server.jetty.accesslog.filename=/var/log/jetty-access.log

By default, logs are redirected to System.err. For more details, see the Jetty documentation.

75.11 Running Behind a Front-end Proxy Server

Your application might need to send 302 redirects or render content with absolute links back to itself. When running behind a proxy, the caller wants a link to the proxy and not to the physical address of the machine hosting your app. Typically, such situations are handled through a contract with the proxy, which adds headers to tell the back end how to construct links to itself.

If the proxy adds conventional X-Forwarded-For and X-Forwarded-Proto headers (most proxy servers do so), the absolute links should be rendered correctly, provided server.use-forward-headers is set to true in your application.properties.

[Note]Note

If your application runs in Cloud Foundry or Heroku, the server.use-forward-headers property defaults to true. In all other instances, it defaults to false.

75.11.1 Customize Tomcat’s Proxy Configuration

If you use Tomcat, you can additionally configure the names of the headers used to carry “forwarded” information, as shown in the following example:

server.tomcat.remote-ip-header=x-your-remote-ip-header
server.tomcat.protocol-header=x-your-protocol-header

Tomcat is also configured with a default regular expression that matches internal proxies that are to be trusted. By default, IP addresses in 10/8, 192.168/16, 169.254/16 and 127/8 are trusted. You can customize the valve’s configuration by adding an entry to application.properties, as shown in the following example:

server.tomcat.internal-proxies=192\\.168\\.\\d{1,3}\\.\\d{1,3}
[Note]Note

The double backslashes are required only when you use a properties file for configuration. If you use YAML, single backslashes are sufficient, and a value equivalent to that shown in the preceding example would be 192\.168\.\d{1,3}\.\d{1,3}.

[Note]Note

You can trust all proxies by setting the internal-proxies to empty (but do not do so in production).

You can take complete control of the configuration of Tomcat’s RemoteIpValve by switching the automatic one off (to do so, set server.use-forward-headers=false) and adding a new valve instance in a TomcatServletWebServerFactory bean.

75.12 Configure Tomcat

Generally, you can follow the advice from “Section 74.8, “Discover Built-in Options for External Properties”” about @ConfigurationProperties (ServerProperties is the main one here). However, you should also look at WebServerFactoryCustomizer and various Tomcat-specific *Customizers that you can add. The Tomcat APIs are quite rich. Consequently, once you have access to the TomcatServletWebServerFactory, you can modify it in a number of ways. Alternatively, if you need more control and customization, you can add your own TomcatServletWebServerFactory.

75.13 Enable Multiple Connectors with Tomcat

You can add an org.apache.catalina.connector.Connector to the TomcatServletWebServerFactory, which can allow multiple connectors, including HTTP and HTTPS connectors, as shown in the following example:

@Bean
public ServletWebServerFactory servletContainer() {
	TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
	tomcat.addAdditionalTomcatConnectors(createSslConnector());
	return tomcat;
}

private Connector createSslConnector() {
	Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
	Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
	try {
		File keystore = new ClassPathResource("keystore").getFile();
		File truststore = new ClassPathResource("keystore").getFile();
		connector.setScheme("https");
		connector.setSecure(true);
		connector.setPort(8443);
		protocol.setSSLEnabled(true);
		protocol.setKeystoreFile(keystore.getAbsolutePath());
		protocol.setKeystorePass("changeit");
		protocol.setTruststoreFile(truststore.getAbsolutePath());
		protocol.setTruststorePass("changeit");
		protocol.setKeyAlias("apitester");
		return connector;
	}
	catch (IOException ex) {
		throw new IllegalStateException("can't access keystore: [" + "keystore"
				+ "] or truststore: [" + "keystore" + "]", ex);
	}
}

75.14 Use Tomcat’s LegacyCookieProcessor

By default, the embedded Tomcat used by Spring Boot does not support "Version 0" of the Cookie format, so you may see the following error:

java.lang.IllegalArgumentException: An invalid character [32] was present in the Cookie value

If at all possible, you should consider updating your code to only store values compliant with later Cookie specifications. If, however, you cannot change the way that cookies are written, you can instead configure Tomcat to use a LegacyCookieProcessor. To switch to the LegacyCookieProcessor, use an WebServerFactoryCustomizer bean that adds a TomcatContextCustomizer, as shown in the following example:

@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> cookieProcessorCustomizer() {
	return (factory) -> factory.addContextCustomizers(
			(context) -> context.setCookieProcessor(new LegacyCookieProcessor()));
}

75.15 Configure Undertow

Generally you can follow the advice from “Section 74.8, “Discover Built-in Options for External Properties”” about @ConfigurationProperties (ServerProperties and ServerProperties.Undertow are the main ones here). However, you should also look at WebServerFactoryCustomizer. Once you have access to the UndertowServletWebServerFactory, you can use an UndertowBuilderCustomizer to modify Undertow’s configuration to meet your needs. Alternatively, if you need more control and customization, you can add your own UndertowServletWebServerFactory.

75.16 Enable Multiple Listeners with Undertow

Add an UndertowBuilderCustomizer to the UndertowServletWebServerFactory and add a listener to the Builder, as shown in the following example:

@Bean
public UndertowServletWebServerFactory servletWebServerFactory() {
	UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
	factory.addBuilderCustomizers(new UndertowBuilderCustomizer() {

		@Override
		public void customize(Builder builder) {
			builder.addHttpListener(8080, "0.0.0.0");
		}

	});
	return factory;
}

75.17 Create WebSocket Endpoints Using @ServerEndpoint

If you want to use @ServerEndpoint in a Spring Boot application that used an embedded container, you must declare a single ServerEndpointExporter @Bean, as shown in the following example:

@Bean
public ServerEndpointExporter serverEndpointExporter() {
	return new ServerEndpointExporter();
}

The bean shown in the preceding example registers any @ServerEndpoint annotated beans with the underlying WebSocket container. When deployed to a standalone servlet container, this role is performed by a servlet container initializer, and the ServerEndpointExporter bean is not required.

75.18 Enable HTTP Response Compression

HTTP response compression is supported by Jetty, Tomcat, and Undertow. It can be enabled in application.properties, as follows:

server.compression.enabled=true

By default, responses must be at least 2048 bytes in length for compression to be performed. You can configure this behavior by setting the server.compression.min-response-size property.

By default, responses are compressed only if their content type is one of the following:

  • text/html
  • text/xml
  • text/plain
  • text/css

You can configure this behavior by setting the server.compression.mime-types property.