Spring Boot Observability and Production Operations - Complete Notes
A practical and interview-focused guide to Actuator, health probes, Micrometer metrics, structured logs, correlation, distributed tracing, secure configuration, graceful shutdown, JVM diagnostics, containers, deployment safety, and evidence-based incident troubleshooting.
00. The production mental model
Production operations is the discipline of making a running system understandable, safe to change, and recoverable when reality differs from expectations.
A program can be logically correct and still fail as a service. It can run out of database connections, wait forever on a downstream dependency, exhaust heap, produce too much logging, reject traffic during a rollout, or return successful HTTP responses while silently losing business work. Production readiness therefore includes runtime signals and operating procedures, not only application code.
Think of a service as an aircraft. Logs are the event record, metrics are the instrument panel, and traces show the route of one journey through multiple systems. Health probes communicate whether the aircraft should receive passengers or be restarted. None of these signals alone explains every failure.
| Question | Primary evidence | Typical use |
|---|---|---|
| What happened to one request? | Trace plus correlated logs | Follow a failure across services and dependencies. |
| Is the whole service getting worse? | Metrics | Detect rate, error, latency, and saturation changes. |
| Why did code choose this path? | Structured logs | Inspect contextual events without attaching a debugger. |
| Should this instance receive traffic? | Readiness | Remove an instance from routing without necessarily restarting it. |
| Is this process unable to recover? | Liveness | Ask the platform to restart a stuck instance. |
| What is the JVM doing now? | Thread dump, heap evidence, JFR | Diagnose deadlock, CPU, allocation, pause, or memory problems. |
Start with user-visible latency, errors, throughput, and resource saturation. Add lower-level signals only when they help explain or predict those outcomes. A dashboard containing hundreds of unowned graphs is not observability.
01. Spring Boot Actuator
Actuator adds production-oriented endpoints and auto-configuration that expose selected runtime information through HTTP or JMX.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
</dependency>
An endpoint must be both enabled and exposed. Most endpoints are enabled for in-process use, but only health is exposed over HTTP by default. Exposure is an attack-surface decision. Endpoints such as environment, configuration properties, heap dump, loggers, mappings, scheduled tasks, and thread dump can reveal secrets, application structure, customer data, or operational controls.
management:
endpoints:
web:
exposure:
include: health,info,prometheus
endpoint:
health:
show-details: when-authorized
roles: OPS
info:
env:
enabled: false
Prefer an explicit allowlist over *. Protect management traffic with Spring Security,
network policy, a private listener, or several layers together. A separate management port can
reduce exposure, but a probe on that port can succeed while the application connector is broken.
For traffic decisions, expose a minimal health group on the main server port.
| Endpoint | Purpose | Production caution |
|---|---|---|
health |
Aggregate component and availability state. | Do not expose internal details anonymously. |
prometheus |
Prometheus-formatted meter scrape. | Restrict access and control label cardinality. |
metrics |
Diagnostic meter names and measurements. | It is a diagnostic view, not usually the monitoring backend. |
loggers |
Inspect and change runtime log levels. | Write access can create data leakage and severe volume. |
threaddump, heapdump |
JVM diagnostic evidence. | Heap dumps may contain credentials and personal data. |
env, configprops |
Configuration diagnosis. | Sanitization helps but is not a reason to publish these openly. |
Securing Actuator with an explicit filter chain
@Bean
@Order(1)
SecurityFilterChain actuatorSecurity(HttpSecurity http) throws Exception {
http.securityMatcher(EndpointRequest.toAnyEndpoint())
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(EndpointRequest.to(HealthEndpoint.class)).permitAll()
.anyRequest().hasRole("OPS"))
.httpBasic(Customizer.withDefaults());
return http.build();
}
Defining a custom SecurityFilterChain makes Spring Boot back off from its Actuator
security auto-configuration, so the application must cover every intended management rule. Network
restrictions remain useful because application authentication can itself be misconfigured.
02. Health indicators and health groups
Health is a small operational contract, not a detailed status page and not a replacement for metrics.
Spring Boot assembles HealthContributor beans into a tree. A
HealthIndicator returns a status such as UP, DOWN,
OUT_OF_SERVICE, or UNKNOWN, optionally with details. A
CompositeHealthContributor groups contributors. A status aggregator chooses the
overall status, and an HTTP status mapper turns it into a response code.
@Component
final class CatalogHealthIndicator implements HealthIndicator {
private final CatalogClient catalog;
CatalogHealthIndicator(CatalogClient catalog) {
this.catalog = catalog;
}
@Override
public Health health() {
try {
CatalogStatus status = catalog.status(Duration.ofMillis(300));
return status.acceptingOrders()
? Health.up().withDetail("mode", "accepting-orders").build()
: Health.outOfService().withDetail("mode", status.mode()).build();
} catch (Exception failure) {
return Health.down(failure).build();
}
}
}
Keep checks fast, bounded, and cheap. Never perform a full table scan, mutate state, or wait on an unbounded remote call. If health collection becomes slow, monitoring can amplify an outage by creating more load. A component that can be observed through metrics but does not control routing often should not be in readiness.
Restarting every instance does not repair PostgreSQL, Redis, DNS, or a payment provider. If a process is responsive but a dependency is unavailable, keep liveness healthy and use readiness, errors, alerts, circuit breakers, and recovery procedures for the dependency.
03. Liveness, readiness, and startup probes
Each probe must answer one narrow question because the platform takes a different action for each answer.
| Probe | Question | Failure action | What usually belongs |
|---|---|---|---|
| Startup | Has initialization completed? | Delay liveness and readiness evaluation, then restart after its failure budget. | Boot completion for legitimately slow applications. |
| Liveness | Is this process stuck beyond self-recovery? | Restart the container. | Internal availability state, not shared external systems. |
| Readiness | Should new traffic be routed here now? | Remove the instance from service endpoints. | Warmup, draining state, and carefully selected critical capabilities. |
management:
endpoint:
health:
probes:
enabled: true
group:
liveness:
additional-path: "server:/livez"
readiness:
additional-path: "server:/readyz"
startupProbe:
httpGet:
path: /livez
port: 8080
periodSeconds: 5
failureThreshold: 30
livenessProbe:
httpGet:
path: /livez
port: 8080
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
httpGet:
path: /readyz
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 2
Tune thresholds from real startup and response distributions. A startup probe is clearer than a huge liveness initial delay because it protects slow initialization without weakening later detection. Avoid probe periods so aggressive that temporary CPU pressure triggers a restart loop. Test failure actions in a staging environment, including rolling deployment and termination.
04. Metrics and Micrometer
Metrics summarize behavior as numeric time series and are best for trends, alerts, capacity, and service-level objectives.
Micrometer is an instrumentation facade. Application and library code use a common meter model,
while a registry publishes to Prometheus, OTLP, Datadog, or another backend. Spring Boot
auto-configures a MeterRegistry, built-in JVM and system binders, web request
metrics, and integration metrics when the matching technology is present.
| Meter | Use | Example |
|---|---|---|
| Counter | Monotonically increasing event count. | Orders rejected by stable reason code. |
| Timer | Count plus latency distribution. | Payment authorization duration. |
| Distribution summary | Distribution of non-time values. | Payload size or batch size. |
| Gauge | Current sampled value. | In-flight work or queue depth. |
| Long task timer | Duration and count of still-running work. | Imports or reconciliation jobs. |
@Component
final class CheckoutMetrics {
private final MeterRegistry registry;
CheckoutMetrics(MeterRegistry registry) {
this.registry = registry;
}
void recordRejected(String reason) {
registry.counter(
"checkout.rejected",
"reason", normalizeReason(reason))
.increment();
}
<T> T timeAuthorization(Supplier<T> action) {
return registry.timer("checkout.authorization")
.record(action);
}
private String normalizeReason(String reason) {
return switch (reason) {
case "OUT_OF_STOCK", "PAYMENT_DECLINED" -> reason;
default -> "OTHER";
};
}
}
Cardinality is a production constraint
Every unique combination of meter name and tag values creates a time series. Tags such as customer ID, order ID, URL with identifiers, exception message, email address, or raw SQL can create millions of series. That increases memory, ingestion cost, query time, and incident risk. Use bounded dimensions such as route template, status class, operation, region, or enumerated outcome. Put request-specific values in traces and logs.
- Traffic: request, job, or message rate.
- Errors: failed outcomes, ideally separated by stable reason.
- Latency: distributions and percentiles, not average alone.
- Saturation: pool usage, queue depth, CPU, memory pressure, and thread availability.
Configure histograms and percentiles deliberately. Client-side percentiles are difficult to aggregate across instances. Histogram buckets support aggregate percentile estimation but add series. Choose buckets around user-facing latency objectives, and verify backend semantics for counters, steps, resets, and exemplars.
05. Logs, structured logging, and correlation
A useful log is a bounded, structured event that explains something an operator may need to know.
Spring Boot uses Commons Logging internally and normally provides Logback through its starters. Containerized applications usually write to standard output and let the platform collect, rotate, retain, and ship logs. Avoid application-managed files unless the environment specifically requires them.
spring:
application:
name: orders
logging:
structured:
format:
console: ecs
level:
root: INFO
com.example.orders: INFO
log.atInfo()
.addKeyValue("order.id", orderId)
.addKeyValue("outcome", "accepted")
.log("Order accepted");
log.atWarn()
.addKeyValue("payment.provider", provider)
.addKeyValue("attempt", attempt)
.setCause(failure)
.log("Payment authorization failed");
Define event names and field meanings as an internal contract. Include service, environment, version, trace and span IDs, operation, stable outcome, and safe business identifiers only when policy permits. Never log passwords, access or refresh tokens, session cookies, API keys, secret configuration, full payment data, or unnecessary personal information. Masking after ingestion is weaker than never emitting the value.
Correlation IDs join events for one logical operation. Distributed tracing usually supplies trace
and span IDs and propagates trace context over supported clients. If accepting a client-provided
request ID, validate its length and character set before logging it. Do not assume MDC values move
to worker threads automatically. Thread changes require explicit context propagation, and every
manually installed MDC value must be cleared in a finally block.
Debug logging on a hot path can consume CPU, disk, network, and backend quota. Repeated stack traces can multiply one failure into thousands of events. Use sampling, aggregation, bounded messages, retention policy, and time-limited runtime level changes.
06. Observations and distributed tracing
A trace follows one operation through nested spans, while Micrometer Observation can drive both metrics and tracing from one instrumentation lifecycle.
An observation has a name, a context, lifecycle events, and key values. Low-cardinality values can become metric tags and trace attributes. High-cardinality values belong only in traces. Registered handlers turn the lifecycle into timers, spans, context propagation, or other behavior.
@Component
final class InventoryReservation {
private final ObservationRegistry observations;
InventoryReservation(ObservationRegistry observations) {
this.observations = observations;
}
Reservation reserve(String warehouse, UUID orderId) {
return Observation.createNotStarted("inventory.reserve", observations)
.lowCardinalityKeyValue("warehouse.region", regionOf(warehouse))
.highCardinalityKeyValue("order.id", orderId.toString())
.observe(() -> performReservation(warehouse, orderId));
}
}
Auto-instrumented HTTP clients are important because they inject trace headers. Constructing a
client outside the auto-configured builders can lose propagation. Context can also disappear at
@Async, executor, scheduler, callback, or reactive boundaries. Use Spring Boot
context propagation support or a ContextPropagatingTaskDecorator for a custom
executor, then test that child work retains the expected trace.
Sampling controls volume and cost. Head sampling decides near the start and can miss rare failures. Tail sampling decides after more of the trace is known but requires capable collector infrastructure. A production strategy may keep all errors and slow operations while sampling a smaller share of ordinary successes. Trace data is not a durable audit record.
07. Configuration and secret management
Build one artifact and supply environment-specific configuration at deployment time.
Spring Boot can read properties, YAML, environment variables, command-line arguments, imported
files, and configuration trees. Bind related settings into validated
@ConfigurationProperties objects. Fail startup for missing or invalid required values
rather than discovering them under traffic.
@ConfigurationProperties("payments")
@Validated
public record PaymentProperties(
@NotNull URI baseUrl,
@NotNull Duration connectTimeout,
@NotNull Duration readTimeout,
@Min(1) int maxAttempts) {}
spring:
config:
import: "optional:configtree:/run/secrets/"
Store secrets in a dedicated secret manager or platform secret facility, grant access by workload identity, encrypt in transit and at rest, rotate them, and audit reads. Environment variables are convenient but may be visible through process inspection, diagnostics, deployment metadata, or accidental logging. Mounted files also need strict permissions and rotation behavior. Never bake secrets into source, images, default configuration, test fixtures, or command history.
Decide which values can change without restart, how changes are validated, whether all instances must agree, how rollback works, and how a change is audited. Restart-based configuration is often simpler and safer than silently changing critical behavior in place.
08. Graceful shutdown and draining
Graceful shutdown stops new work, gives accepted work a bounded chance to finish, and then releases resources.
Current Spring Boot versions enable graceful shutdown for supported embedded servers. Closing the
application context begins server shutdown early in the SmartLifecycle stop phases.
Existing requests receive a grace period while new requests are rejected according to server
behavior.
spring:
lifecycle:
timeout-per-shutdown-phase: 25s
The platform termination grace period must be longer than application draining, with time for
network propagation and final process termination. For example, if Spring has 25 seconds, a
Kubernetes pod might need 35 or 40 seconds. On SIGTERM, readiness should turn false
before or while the server stops accepting traffic. Load balancer state does not update
instantaneously, so allow a measured drain interval where needed.
HTTP is only one kind of work. Stop message consumption, scheduler claims, polling loops, and new async submissions. Let in-flight work finish or persist a safe retry state. A request that exceeds the grace period needs idempotency because the client may retry without knowing whether the first attempt committed. Do not rely only on JVM shutdown hooks for critical durable work.
| Resource | Shutdown responsibility | Failure if ignored |
|---|---|---|
| HTTP server | Stop admission, drain bounded in-flight requests. | Connection resets and partial client outcomes. |
| Message listener | Stop polling, finish or abandon safely, acknowledge only after durable success. | Duplicate or lost processing. |
| Executor | Reject new work and await a bounded termination. | Accepted tasks disappear at process exit. |
| Database pool | Close after application work is drained. | In-flight transactions fail unexpectedly. |
| Telemetry exporter | Flush within a short final budget. | Last logs, metrics, or spans may be missing. |
09. Database connection pool monitoring
A connection pool is a concurrency gate around a scarce database resource, not a speed setting to maximize.
Spring Boot commonly uses HikariCP with JDBC and JPA starters. Monitor active, idle, total, pending, acquisition time, usage time, timeout count, maximum size, and database-side connections. The important incident pattern is sustained pending demand plus acquisition latency and timeouts, especially when active connections stay near the configured maximum.
| Signal combination | Likely explanation | Next evidence |
|---|---|---|
| Active near max, pending rising, slow SQL | Queries or lock waits hold connections too long. | Database activity, lock graph, slow query plans, transaction duration. |
| Active near max, CPU low, downstream slow | A transaction performs network I/O while holding a connection. | Traces and thread stacks around service boundaries. |
| Frequent acquisition timeout after load increase | Demand exceeds bounded database concurrency. | Arrival rate, query latency, queueing, database capacity. |
| Many idle connections across replicas | Pool size multiplied by instance count exceeds the useful budget. | Deployment replicas, database connection limit, proxy or pooler policy. |
Increasing the pool can move contention into PostgreSQL and make every query slower. Size the total across all instances, workers, administrative tools, and failover headroom. Keep transactions short, set bounded acquisition and statement timeouts, diagnose leaks, and apply backpressure before requests pile up without limit.
10. Thread dumps, heap diagnostics, and JFR
Runtime diagnostics are snapshots and recordings that test a hypothesis about CPU, blocking, deadlock, allocation, garbage collection, or memory retention.
Thread dumps
A thread dump records thread names, states, stack traces, and owned or awaited monitors. Capture several dumps a few seconds apart. One snapshot can show a normal moment; repeated identical stacks reveal stuck work, while moving stacks reveal progress.
jcmd <pid> Thread.print -l
jcmd <pid> GC.class_histogram
jcmd <pid> GC.heap_dump filename=/secure/diagnostics/app.hprof
jcmd <pid> JFR.start name=incident settings=profile duration=5m \
filename=/secure/diagnostics/incident.jfr
| Pattern | Interpretation | Follow-up |
|---|---|---|
| Many request threads waiting for a pool connection | Database concurrency is saturated or connections are held too long. | Pool metrics, transaction spans, database sessions and locks. |
| Many threads blocked on the same monitor | Lock contention or a deadlock candidate. | Owner stack, synchronized region, repeated dumps. |
| RUNNABLE thread with the same CPU-heavy stack | Hot loop, expensive parsing, serialization, compression, or algorithm. | CPU profile or JFR method samples. |
| Workers waiting in a bounded queue | May be normal when idle. | Compare queue depth, active count, throughput, and latency. |
Heap evidence
Heap usage rising after full garbage collections suggests retained live objects, but a single sawtooth is normal. Start with memory pool, allocation rate, pause, and post-GC trend metrics. A class histogram is smaller and faster than a full heap dump. A heap dump can pause the process, consume disk roughly comparable to live heap, and contain secrets or personal data. Store it in a restricted location, transfer it securely, set retention, and delete it according to policy.
In analysis, distinguish shallow size from retained size. Find dominator paths and GC roots that keep unexpected objects alive. Common causes include unbounded caches, static collections, thread-local values, listeners that were never removed, buffered requests, oversized sessions, class-loader retention, and queues whose consumers cannot keep up.
Java Flight Recorder
JFR records time-based evidence such as CPU samples, allocation, locks, garbage collection, file and socket activity with production-oriented overhead. It is often more useful than a heap dump for a broad latency or CPU incident. Use a bounded recording, preserve the exact deployment version, and correlate its time window with service metrics and traces.
11. Error reporting and failure taxonomy
Error reporting must group useful failures without leaking data or turning expected outcomes into noise.
Separate programmer defects, transient infrastructure failures, invalid client input, expected
business rejection, and security events. They have different severity, ownership, retry policy,
and alert behavior. A declined card is not an application exception; a
NullPointerException in every checkout is.
- Capture the application version, environment, operation, safe request context, and trace ID.
- Preserve exception type, message after redaction, stack, cause, and suppressed exceptions.
- Group by stable fingerprint, not raw message containing identifiers.
- Rate-limit duplicate reports while retaining occurrence counts.
- Attach release and deployment markers to spot regressions.
- Link alerts to a dashboard, runbook, owner, and rollback procedure.
Report an exception once at the boundary that can add operational context or choose recovery. Logging and rethrowing at every layer creates duplicates. If a lower layer handles and recovers from a failure, log at an appropriate level only when the event is still operationally meaningful.
12. Containerization and deployment concerns
A container image is a versioned runtime artifact; orchestration supplies identity, configuration, resources, rollout, and recovery.
FROM eclipse-temurin:21-jre
RUN addgroup --system spring && adduser --system --ingroup spring spring
WORKDIR /app
COPY --chown=spring:spring target/orders.jar app.jar
USER spring
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
Use a trusted minimal base, run as a non-root user, scan application and operating-system dependencies, rebuild for security updates, and pin an auditable image digest in deployment. Multi-stage builds can keep compilers and source out of the runtime image. Never put secrets in image layers or build arguments that become metadata.
Resource limits and JVM ergonomics
Container memory includes Java heap, metaspace, code cache, direct buffers, thread stacks, native libraries, memory-mapped regions, and monitoring overhead. Setting maximum heap equal to the container limit invites an operating-system kill. Leave measured native headroom. CPU limits can affect parallelism, garbage collection, timeouts, and startup. Load-test with the same limits used in production.
Safe rollouts
- Use immutable versions and expose the version in telemetry.
- Require readiness before routing and set a realistic progress deadline.
- Keep enough healthy capacity during rolling replacement.
- Use backward-compatible database migrations across old and new application versions.
- Watch error, latency, saturation, and business outcomes during canary or staged rollout.
- Define an automated or quick manual rollback, but remember data migrations may not roll back.
- Test termination, node loss, dependency outage, and telemetry backend failure.
13. SLOs, dashboards, and actionable alerts
Monitoring becomes operationally useful when it measures a promise, identifies risk, and routes a concrete action to an owner.
A service-level indicator is a measured ratio or distribution, such as successful eligible requests divided by total eligible requests, or the share of requests below a latency threshold. A service-level objective is the target over a window. The error budget is the allowed unreliability. Define eligibility and success from the user view, including which status codes, cancellations, maintenance, and client errors count.
| Dashboard layer | Purpose | Example panels |
|---|---|---|
| Service overview | Detect user impact. | Traffic, errors, latency distribution, availability SLI. |
| Saturation | Find constrained resources. | CPU, heap after GC, pool pending, executor queue, downstream limits. |
| Dependencies | Locate propagated failure. | Dependency latency, errors, retries, circuit state, database waits. |
| Deployment | Connect change to behavior. | Version, replica readiness, restart rate, rollout markers. |
Page on sustained user impact or imminent exhaustion that requires timely human action. Ticket slower trends such as disk growth or certificate expiry. Avoid alerts for every exception, CPU spike, or single failed probe. Use multi-window burn-rate alerts for SLOs so severe fast burns and slower persistent burns are both detected without constant noise.
14. A practical incident workflow
Troubleshooting should narrow uncertainty with time-correlated evidence, not begin with random restarts or configuration changes.
- Protect users. Roll back, disable a risky feature, shed optional work, fail over, or scale only when the action is safe and understood.
- State the symptom precisely. Name the operation, affected population, start time, severity, and evidence of recovery or continued impact.
- Check recent changes. Compare deployment, configuration, traffic, dependency, certificate, and infrastructure events to the symptom time.
- Follow the golden signals. Determine whether the problem is errors, latency, traffic shape, saturation, or several together.
- Localize. Compare versions, zones, instances, routes, tenants, and dependencies. Use traces to find where time or failure appears.
- Form one falsifiable hypothesis. Say what evidence would confirm or reject it, then run the smallest safe check.
- Mitigate and verify. Watch the original user signal, not only the component that was changed.
- Preserve evidence and learn. Record the timeline, contributing conditions, detection gap, safe corrective actions, owners, and deadlines.
Symptom-driven playbooks
| Symptom | First checks | Common deeper causes |
|---|---|---|
| Latency rises, CPU rises | Route latency, CPU profile, GC, allocation, recent traffic and release. | Hot algorithm, serialization, retry storm, excessive logging, allocation churn. |
| Latency rises, CPU stays low | Thread states, pool pending, dependency spans, database locks. | I/O wait, lock contention, exhausted pool, downstream timeout. |
Restarts and OOMKilled |
Container limit, last termination reason, heap and native memory trend. | Leak, burst allocation, direct buffer, too many threads, heap too close to limit. |
| Readiness flaps during traffic | Probe latency, dependency check, CPU throttling, GC pauses. | Overloaded probe path, external dependency in readiness, thresholds too strict. |
| Errors begin after rollout | Version split, configuration diff, schema compatibility, error fingerprint. | Bad release, mixed-version contract, missing secret, migration ordering. |
| Database acquisition timeouts | Active and pending pool metrics, transaction duration, DB sessions and locks. | Slow query, long transaction, network call inside transaction, pool multiplied by replicas. |
15. Testing production behavior
Production controls deserve automated tests and failure drills because their mistakes often appear only during deployment or outage.
- Test endpoint exposure and authorization, including anonymous denial of sensitive endpoints.
- Test custom health indicators for timeout, exception, degraded state, and redacted details.
- Test readiness transitions during startup and graceful shutdown.
- Use a test registry to assert meter name, bounded tags, count, and timing behavior.
- Capture logs to verify event level, required fields, correlation, and secret redaction.
- Verify trace propagation through HTTP clients, executors, async work, and message boundaries.
- Run the application with container limits and exercise startup, load, termination, and restart.
- Simulate slow, unavailable, and partially failing dependencies with bounded timeouts.
- Validate dashboards and alert queries against synthetic incidents or replayed data.
@Test
void recordsABoundedRejectionReason() {
SimpleMeterRegistry registry = new SimpleMeterRegistry();
CheckoutMetrics metrics = new CheckoutMetrics(registry);
metrics.recordRejected("unexpected-provider-text-123");
Counter counter = registry.get("checkout.rejected")
.tag("reason", "OTHER")
.counter();
assertThat(counter.count()).isEqualTo(1.0);
}
16. Security and performance implications
| Area | Risk | Production practice |
|---|---|---|
| Actuator | Information disclosure or operational mutation. | Explicit allowlist, least privilege, private network, audit access. |
| Logs and errors | Secret or personal-data leakage and ingestion overload. | Data classification, redaction, sampling, quotas, retention. |
| Metrics | Unbounded cardinality and expensive aggregation. | Bounded tag values, meter filters, series budgets. |
| Traces | Cost, propagation gaps, sensitive attributes. | Sampling, attribute policy, exporter limits, context tests. |
| Health checks | Probe storm or restart cascade. | Cheap bounded checks and correct failure semantics. |
| Diagnostics | Pause, disk exhaustion, sensitive heap contents. | Incident approval, capacity check, access control, deletion policy. |
| Secrets | Long-lived credential theft. | Workload identity, short lifetime, rotation, audited access. |
17. Common mistakes and corrections
| Mistake | Why it fails | Correction |
|---|---|---|
| Expose every Actuator endpoint publicly. | Diagnostics reveal internals and some endpoints change state. | Expose the minimum and authorize explicitly. |
| Put PostgreSQL in liveness. | A shared outage restarts every healthy process and increases load. | Keep liveness internal; model dependency impact separately. |
| Use average latency only. | A small slow population disappears inside the mean. | Use distributions, percentiles, thresholds, and counts. |
| Tag metrics with request or customer IDs. | Each value creates new time series. | Use bounded metric tags and place IDs in traces or safe logs. |
| Log every exception in every layer. | One failure becomes duplicate noise and higher cost. | Report once at the useful boundary with stable context. |
| Increase the database pool when it saturates. | More concurrency can overload the database and worsen latency. | Find hold time, query, lock, and capacity causes first. |
| Set heap equal to container memory. | Native memory has no headroom and the process is killed. | Measure all memory categories and reserve native headroom. |
| Call a restart the root-cause fix. | It removes evidence and the condition may return. | Use restart only as mitigation, then preserve and analyze evidence. |
| Assume graceful HTTP shutdown handles background work. | Consumers, jobs, and executors have separate admission paths. | Drain each source and make interrupted work retry-safe. |
| Create alerts without owners or runbooks. | Humans receive noise with no safe next action. | Attach impact, owner, evidence, urgency, and response procedure. |
18. Practice exercises
- Design Actuator exposure for a public API. State which endpoints are public, which are private, which are disabled, and how each layer is protected.
- A Redis outage makes liveness fail and Kubernetes restarts 100 pods. Explain the feedback loop and redesign liveness, readiness, and dependency alerting.
- Define availability and latency SLIs for checkout. Specify eligible events, success, latency boundary, labels, and how retries count.
- Create Micrometer meters for a file import. Include completion outcome, item count, duration, and active imports without unbounded tags.
-
Trace an HTTP request that enters Spring MVC, calls an
@Asyncmethod, and invokes a payment service. Identify every context propagation boundary. - Write a termination timeline for an API plus Kafka consumer. Fit readiness, listener stop, in-flight work, telemetry flush, and forced termination into one budget.
- Diagnose rising latency with low CPU, a full Hikari pool, and many threads waiting for connections. List evidence that separates slow SQL, lock waits, and remote calls in transactions.
- Plan collection of a heap dump from a production container. Include disk capacity, security, expected pause, transfer, analysis, retention, and deletion.
19. Interview questions and answers
What is the difference between monitoring and observability?
Monitoring checks known conditions through predefined signals and alerts. Observability is the ability to infer internal state from outputs, including investigating failures that were not predicted. Good systems need both: clear known alerts and enough correlated telemetry for new questions.
What does Spring Boot Actuator provide?
It provides production endpoints, health contributor infrastructure, Micrometer integration, application availability state, auditing and management support, and auto-configuration around runtime features. Endpoint enablement, exposure, and authorization remain explicit operational decisions.
How do liveness and readiness differ?
Liveness means the process cannot recover and should be restarted. Readiness means the instance should or should not receive new traffic. A dependency outage normally affects capability or readiness, not liveness, because restarting the process does not repair the dependency.
Why should liveness exclude external dependencies?
A shared dependency failure would cause every application instance to restart simultaneously. That removes otherwise responsive capacity, increases startup and dependency load, and can create a restart loop. Liveness should detect internal unrecoverable process states.
What is Micrometer?
Micrometer is a facade for application metrics and an observation API used by Spring Boot for metrics and tracing integration. Code uses common meter types and a registry publishes them to a chosen backend.
Why is metric tag cardinality important?
A unique tag-value combination creates a distinct time series. Unbounded values such as request IDs or customer IDs can exhaust memory, increase monitoring cost, and make queries unusable. Metrics need stable bounded dimensions.
How are logs, metrics, and traces used together?
Metrics detect that a population is unhealthy. Traces localize latency and errors for sampled operations across components. Correlated structured logs explain detailed decisions and events. Time, service version, trace IDs, and stable operation names connect them.
What is an Observation in Micrometer?
It is a named lifecycle around an operation with context and key values. Observation handlers can produce metrics, spans, context propagation, and other effects. Low-cardinality values may feed metrics and traces; high-cardinality values should remain trace-only.
How do you investigate a Spring application with high latency and low CPU?
Check latency by route and dependency, thread dumps, database pool pending and acquisition time, executor queues, database locks and sessions, downstream spans, timeouts, and connection state. Low CPU suggests waiting or contention more than computation.
Why can a full connection pool be a symptom rather than a root cause?
Connections stay active because transactions or queries take too long. The cause may be slow SQL, lock waits, network calls inside a transaction, database saturation, or leaked resources. Increasing pool size can only increase database concurrency and worsen the condition.
What does graceful shutdown require besides enabling a property?
It requires traffic draining, aligned platform and application timeouts, stopping every work admission source, bounded completion, retry-safe interrupted work, correct resource close order, and deployment tests using real termination signals.
When would you take a thread dump, heap dump, or JFR recording?
Use repeated thread dumps for blocking, deadlock, and current stack evidence. Use heap histograms and dumps for retained-object or leak analysis. Use JFR for time-based CPU, allocation, GC, lock, and I/O evidence. Heap dumps have the greatest storage and data-sensitivity risk.
How should secrets reach a Spring Boot application?
Prefer a secret manager or platform-mounted secret with workload identity, least privilege, encryption, rotation, and audited access. Import or bind it at runtime, prevent it from entering images and logs, and define how rotation reaches live instances.
What makes an alert actionable?
It identifies real or imminent user impact, has an owner and urgency, contains evidence and useful scope, links to a dashboard and runbook, and offers a safe first action. If no timely human action is required, it is usually a ticket or dashboard signal rather than a page.
20. Concise cheat sheet
- Actuator endpoint enabled does not mean exposed; exposed does not mean safely authorized.
- Expose only required management endpoints and keep sensitive details private.
- Liveness asks restart; readiness asks route traffic; startup protects initialization.
- Keep health checks cheap, bounded, non-mutating, and operationally meaningful.
- Begin metrics with traffic, errors, latency distributions, and saturation.
- Never use request, customer, order, message, or raw URL values as metric tags.
- Use structured events and trace IDs; never emit secrets or unnecessary personal data.
- Propagate observation context across executors, async work, reactive code, and network calls.
- Sample traces by a deliberate cost and diagnostic policy.
- Bind validated configuration and fail startup for missing required values.
- Keep secrets out of source, images, logs, diagnostics, and command history.
- Fit application drain time inside the platform termination grace period.
- Stop every source of work, not only HTTP admission.
- Pool saturation requires hold-time diagnosis before a size increase.
- Capture repeated thread dumps; treat heap dumps as sensitive production data.
- Leave native memory headroom below a container limit.
- Correlate symptoms with releases, configuration, traffic, and dependencies.
- Alert on actionable user impact or imminent exhaustion, with an owner and runbook.
21. Official references
- Spring Boot production-ready features
- Spring Boot Actuator endpoints and health
- Spring Boot metrics
- Spring Boot observability and context propagation
- Spring Boot tracing
- Spring Boot logging and structured logging
- Spring Boot externalized configuration
- Spring Boot graceful shutdown
- Micrometer reference documentation
- Micrometer Tracing reference
- Kubernetes liveness, readiness, and startup probes
- Docker build best practices
- Java troubleshooting guide