From 2edd6257151bdc364a66001d8871d23d4ed82c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Ko=CC=88ninger?= Date: Fri, 17 Jul 2026 10:20:47 +0200 Subject: [PATCH 1/5] feat(sample-servlet): add CompositeHealthContributor example Demonstrates ExternalApiHealthIndicator, MessageBrokerHealthIndicator, and ExternalServicesHealthContributor (CompositeHealthContributor) wired via HealthContributorConfig. Uses the Spring Boot 4.x health API (org.springframework.boot.health.contributor). --- .../health/ExternalApiHealthIndicator.java | 34 ++++++++++ .../ExternalServicesHealthContributor.java | 65 +++++++++++++++++++ .../health/HealthContributorConfig.java | 52 +++++++++++++++ .../health/MessageBrokerHealthIndicator.java | 34 ++++++++++ 4 files changed, 185 insertions(+) create mode 100644 spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/health/ExternalApiHealthIndicator.java create mode 100644 spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/health/ExternalServicesHealthContributor.java create mode 100644 spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/health/HealthContributorConfig.java create mode 100644 spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/health/MessageBrokerHealthIndicator.java diff --git a/spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/health/ExternalApiHealthIndicator.java b/spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/health/ExternalApiHealthIndicator.java new file mode 100644 index 00000000000..ca37cfc1f6e --- /dev/null +++ b/spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/health/ExternalApiHealthIndicator.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.codecentric.boot.admin.sample.health; + +import org.springframework.boot.health.contributor.Health; +import org.springframework.boot.health.contributor.HealthIndicator; + +/** + * A sample {@link HealthIndicator} that simulates checking connectivity to an external + * API. In a real application this would perform an actual HTTP call or similar check. + */ +public class ExternalApiHealthIndicator implements HealthIndicator { + + @Override + public Health health() { + // Simulate a reachable external API + return Health.up().withDetail("url", "https://api.example.com").withDetail("responseTime", "42ms").build(); + } + +} diff --git a/spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/health/ExternalServicesHealthContributor.java b/spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/health/ExternalServicesHealthContributor.java new file mode 100644 index 00000000000..83c36266508 --- /dev/null +++ b/spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/health/ExternalServicesHealthContributor.java @@ -0,0 +1,65 @@ +/* + * Copyright 2014-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.codecentric.boot.admin.sample.health; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Stream; + +import org.springframework.boot.health.contributor.CompositeHealthContributor; +import org.springframework.boot.health.contributor.HealthContributor; +import org.springframework.boot.health.contributor.HealthContributors; + +/** + * A {@link CompositeHealthContributor} that groups related external-dependency health + * checks under a single {@code /actuator/health/externalServices} entry. + * + *

+ * Spring Boot aggregates the individual {@link HealthContributor}s and rolls up the worst + * status to the composite level, so a single degraded dependency is immediately visible + * both in the top-level health summary and in the drill-down view. + * + *

+ * The composite is registered as a bean named {@code externalServices} in + * {@link HealthContributorConfig}, which causes Spring Boot Actuator to expose it at + * {@code /actuator/health/externalServices}. + */ +// tag::composite-health-contributor[] +public class ExternalServicesHealthContributor implements CompositeHealthContributor { + + private final Map contributors = new LinkedHashMap<>(); + + public ExternalServicesHealthContributor(ExternalApiHealthIndicator externalApi, + MessageBrokerHealthIndicator messageBroker) { + contributors.put("externalApi", externalApi); + contributors.put("messageBroker", messageBroker); + } + + @Override + public HealthContributor getContributor(String name) { + return contributors.get(name); + } + + @Override + public Stream stream() { + return contributors.entrySet() + .stream() + .map((entry) -> new HealthContributors.Entry(entry.getKey(), entry.getValue())); + } + +} +// end::composite-health-contributor[] diff --git a/spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/health/HealthContributorConfig.java b/spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/health/HealthContributorConfig.java new file mode 100644 index 00000000000..3fc112efa6d --- /dev/null +++ b/spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/health/HealthContributorConfig.java @@ -0,0 +1,52 @@ +/* + * Copyright 2014-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.codecentric.boot.admin.sample.health; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Registers the individual health indicators and the composite contributor as Spring + * beans. + * + *

+ * The bean name {@code externalServices} is used by Spring Boot Actuator as the path + * segment, so the composite becomes available at + * {@code /actuator/health/externalServices}. + */ +@Configuration(proxyBeanMethods = false) +public class HealthContributorConfig { + + @Bean + public ExternalApiHealthIndicator externalApiHealthIndicator() { + return new ExternalApiHealthIndicator(); + } + + @Bean + public MessageBrokerHealthIndicator messageBrokerHealthIndicator() { + return new MessageBrokerHealthIndicator(); + } + + // tag::composite-health-contributor-bean[] + @Bean + public ExternalServicesHealthContributor externalServices(ExternalApiHealthIndicator externalApi, + MessageBrokerHealthIndicator messageBroker) { + return new ExternalServicesHealthContributor(externalApi, messageBroker); + } + // end::composite-health-contributor-bean[] + +} diff --git a/spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/health/MessageBrokerHealthIndicator.java b/spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/health/MessageBrokerHealthIndicator.java new file mode 100644 index 00000000000..777228898af --- /dev/null +++ b/spring-boot-admin-samples/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/sample/health/MessageBrokerHealthIndicator.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.codecentric.boot.admin.sample.health; + +import org.springframework.boot.health.contributor.Health; +import org.springframework.boot.health.contributor.HealthIndicator; + +/** + * A sample {@link HealthIndicator} that simulates checking an internal message broker. In + * a real application this would verify queue depth, connection status, etc. + */ +public class MessageBrokerHealthIndicator implements HealthIndicator { + + @Override + public Health health() { + // Simulate a healthy message broker + return Health.up().withDetail("broker", "in-memory-stub").withDetail("queueDepth", 0).build(); + } + +} From 49fb3b9821f3b44c062b5f62e9995af2ceeb9526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Ko=CC=88ninger?= Date: Fri, 17 Jul 2026 10:20:52 +0200 Subject: [PATCH 2/5] feat(health-ui): redesign health details panel Replace the flat dl/dd grid with status-colour-coded rows. Composite contributors render as collapsible section headers; children are indented below. All badges align to a shared left column (w-52). Label top-aligns with the badge on tall rows. --- .../__snapshots__/health-details.spec.ts.snap | 2 +- .../instances/details/details-health.vue | 209 +++++++++++----- .../instances/details/health-details.vue | 226 ++++++++++++++---- 3 files changed, 329 insertions(+), 108 deletions(-) diff --git a/spring-boot-admin-server-ui/src/main/frontend/views/instances/details/__snapshots__/health-details.spec.ts.snap b/spring-boot-admin-server-ui/src/main/frontend/views/instances/details/__snapshots__/health-details.spec.ts.snap index 673cf2a8713..716da368beb 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/views/instances/details/__snapshots__/health-details.spec.ts.snap +++ b/spring-boot-admin-server-ui/src/main/frontend/views/instances/details/__snapshots__/health-details.spec.ts.snap @@ -4,7 +4,7 @@ exports[`HealthDetails > Health .details > should format object details correctl

diff --git a/spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-health.vue b/spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-health.vue index 8bad6f5d79c..288c3f0193e 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-health.vue +++ b/spring-boot-admin-server-ui/src/main/frontend/views/instances/details/details-health.vue @@ -25,7 +25,6 @@ v-if="health.status" :status="health.status" class="ml-2 transition-opacity" - :class="{ 'opacity-0': panelOpen }" /> @@ -47,60 +46,62 @@ class="border-l-4" :title="$t('term.fetch_failed')" /> -
- - -