Management Surface (Actuator-style endpoints)
See also: Health / Readiness / Liveness Probes — the health half of the management surface, and REST Server — Mixins and Multi-Mount Paths — the
@Rest(mixins=...)composition primitives this page builds on.
Starting with 10.0.0, Juneau ships a coherent, discoverable Actuator-style management surface while preserving Juneau's a-la-carte composability. Every endpoint exists in two independently-mountable flavors — a composable *Mixin and a routed *Resource — sharing one worker, and a convenience BasicActuatorGroup assembles them under a single configurable path prefix.
Endpoint catalog
| Endpoint | What it does | Module | Default exposure |
|---|---|---|---|
GET /info | Application manifest / build / version / git metadata | juneau-microservice | on |
GET /loggers, GET /loggers/{name} | Read logger levels (JUL default; Logback/Log4j2 opt-in) | juneau-rest-server | on |
PUT/POST /loggers/{name} | Set a logger level at runtime | juneau-rest-server | deny-by-default |
GET /metrics | Prometheus scrape from a Micrometer registry | juneau-rest-server-metrics-micrometer | on (501 if no registry) |
GET /healthz, /readyz, /livez | Health / readiness / liveness probes | juneau-rest-server | on |
GET /threaddump | ThreadMXBean thread dump | juneau-rest-server | deny-by-default |
GET /heapdump | HotSpot .hprof heap dump | juneau-rest-server | deny-by-default |
Endpoints are split across modules by dependency: /loggers (JUL backend) and the JDK-only /threaddump//heapdump need only the REST server; non-JUL /loggers backends (Logback/Log4j2) need juneau-rest-server-management-logging; /metrics needs Micrometer; /info needs the microservice runtime that resolves the application ManifestFile.
BasicActuatorGroup — one-liner assembly
import org.apache.juneau.microservice.management.*;
import org.apache.juneau.rest.server.*;
// Mount the whole surface under /actuator.
@Rest(children=BasicActuatorGroup.class)
public class RootResources extends BasicRestServletGroup { /* ... */ }
BasicActuatorGroup composes HealthMixin and DumpsMixin under a single prefix. The prefix defaults to /actuator and is configurable via the juneau.actuator.path system property (resolved through the standard $S{...} SVL var):
java -Djuneau.actuator.path=/manage -jar myapp.jar
/infoand/loggersare off by default — unlike health, they are not mounted on the bare group, sinceInfoMixindiscloses the full manifest andLoggersMixin's read side discloses logger topology, neither behind any auth. Re-enable either (or both) a-la-carte on a subclass:@Rest(mixins={InfoMixin.class, LoggersMixin.class})
public class MyActuatorGroup extends BasicActuatorGroup { /* ... */ }Mixins declared on a subclass are additive to the ones inherited from
BasicActuatorGroup, soHealthMixin/DumpsMixinstay mounted too. With no such subclass,/infoand/loggersreturn404under the group's prefix.
/metricsis not assembled into the group — it lives in thejuneau-rest-server-metrics-micrometermodule (whichjuneau-microservicedoes not depend on). AddMetricsMixina-la-carte to your host resource when that module is on the classpath.
Composition is convenience, not the only way in — every endpoint remains independently mountable a-la-carte via its standalone *Mixin/*Resource flavor.
Exposure / security policy
The surface is on-by-default-with-gating: non-sensitive reads are exposed; mutating/sensitive operations are deny-by-default and require an explicit opt-in bean. No auth provider is auto-wired (explicit-over-magic) — wire your own guard (e.g. a BearerTokenGuard) on top.
There are two complementary layers:
- Functional gate (deny-by-default): a settings bean (
LoggersSettings,DumpsSettings) controls whether the sensitive capability is enabled at all. Off until you opt in. - Authz gate (who may call it): apply a guard to the sensitive mixin at composition time with
@Rest(mixinDefs=@Mixin(type=..., guards=...))— the host-side override from Mixin Sub-Contexts. For example, to requireAdminGuardon the diagnostic dumps:
import org.apache.juneau.rest.server.*;
import org.apache.juneau.rest.server.management.*;
@Rest(mixinDefs=@Mixin(type=DumpsMixin.class, guards=AdminGuard.class))
public class MyManagedResource extends BasicRestServlet { ... }
This guards the dump endpoints without subclassing DumpsMixin or editing it. Combine the two layers: DumpsSettings enables the dumps, @Mixin(guards=...) restricts who can call them.
/loggers set-level (deny-by-default)
The read endpoints are always on; the PUT/POST set-level endpoints respond 403 unless a LoggersSettings bean enables writes:
import org.apache.juneau.commons.inject.*;
import org.apache.juneau.rest.server.management.*;
@Bean
LoggersSettings loggersSettings() {
return LoggersSettings.create().enableWrite().build();
}
/threaddump + /heapdump (deny-by-default)
Both diagnostics respond 403 unless a DumpsSettings bean opts them in (independently). /heapdump additionally responds 501 on a JVM without HotSpot heap-dump support, and streams a self-deleting .hprof temp file when enabled:
@Bean
DumpsSettings dumpsSettings() {
return DumpsSettings.create()
.enableThreadDump()
.enableHeapDump()
.build();
}
Why deny-by-default? A thread dump can leak sensitive runtime state; a heap dump can be large and contain secrets. Both stay off until an operator explicitly opts in.
/info and manifest stamping
/info renders the running application's META-INF/MANIFEST.MF main attributes — sourced from Microservice.getManifest() — as a JSON map. With no manifest registered it degrades cleanly to {}.
To surface build/version/git metadata, stamp them into your application jar's manifest at build time. /info reads your app jar's manifest, so the stamping goes in your build, not Juneau's. A typical Maven recipe:
<!-- 1. Capture git metadata into Maven properties. -->
<plugin>
<groupId>io.github.git-commit-id</groupId>
<artifactId>git-commit-id-maven-plugin</artifactId>
<executions>
<execution><goals><goal>revision</goal></goals></execution>
</executions>
<configuration>
<generateGitPropertiesFile>false</generateGitPropertiesFile>
</configuration>
</plugin>
<!-- 2. Stamp them (plus version/build-time) into MANIFEST.MF. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
<manifestEntries>
<Build-Time>${maven.build.timestamp}</Build-Time>
<Git-Commit-Id>${git.commit.id.abbrev}</Git-Commit-Id>
<Git-Branch>${git.branch}</Git-Branch>
</manifestEntries>
</archive>
</configuration>
</plugin>
addDefaultImplementationEntries contributes Implementation-Title/Implementation-Version; the manifestEntries add the git/build fields. All of them then appear in /info:
{
"Implementation-Version": "1.4.2",
"Build-Time": "2026-06-17T00:00:00Z",
"Git-Commit-Id": "abc1234",
"Git-Branch": "main"
}
/metrics — Micrometer scrape
/metrics strictly consumes a consumer-provided MeterRegistry resolved from the bean store — it never auto-registers a default (consistent with micrometer-core being a provided dependency). It renders a Prometheus text scrape when a PrometheusMeterRegistry is present and degrades to 501 Not Implemented otherwise.
@Bean
MeterRegistry meterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
Mount the endpoint by mixing MetricsMixin into your host resource (the juneau-rest-server-metrics-micrometer module must be on the classpath).
/loggers backends (JUL, Logback, Log4j2)
The /loggers endpoint drives a LogBackend. By default that is java.util.logging (JulLogBackend) — the backend Juneau itself configures, and the only one bundled with juneau-rest-server (the core stays dependency-free). Applications that route logging through SLF4J→Logback or Log4j2 select the matching backend explicitly.
Explicit backend selection (no auto-detection)
Consistent with Juneau's explicit-over-magic stance, you declare the backend on a LoggersSettings bean — the endpoint never classpath-scans for, and then silently drives, a backend you didn't choose. The Logback / Log4j2 adapters ship in the opt-in juneau-rest-server-management-logging module (the backends are declared provided, so they never leak transitively onto your classpath):
<dependency>
<groupId>org.apache.juneau</groupId>
<artifactId>juneau-rest-server-management-logging</artifactId>
<version>${juneau.version}</version>
</dependency>
<!-- plus your chosen backend at the version you want, e.g. ch.qos.logback:logback-classic -->
@Bean
public LoggersSettings loggersSettings() {
return LoggersSettings.create()
.enableWrite() // opt in to the set-level endpoints (deny-by-default)
.backend(new LogbackLogBackend()) // or new Log4j2LogBackend() — explicit, no auto-detect
.build();
}
When no LoggersSettings bean is registered (or none declares a backend), the endpoint drives JUL.
Same response shape across backends
The endpoint's contract is identical regardless of backend:
- Read reports each logger's configured level — the level set on the logger itself, or the empty string when it inherits from an ancestor (Log4j2
LoggerConfigs always carry a concrete level, so the empty-string "inherited" marker only appears for JUL/Logback). The root logger is keyedROOT. - Set is process-lifetime-only (JUL
Logger.setLevel, LogbackLogger.setLevel, Log4j2Configurator.setLevel) — it does not rewrite the backend's configuration file. A blank/empty level clears the logger's own level so it inherits again.
Level names are backend-native (JUL FINE/INFO/…; Logback & Log4j2 DEBUG/INFO/WARN/…). An invalid level name fails with 400.
SLF4J is a facade — pick the real backend
SLF4J itself emits nothing; the levels live in the bound backend. Select the backend that actually emits: LogbackLogBackend for SLF4J→Logback, Log4j2LogBackend for Log4j2 (including SLF4J→Log4j2). When the SLF4J binding is slf4j-jdk14 (SLF4J→JUL), the backend is JUL, so the default JulLogBackend is correct. Custom backends can implement LogBackend directly.