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:

Bash
./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:

Bash
java -Djarmode=layertools -jar target/bookstore-0.0.1-SNAPSHOT.jar list
Plaintext
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:

YAML
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:

YAML
info:
  app:
    name: bookstore-service
    version: "@project.version@"
HTML
<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>
Bash
curl localhost:8080/actuator/info
JSON
{ "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:

Bash
curl localhost:8080/actuator/metrics/http.server.requests
JSON
{
  "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:

Bash
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:

YAML
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:

Java
@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/health into 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/application layer split — see the Docker page in this track for the fix.