Spring Boot 2 - example APR native library configuration with Tomcat 9 (Http11AprProtocol + upgrade to HTTP2)
In this article, we would like to show how in simple way configure APR native library with Spring Boot 2 (Tomcat 9).

Motivation to use APR native libraries is better connection management performance.
Using the below configuration you can:
- use APR native library with Tomcat 9 (under Spring Boot 2 application),
- enable HTTPS (or HTTP),
- enable HTTP2 support,
- enable HTTP/HTTPS conpression (gzip or brotli),
- use only
*.pem
certificate and private key when APR is enabled.
Repository: GutHub
The application was run using:
Run the command:
xxxxxxxxxx
apt-get install libtcnative-1 # or: apt-get install libapr1-dev libssl-dev
# or: apt-get install libapr1.0-dev libssl-dev
- download native libaries (download link 1, download link 2 or direct link)
- extract
tomcat-native-2.0.3-openssl-3.0.8-win32-bin.zip
archive, - paste extracted directory into
C:\Program Files
directory (or:C:\Program Files (x86)
).
It is necessary to add additional configurations to the application.properties
file and paste TomcatAprConfig.java
file into your web application configurations.
application.properties
file:
xxxxxxxxxx
# ...
server.tomcat.apr.enabled=true
server.ssl.enabled=true
server.http2.enabled=true
# Required for Http11AprProtocol with *.pem files (only if server.tomcat.apr.enabled=true)
#
# Use the OpenSSL command to generate own key store:
# openssl.exe req -x509 -newkey rsa:4096 -keyout private_key.pem -out public_certificate.pem -days 365 -nodes -subj '/CN=localhost'
#
#
server.ssl.public-certificate=classpath:certificates/public_certificate.pem
server.ssl.private-key=classpath:certificates/private_key.pem
# Required for Http11NioProtocol with JKS (only if server.tomcat.apr.enabled=false)
#
# Use the Java CLI command to generate own key store:
# keytool -genkey -keyalg RSA -alias my-application -keystore keystore.jks -storepass P@$$word -validity 3650 -keysize 2048
#
#
# server.ssl.key-store=classpath:certificates/keystore.jks
# server.ssl.key-store-password=P@$$word
# Required for Http11NioProtocol with P12 (only if server.tomcat.apr.enabled=false)
#
# Use the OpenSSL command to convert *.pem files to *.p12 file:
# openssl pkcs12 -export -in public_certificate.pem -inkey private_key.pem -out keystore.p12 -name my-application
#
#
# server.ssl.key-store-type=PKCS12
# server.ssl.key-store=classpath:certificates/keystore.p12
# server.ssl.key-store-password=P@$$word
# server.ssl.key-alias=my-application
server.compression.enabled=true
server.compression.min-response-size=2048
server.compression.mime-types=text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml
# ...
TomcatAprConfig.java
file:
xxxxxxxxxx
package com.example.config;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.core.AprLifecycleListener;
import org.apache.coyote.http11.Http11AprProtocol;
import org.apache.coyote.http2.Http2Protocol;
import org.apache.tomcat.util.buf.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.Ssl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
name = "server.tomcat.apr.enabled", havingValue = "true") (
public class TomcatAprConfig {
// properties
"${server.ssl.enabled:#{true}}") (
private Boolean sslEnabled;
"${server.http2.enabled:#{false}}") (
private Boolean http2Enabled;
"${server.ssl.key-store-type:#{null}}") (
private String keyStoreType;
"${server.ssl.key-store:#{null}}") (
private String keyStorePath;
"${server.ssl.key-store-password:#{null}}") (
private String keyStorePassword;
"${server.ssl.key-alias:#{null}}") (
private String keyAlias;
"${server.ssl.public-certificate:#{null}}") (
private String publicCertificatePath;
"${server.ssl.private-key:#{null}}") (
private String privateKeyPath;
"${server.compression.enabled:#{false}}") (
private Boolean compressionEnabled;
"${server.compression.min-response-size:#{2048}}") (
private Integer compressionMinSize;
"${server.compression.mime-types:text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml}") (
private String compressionMimeTypes;
// public methods
name = "tomcatServletWebServerFactory") (
public TomcatServletWebServerFactory createServerFactory(ServerProperties serverProperties, ResourceLoader resourceLoader) {
// This configuration is used to improve client connection performance by using native libraries.
if (this.keyStoreType != null || this.keyStorePath != null || this.keyStorePassword != null || this.keyAlias != null) {
throw new RuntimeException("Tomcat APR configuration can not be uses with server.ssl.key-store-type, server.ssl.key-store, server.ssl.key-store-password and server.ssl.key-alias properties (Use server.ssl.public-certificate and server.ssl.private-key properties with PEM format)");
}
TomcatServletWebServerFactory serverFactory = new TomcatServletWebServerFactory() {
public Ssl getSsl() {
return null; // null returned to stop the default SSL customizer
}
};
serverFactory.setProtocol("org.apache.coyote.http11.Http11AprProtocol"); // the protocol that will enable APR
serverFactory.setContextLifecycleListeners(this.createLifecycleListeners());
serverFactory.setTomcatConnectorCustomizers(this.createConnectorCustomizers(serverProperties, resourceLoader));
return serverFactory;
}
// private methods
private List<AprLifecycleListener> createLifecycleListeners() {
AprLifecycleListener lifecycleListener = new AprLifecycleListener();
return Collections.singletonList(lifecycleListener);
}
private List<TomcatConnectorCustomizer> createConnectorCustomizers(ServerProperties serverProperties, ResourceLoader resourceLoader) {
TomcatConnectorCustomizer connectorCustomizer = tomcatConnector -> {
Http11AprProtocol aprProtocol = (Http11AprProtocol) tomcatConnector.getProtocolHandler();
// we are able to force to disable even server.ssl.* properties are defined
if (this.sslEnabled) {
if (this.publicCertificatePath == null) {
throw new RuntimeException("Public certificate path is not configured.");
}
if (this.privateKeyPath == null) {
throw new RuntimeException("Private key path is not configured.");
}
Ssl connectionSsl = serverProperties.getSsl();
String[] connectionCiphers = connectionSsl.getCiphers();
String[] connectionProtocols = connectionSsl.getEnabledProtocols();
tomcatConnector.setSecure(true);
tomcatConnector.setScheme("https");
aprProtocol.setSSLEnabled(true);
if (connectionProtocols != null && connectionProtocols.length > 0) {
aprProtocol.setSslEnabledProtocols(this.joinStrings(connectionProtocols));
}
if (connectionCiphers != null && connectionCiphers.length > 0) {
aprProtocol.setCiphers(this.joinStrings(connectionCiphers));
}
try {
aprProtocol.setSSLCertificateFile(this.resolvePath(resourceLoader, this.publicCertificatePath));
aprProtocol.setSSLCertificateKeyFile(this.resolvePath(resourceLoader, this.privateKeyPath));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
if (this.compressionEnabled) {
if (this.http2Enabled) {
Http2Protocol http2Protocol = new Http2Protocol();
http2Protocol.setCompression("on");
http2Protocol.setCompressibleMimeType(this.compressionMimeTypes);
http2Protocol.setCompressionMinSize(this.compressionMinSize);
tomcatConnector.addUpgradeProtocol(http2Protocol);
} else {
aprProtocol.setCompression("on");
aprProtocol.setCompressibleMimeType(this.compressionMimeTypes);
aprProtocol.setCompressionMinSize(this.compressionMinSize);
}
} else {
if (this.http2Enabled) {
tomcatConnector.addUpgradeProtocol(new Http2Protocol());
}
}
} else {
tomcatConnector.setSecure(false);
tomcatConnector.setScheme("http");
aprProtocol.setSSLEnabled(false);
if (this.compressionEnabled) {
aprProtocol.setCompression("on");
aprProtocol.setCompressibleMimeType(this.compressionMimeTypes);
aprProtocol.setCompressionMinSize(this.compressionMinSize);
}
}
};
return Collections.singletonList(connectorCustomizer);
}
private String joinStrings(String[] strings) {
List<String> list = Arrays.asList(strings);
return StringUtils.join(list, ',');
}
private String resolvePath(ResourceLoader loader, String path) throws IOException {
Resource resource = loader.getResource(path);
try {
File file = resource.getFile();
return file.getAbsolutePath();
} catch (Exception ex) {
throw new IOException("Absolute '" + path + "' path resolving error.", ex);
}
}
}
Run the command:
xxxxxxxxxx
mvn clean package
Note: consider to use absolute paths to certificates and do not store them inside
*.jar
file.
Depending on operating system we need to indicate where APR libaries are installed.
Run the command:
xxxxxxxxxx
java -Djava.library.path="/usr/lib/x86_64-linux-gnu" -jar my-application.jar
Note: be sure the APR is installed in
/usr/lib/x86_64-linux-gnu
directory.
Run the command:
xxxxxxxxxx
java -Djava.library.path="C:\\Program Files\\tomcat-native-2.0.3-openssl-3.0.8-win32-bin\\bin\\x64" -jar my-application.jar
Note: use
C:\\Program Files\\tomcat-native-2.0.3-openssl-3.0.8-win32-bin\\bin
path under x86.