Skip to content

Commit 7fcf34e

Browse files
committed
Add property 'server.tomcat.use-apr' to control Tomcat's APR
The property's default depends on the Java version. On Java < 24, it defaults to WHEN_AVAILABLE, on Java >=24 it defaults to NEVER. Closes gh-44033
1 parent 59ddba1 commit 7fcf34e

File tree

10 files changed

+180
-47
lines changed

10 files changed

+180
-47
lines changed

Diff for: spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java

+42-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@
3434
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
3535
import org.springframework.boot.context.properties.NestedConfigurationProperty;
3636
import org.springframework.boot.convert.DurationUnit;
37+
import org.springframework.boot.system.JavaVersion;
3738
import org.springframework.boot.web.server.Compression;
3839
import org.springframework.boot.web.server.Cookie;
3940
import org.springframework.boot.web.server.Http2;
@@ -513,6 +514,12 @@ public static class Tomcat {
513514
*/
514515
private DataSize maxHttpResponseHeaderSize = DataSize.ofKilobytes(8);
515516

517+
/**
518+
* Whether to use APR. If running on Java below 24, the default value is
519+
* 'WHEN_AVAILABLE'. On Java 24 or later, the default value is 'NEVER'.
520+
*/
521+
private UseApr useApr;
522+
516523
public DataSize getMaxHttpFormPostSize() {
517524
return this.maxHttpFormPostSize;
518525
}
@@ -669,6 +676,18 @@ public void setMaxHttpResponseHeaderSize(DataSize maxHttpResponseHeaderSize) {
669676
this.maxHttpResponseHeaderSize = maxHttpResponseHeaderSize;
670677
}
671678

679+
public UseApr getUseApr() {
680+
if (this.useApr == null) {
681+
return JavaVersion.getJavaVersion().isEqualOrNewerThan(JavaVersion.TWENTY_FOUR) ? UseApr.NEVER
682+
: UseApr.WHEN_AVAILABLE;
683+
}
684+
return this.useApr;
685+
}
686+
687+
public void setUseApr(UseApr useApr) {
688+
this.useApr = useApr;
689+
}
690+
672691
/**
673692
* Tomcat access log properties.
674693
*/
@@ -1918,4 +1937,26 @@ public enum ForwardHeadersStrategy {
19181937

19191938
}
19201939

1940+
/**
1941+
* When to use APR.
1942+
*/
1943+
public enum UseApr {
1944+
1945+
/**
1946+
* Always use APR and fail if it's not available.
1947+
*/
1948+
ALWAYS,
1949+
1950+
/**
1951+
* Use APR if it is available.
1952+
*/
1953+
WHEN_AVAILABLE,
1954+
1955+
/**
1956+
* Never user APR.
1957+
*/
1958+
NEVER
1959+
1960+
}
1961+
19211962
}

Diff for: spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/TomcatReactiveWebServerFactoryCustomizer.java

+27-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,9 +16,14 @@
1616

1717
package org.springframework.boot.autoconfigure.web.reactive;
1818

19+
import org.apache.catalina.core.AprLifecycleListener;
20+
1921
import org.springframework.boot.autoconfigure.web.ServerProperties;
22+
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat;
23+
import org.springframework.boot.autoconfigure.web.ServerProperties.UseApr;
2024
import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory;
2125
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
26+
import org.springframework.util.Assert;
2227

2328
/**
2429
* {@link WebServerFactoryCustomizer} to apply {@link ServerProperties} to Tomcat reactive
@@ -38,7 +43,27 @@ public TomcatReactiveWebServerFactoryCustomizer(ServerProperties serverPropertie
3843

3944
@Override
4045
public void customize(TomcatReactiveWebServerFactory factory) {
41-
factory.setDisableMBeanRegistry(!this.serverProperties.getTomcat().getMbeanregistry().isEnabled());
46+
Tomcat tomcatProperties = this.serverProperties.getTomcat();
47+
factory.setDisableMBeanRegistry(!tomcatProperties.getMbeanregistry().isEnabled());
48+
factory.setUseApr(getUseApr(tomcatProperties.getUseApr()));
49+
}
50+
51+
private boolean getUseApr(UseApr useApr) {
52+
return switch (useApr) {
53+
case ALWAYS -> {
54+
Assert.state(isAprAvailable(), "APR has been configured to 'ALWAYS', but it's not available");
55+
yield true;
56+
}
57+
case WHEN_AVAILABLE -> isAprAvailable();
58+
case NEVER -> false;
59+
};
60+
}
61+
62+
private boolean isAprAvailable() {
63+
// At least one instance of AprLifecycleListener has to be created for
64+
// isAprAvailable() to work
65+
new AprLifecycleListener();
66+
return AprLifecycleListener.isAprAvailable();
4267
}
4368

4469
}

Diff for: spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/TomcatServletWebServerFactoryCustomizer.java

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,11 +16,15 @@
1616

1717
package org.springframework.boot.autoconfigure.web.servlet;
1818

19+
import org.apache.catalina.core.AprLifecycleListener;
20+
1921
import org.springframework.boot.autoconfigure.web.ServerProperties;
22+
import org.springframework.boot.autoconfigure.web.ServerProperties.UseApr;
2023
import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory;
2124
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
2225
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
2326
import org.springframework.core.Ordered;
27+
import org.springframework.util.Assert;
2428
import org.springframework.util.ObjectUtils;
2529

2630
/**
@@ -56,6 +60,7 @@ public void customize(TomcatServletWebServerFactory factory) {
5660
}
5761
customizeUseRelativeRedirects(factory, tomcatProperties.isUseRelativeRedirects());
5862
factory.setDisableMBeanRegistry(!tomcatProperties.getMbeanregistry().isEnabled());
63+
factory.setUseApr(getUseApr(tomcatProperties.getUseApr()));
5964
}
6065

6166
private void customizeRedirectContextRoot(ConfigurableTomcatWebServerFactory factory, boolean redirectContextRoot) {
@@ -67,4 +72,22 @@ private void customizeUseRelativeRedirects(ConfigurableTomcatWebServerFactory fa
6772
factory.addContextCustomizers((context) -> context.setUseRelativeRedirects(useRelativeRedirects));
6873
}
6974

75+
private boolean getUseApr(UseApr useApr) {
76+
return switch (useApr) {
77+
case ALWAYS -> {
78+
Assert.state(isAprAvailable(), "APR has been configured to 'ALWAYS', but it's not available");
79+
yield true;
80+
}
81+
case WHEN_AVAILABLE -> isAprAvailable();
82+
case NEVER -> false;
83+
};
84+
}
85+
86+
private boolean isAprAvailable() {
87+
// At least one instance of AprLifecycleListener has to be created for
88+
// isAprAvailable() to work
89+
new AprLifecycleListener();
90+
return AprLifecycleListener.isAprAvailable();
91+
}
92+
7093
}

Diff for: spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -35,9 +35,12 @@
3535
import org.eclipse.jetty.server.Server;
3636
import org.eclipse.jetty.util.thread.QueuedThreadPool;
3737
import org.junit.jupiter.api.Test;
38+
import org.junit.jupiter.api.condition.EnabledForJreRange;
39+
import org.junit.jupiter.api.condition.JRE;
3840
import reactor.netty.http.HttpDecoderSpec;
3941

4042
import org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.Accesslog;
43+
import org.springframework.boot.autoconfigure.web.ServerProperties.UseApr;
4144
import org.springframework.boot.context.properties.bind.Bindable;
4245
import org.springframework.boot.context.properties.bind.Binder;
4346
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
@@ -69,6 +72,7 @@
6972
* @author Chris Bono
7073
* @author Parviz Rozikov
7174
* @author Lasse Wulff
75+
* @author Moritz Halbritter
7276
*/
7377
@DirtiesUrlFactories
7478
class ServerPropertiesTests {
@@ -494,6 +498,18 @@ void nettyInitialBufferSizeMatchesHttpDecoderSpecDefault() {
494498
.isEqualTo(HttpDecoderSpec.DEFAULT_INITIAL_BUFFER_SIZE);
495499
}
496500

501+
@Test
502+
@EnabledForJreRange(max = JRE.JAVA_23)
503+
void shouldDefaultAprToWhenAvailableUntilJava23() {
504+
assertThat(this.properties.getTomcat().getUseApr()).isEqualTo(UseApr.WHEN_AVAILABLE);
505+
}
506+
507+
@Test
508+
@EnabledForJreRange(min = JRE.JAVA_24)
509+
void shouldDefaultAprToNeverOnJava24AndLater() {
510+
assertThat(this.properties.getTomcat().getUseApr()).isEqualTo(UseApr.NEVER);
511+
}
512+
497513
private Connector getDefaultConnector() {
498514
return new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
499515
}

Diff for: spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java

+21-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@
3636
import org.apache.catalina.core.AprLifecycleListener;
3737
import org.apache.catalina.loader.WebappLoader;
3838
import org.apache.catalina.startup.Tomcat;
39+
import org.apache.catalina.startup.Tomcat.FixContextListener;
3940
import org.apache.catalina.webresources.StandardRoot;
4041
import org.apache.commons.logging.Log;
4142
import org.apache.commons.logging.LogFactory;
@@ -83,8 +84,6 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
8384

8485
private List<LifecycleListener> contextLifecycleListeners = new ArrayList<>();
8586

86-
private final List<LifecycleListener> serverLifecycleListeners = getDefaultServerLifecycleListeners();
87-
8887
private Set<TomcatContextCustomizer> tomcatContextCustomizers = new LinkedHashSet<>();
8988

9089
private Set<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new LinkedHashSet<>();
@@ -101,6 +100,8 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
101100

102101
private boolean disableMBeanRegistry = true;
103102

103+
private boolean useApr;
104+
104105
/**
105106
* Create a new {@link TomcatReactiveWebServerFactory} instance.
106107
*/
@@ -116,10 +117,12 @@ public TomcatReactiveWebServerFactory(int port) {
116117
super(port);
117118
}
118119

119-
private static List<LifecycleListener> getDefaultServerLifecycleListeners() {
120-
AprLifecycleListener aprLifecycleListener = new AprLifecycleListener();
121-
return AprLifecycleListener.isAprAvailable() ? new ArrayList<>(Arrays.asList(aprLifecycleListener))
122-
: new ArrayList<>();
120+
private List<LifecycleListener> getDefaultServerLifecycleListeners() {
121+
ArrayList<LifecycleListener> lifecycleListeners = new ArrayList<>();
122+
if (this.useApr) {
123+
lifecycleListeners.add(new AprLifecycleListener());
124+
}
125+
return lifecycleListeners;
123126
}
124127

125128
@Override
@@ -130,7 +133,7 @@ public WebServer getWebServer(HttpHandler httpHandler) {
130133
Tomcat tomcat = new Tomcat();
131134
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
132135
tomcat.setBaseDir(baseDir.getAbsolutePath());
133-
for (LifecycleListener listener : this.serverLifecycleListeners) {
136+
for (LifecycleListener listener : getDefaultServerLifecycleListeners()) {
134137
tomcat.getServer().addLifecycleListener(listener);
135138
}
136139
Connector connector = new Connector(this.protocol);
@@ -171,7 +174,7 @@ protected void prepareContext(Host host, TomcatHttpHandlerAdapter servlet) {
171174
context.setResources(resourcesRoot);
172175
context.setPath("");
173176
context.setDocBase(docBase.getAbsolutePath());
174-
context.addLifecycleListener(new Tomcat.FixContextListener());
177+
context.addLifecycleListener(new FixContextListener());
175178
ClassLoader parentClassLoader = ClassUtils.getDefaultClassLoader();
176179
context.setParentClassLoader(parentClassLoader);
177180
skipAllTldScanning(context);
@@ -476,4 +479,13 @@ public void setDisableMBeanRegistry(boolean disableMBeanRegistry) {
476479
this.disableMBeanRegistry = disableMBeanRegistry;
477480
}
478481

482+
/**
483+
* Whether to use APR.
484+
* @param useApr whether to use APR
485+
* @since 3.4.4
486+
*/
487+
public void setUseApr(boolean useApr) {
488+
this.useApr = useApr;
489+
}
490+
479491
}

Diff for: spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java

+16-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -135,8 +135,6 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
135135

136136
private List<LifecycleListener> contextLifecycleListeners = new ArrayList<>();
137137

138-
private final List<LifecycleListener> serverLifecycleListeners = getDefaultServerLifecycleListeners();
139-
140138
private Set<TomcatContextCustomizer> tomcatContextCustomizers = new LinkedHashSet<>();
141139

142140
private Set<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new LinkedHashSet<>();
@@ -159,6 +157,8 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
159157

160158
private boolean disableMBeanRegistry = true;
161159

160+
private boolean useApr;
161+
162162
/**
163163
* Create a new {@link TomcatServletWebServerFactory} instance.
164164
*/
@@ -184,13 +184,10 @@ public TomcatServletWebServerFactory(String contextPath, int port) {
184184
super(contextPath, port);
185185
}
186186

187-
private static List<LifecycleListener> getDefaultServerLifecycleListeners() {
187+
private List<LifecycleListener> getDefaultServerLifecycleListeners() {
188188
ArrayList<LifecycleListener> lifecycleListeners = new ArrayList<>();
189-
if (!NativeDetector.inNativeImage()) {
190-
AprLifecycleListener aprLifecycleListener = new AprLifecycleListener();
191-
if (AprLifecycleListener.isAprAvailable()) {
192-
lifecycleListeners.add(aprLifecycleListener);
193-
}
189+
if (!NativeDetector.inNativeImage() && this.useApr) {
190+
lifecycleListeners.add(new AprLifecycleListener());
194191
}
195192
return lifecycleListeners;
196193
}
@@ -203,7 +200,7 @@ public WebServer getWebServer(ServletContextInitializer... initializers) {
203200
Tomcat tomcat = new Tomcat();
204201
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
205202
tomcat.setBaseDir(baseDir.getAbsolutePath());
206-
for (LifecycleListener listener : this.serverLifecycleListeners) {
203+
for (LifecycleListener listener : getDefaultServerLifecycleListeners()) {
207204
tomcat.getServer().addLifecycleListener(listener);
208205
}
209206
Connector connector = new Connector(this.protocol);
@@ -784,6 +781,15 @@ public void setDisableMBeanRegistry(boolean disableMBeanRegistry) {
784781
this.disableMBeanRegistry = disableMBeanRegistry;
785782
}
786783

784+
/**
785+
* Whether to use APR.
786+
* @param useApr whether to use APR
787+
* @since 3.4.4
788+
*/
789+
public void setUseApr(boolean useApr) {
790+
this.useApr = useApr;
791+
}
792+
787793
/**
788794
* {@link LifecycleListener} to disable persistence in the {@link StandardManager}. A
789795
* {@link LifecycleListener} is used so not to interfere with Tomcat's default manager

0 commit comments

Comments
 (0)