Production Deployment & Actuator Deep Dive
Packaging as a layered fat JAR, Actuator endpoints beyond health checks, and securing Actuator in production.
Packaging as an executable fat JAR
./mvnw clean package (via the spring-boot-maven-plugin, added automatically by spring-boot-starter-parent) produces a single executable JAR containing your compiled classes, all of their dependencies, and an embedded server — a "fat" or "uber" JAR:
./mvnw clean package
java -jar target/bookstore-0.0.1-SNAPSHOT.jar
Structurally, this JAR isn't a flat pile of .class files — Spring Boot organizes it into distinct layers so it can be unpacked and repackaged efficiently:
java -Djarmode=layertools -jar target/bookstore-0.0.1-SNAPSHOT.jar list
dependencies
spring-boot-loader
snapshot-dependencies
application
| Layer | Contents | Changes how often |
|---|---|---|
dependencies |
Third-party libraries (non-snapshot) | Rarely — only on a dependency version bump |
spring-boot-loader |
Spring Boot's own bootstrap classes | Almost never |
snapshot-dependencies |
-SNAPSHOT dependencies |
Occasionally |
application |
Your own compiled classes and resources | Every build |
This layering is the foundation for building efficient Docker images — covered in this track's Docker page — since a container image can cache the rarely-changing dependency layers separately from your own code, which changes on every build.
Actuator beyond /health
Actuator exposes far more than a health check once you opt into additional endpoints:
management:
endpoints:
web:
exposure:
include: health, info, metrics, loggers
/actuator/info
Reports arbitrary build/application metadata — populated from application.yml, or automatically from the build itself:
info:
app:
name: bookstore-service
version: "@project.version@"
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
curl localhost:8080/actuator/info
{ "app": { "name": "bookstore-service", "version": "1.4.0" } }
/actuator/metrics
Backed by Micrometer, Spring Boot's metrics facade — a single endpoint exposes counters and timers for the JVM, HTTP requests, the connection pool, and anything you instrument yourself:
curl localhost:8080/actuator/metrics/http.server.requests
{
"name": "http.server.requests",
"measurements": [
{ "statistic": "COUNT", "value": 1348 },
{ "statistic": "TOTAL_TIME", "value": 42.7 },
{ "statistic": "MAX", "value": 0.31 }
],
"availableTags": [
{ "tag": "uri", "values": ["/api/books", "/api/books/{id}"] },
{ "tag": "status", "values": ["200", "404", "500"] }
]
}
In practice, this endpoint is rarely queried by hand — it's what a Prometheus/Micrometer registry scrapes on an interval to feed dashboards and alerts.
/actuator/loggers
Lets you inspect and change a package's log level at runtime, without a redeploy — genuinely useful for diagnosing a live production issue:
curl -X POST localhost:8080/actuator/loggers/com.example.bookstore \
-H "Content-Type: application/json" \
-d '{"configuredLevel": "DEBUG"}'
Securing Actuator endpoints
Actuator endpoints are operational, not customer-facing — most of them (/env, /heapdump, /loggers) can leak configuration, secrets, or internal detail if left open. Two complementary approaches:
1. Put management endpoints on a separate port, so they're reachable only from an internal network (a Kubernetes readiness probe, an internal monitoring VPC) and never exposed alongside the public API port:
management:
server:
port: 9001
2. Explicitly authorize actuator paths in the security filter chain, requiring a specific role even if the port isn't separated:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health", "/actuator/info").permitAll()
.requestMatchers("/actuator/**").hasRole("OPS")
.anyRequest().authenticated()
);
return http.build();
}
| Endpoint | Typical exposure |
|---|---|
/actuator/health |
Public, or at least reachable by the load balancer/orchestrator |
/actuator/info |
Usually safe to expose publicly — no sensitive data by default |
/actuator/metrics, /actuator/loggers |
Internal only — authenticated, or a separate management port |
/actuator/env, /actuator/heapdump, /actuator/threaddump |
Highly sensitive — can leak secrets/memory contents; restrict tightly or disable entirely |
Common mistakes
- Setting
management.endpoints.web.exposure.include: "*"on a production, internet-facing service — this exposes every actuator endpoint, including ones that can leak environment variables and secrets. - Leaving Actuator on the same port as the public API with no additional authorization, relying only on "nobody will guess the path."
- Never wiring
/actuator/healthinto the deployment platform's own liveness/readiness probes, leaving an unhealthy instance in rotation instead of being cycled out automatically. - Rebuilding the entire fat JAR layer-by-layer in a Docker image on every code change instead of taking advantage of the
dependencies/applicationlayer split — see the Docker page in this track for the fix.