Skip to content

Commit c3c7ed4

Browse files
committed
Fix class cast during additional path matching with health probes
Previously, when health probes were enabled, the post-processor of AutoConfiguredHealthEndpointGroups resulted in the bean no longer implementing AdditionalPathMapper. This then caused a ClassCastException when working with AdditionalPathMapper beans in EndpointRequest's additional path mapping support. This commit updates the type returned by the post-processor to implement both HealthEndpointGroups and AdditionalPathMapper, as AutoConfiguredHealthEndpointGroups does. Its implementation of getAdditionalPaths produces a result that combines both the additional paths of the original HealthEndpointGroups bean and its own additional paths for the probes. Fixes gh-44052
1 parent 1c0253b commit c3c7ed4

File tree

2 files changed

+66
-3
lines changed

2 files changed

+66
-3
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesHealthEndpointGroups.java

+27-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 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,14 +16,20 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.availability;
1818

19+
import java.util.ArrayList;
1920
import java.util.Collections;
2021
import java.util.LinkedHashMap;
2122
import java.util.LinkedHashSet;
23+
import java.util.List;
2224
import java.util.Map;
25+
import java.util.Objects;
2326
import java.util.Set;
2427

28+
import org.springframework.boot.actuate.endpoint.EndpointId;
29+
import org.springframework.boot.actuate.endpoint.web.AdditionalPathsMapper;
2530
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
2631
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
32+
import org.springframework.boot.actuate.health.HealthEndpoint;
2733
import org.springframework.boot.actuate.health.HealthEndpointGroup;
2834
import org.springframework.boot.actuate.health.HealthEndpointGroups;
2935
import org.springframework.util.Assert;
@@ -35,7 +41,7 @@
3541
* @author Brian Clozel
3642
* @author Madhura Bhave
3743
*/
38-
class AvailabilityProbesHealthEndpointGroups implements HealthEndpointGroups {
44+
class AvailabilityProbesHealthEndpointGroups implements HealthEndpointGroups, AdditionalPathsMapper {
3945

4046
private final HealthEndpointGroups groups;
4147

@@ -107,4 +113,23 @@ private boolean isProbeGroup(String name) {
107113
return name.equals(LIVENESS) || name.equals(READINESS);
108114
}
109115

116+
@Override
117+
public List<String> getAdditionalPaths(EndpointId endpointId, WebServerNamespace webServerNamespace) {
118+
if (!HealthEndpoint.ID.equals(endpointId)) {
119+
return null;
120+
}
121+
List<String> additionalPaths = new ArrayList<>();
122+
if (this.groups instanceof AdditionalPathsMapper additionalPathsMapper) {
123+
additionalPaths.addAll(additionalPathsMapper.getAdditionalPaths(endpointId, webServerNamespace));
124+
}
125+
additionalPaths.addAll(this.probeGroups.values()
126+
.stream()
127+
.map(HealthEndpointGroup::getAdditionalPath)
128+
.filter(Objects::nonNull)
129+
.filter((additionalPath) -> additionalPath.hasNamespace(webServerNamespace))
130+
.map(AdditionalHealthEndpointPath::getValue)
131+
.toList());
132+
return additionalPaths;
133+
}
134+
110135
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesHealthEndpointGroupsPostProcessorTests.java

+39-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 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.
@@ -17,10 +17,15 @@
1717
package org.springframework.boot.actuate.autoconfigure.availability;
1818

1919
import java.util.LinkedHashSet;
20+
import java.util.List;
2021
import java.util.Set;
2122

2223
import org.junit.jupiter.api.Test;
24+
import org.mockito.Mockito;
2325

26+
import org.springframework.boot.actuate.endpoint.EndpointId;
27+
import org.springframework.boot.actuate.endpoint.web.AdditionalPathsMapper;
28+
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
2429
import org.springframework.boot.actuate.health.HealthEndpointGroup;
2530
import org.springframework.boot.actuate.health.HealthEndpointGroups;
2631
import org.springframework.mock.env.MockEnvironment;
@@ -103,6 +108,39 @@ void postProcessHealthEndpointGroupsWhenGroupsAlreadyContainedAndAdditionalPathP
103108
assertThat(readiness.getAdditionalPath()).hasToString("server:/readyz");
104109
}
105110

111+
@Test
112+
void delegatesAdditionalPathMappingToOriginalBean() {
113+
HealthEndpointGroups groups = mock(HealthEndpointGroups.class,
114+
Mockito.withSettings().extraInterfaces(AdditionalPathsMapper.class));
115+
given(((AdditionalPathsMapper) groups).getAdditionalPaths(EndpointId.of("health"), WebServerNamespace.SERVER))
116+
.willReturn(List.of("/one", "/two", "/three"));
117+
MockEnvironment environment = new MockEnvironment();
118+
AvailabilityProbesHealthEndpointGroupsPostProcessor postProcessor = new AvailabilityProbesHealthEndpointGroupsPostProcessor(
119+
environment);
120+
HealthEndpointGroups postProcessed = postProcessor.postProcessHealthEndpointGroups(groups);
121+
assertThat(postProcessed).isInstanceOf(AdditionalPathsMapper.class);
122+
AdditionalPathsMapper additionalPathsMapper = (AdditionalPathsMapper) postProcessed;
123+
assertThat(additionalPathsMapper.getAdditionalPaths(EndpointId.of("health"), WebServerNamespace.SERVER))
124+
.containsExactly("/one", "/two", "/three");
125+
}
126+
127+
@Test
128+
void whenAddAdditionalPathsIsTrueThenIncludesOwnAdditionalPathsInGetAdditionalPathsResult() {
129+
HealthEndpointGroups groups = mock(HealthEndpointGroups.class,
130+
Mockito.withSettings().extraInterfaces(AdditionalPathsMapper.class));
131+
given(((AdditionalPathsMapper) groups).getAdditionalPaths(EndpointId.of("health"), WebServerNamespace.SERVER))
132+
.willReturn(List.of("/one", "/two", "/three"));
133+
MockEnvironment environment = new MockEnvironment();
134+
environment.setProperty("management.endpoint.health.probes.add-additional-paths", "true");
135+
AvailabilityProbesHealthEndpointGroupsPostProcessor postProcessor = new AvailabilityProbesHealthEndpointGroupsPostProcessor(
136+
environment);
137+
HealthEndpointGroups postProcessed = postProcessor.postProcessHealthEndpointGroups(groups);
138+
assertThat(postProcessed).isInstanceOf(AdditionalPathsMapper.class);
139+
AdditionalPathsMapper additionalPathsMapper = (AdditionalPathsMapper) postProcessed;
140+
assertThat(additionalPathsMapper.getAdditionalPaths(EndpointId.of("health"), WebServerNamespace.SERVER))
141+
.containsExactly("/one", "/two", "/three", "/livez", "/readyz");
142+
}
143+
106144
private HealthEndpointGroups getPostProcessed(String value) {
107145
MockEnvironment environment = new MockEnvironment();
108146
environment.setProperty("management.endpoint.health.probes.add-additional-paths", value);

0 commit comments

Comments
 (0)