Java

Java and Spring Boot Production Checklist

A risk-based Spring Boot go-live review covering configuration, APIs, transactions, dependencies, observability, security, and recovery.

Rizky Romadon profile photoRizky Romadon··10 min read

Sources

Article details

Context for reading and verifying this note.

Updated Jul 20, 202610 min readJava7 sourcesAI-assisted, human-reviewed

Creation context: AI assisted with research, structure, or drafting. Rizky Romadon reviewed the sources, technical claims, examples, and final publishing decision.

How I Use This Checklist

A production-readiness review should not ask whether a Spring Boot application uses enough annotations. It should ask whether the team can explain the service's behavior when traffic, dependencies, data, and deployments stop behaving like the happy path.

I organize the review around risk. For each important user journey, I want to know:

  1. What state changes?
  2. Which dependency can delay or break it?
  3. What does the caller observe when it fails?
  4. What evidence tells operations that it is unhealthy?
  5. Can the change be disabled, rolled back, retried, or repaired safely?

The checklist below is not a promise that a service is production-ready because every box is checked. It is a structured way to expose assumptions before traffic makes them expensive.

First, Write the Service Contract in One Page

Before reviewing individual settings, I create a compact service profile.

ItemExample answer
Critical journeysCreate order, query status, consume payment result
Data ownedOrder state and idempotency record
Synchronous dependenciesCustomer and payment APIs
Asynchronous dependenciesOrder-event broker
Availability targetDefined by the owning team, not copied from a template
Recovery mechanismsRetry, replay, feature flag, data repair procedure
Sensitive dataCustomer identifiers and payment references
On-call evidenceDashboard, alert, logs, trace, runbook

This profile changes the rest of the review. A timeout value cannot be judged without the caller's deadline. A retry cannot be judged without the operation's idempotency. A readiness probe cannot be judged without knowing which dependency is actually required to serve traffic.

Configuration Must Fail Clearly

Configuration should express an environment contract. A missing signing key or invalid external URL should stop startup rather than fail during the first real request.

I prefer typed configuration properties with validation:

JAVA
@ConfigurationProperties("payments")
@Validated
public record PaymentProperties(
    @NotNull URI baseUrl,
    @NotNull Duration connectTimeout,
    @NotNull Duration readTimeout,
    @NotBlank String audience
) {}

The validation annotations come from jakarta.validation, not the older javax.validation namespace used before the Jakarta transition.

Review questions:

  • Are secrets obtained from an approved secret store or runtime injection mechanism?
  • Can production accidentally inherit development endpoints or credentials?
  • Are defaults safe when a property is omitted?
  • Are configuration changes versioned and auditable?
  • Does startup log the selected non-sensitive configuration clearly enough to diagnose an environment mismatch?
  • Are credentials, full URLs with tokens, and private keys excluded from logs and Actuator output?

Avoid scattering raw environment lookups across business code. A typed boundary makes units, defaults, and required values reviewable.

Validate the API at Three Boundaries

Request validation is more than adding @Valid to a controller. I separate three kinds of validation.

Shape validation

This belongs at the transport boundary: required fields, string length, numeric ranges, and syntactic formats.

JAVA
public record CreateTransferRequest(
    @NotNull UUID sourceAccountId,
    @NotNull UUID destinationAccountId,
    @NotNull @DecimalMin("0.01") BigDecimal amount,
    @NotBlank @Size(max = 140) String reference
) {}

Business validation

Rules such as account state, available balance, ownership, and transaction limits belong in an application or domain service. They often require current data and authorization context.

State-transition validation

The service must protect transitions such as PENDING -> COMPLETED from duplication, stale updates, or invalid order. A DTO annotation cannot enforce concurrency or workflow state.

I also check request-size limits, pagination bounds, supported media types, identifier normalization, and whether the API distinguishes malformed input from a valid request that conflicts with current state.

Error Responses Are a Public Contract

Spring supports ProblemDetail, and RFC 9457 defines the current Problem Details format for HTTP APIs, superseding RFC 7807. A consistent problem response can include type, title, status, detail, and instance, plus carefully chosen extensions.

The production review is less about choosing one class and more about consistency:

  • Can clients distinguish validation, authentication, authorization, conflict, rate limiting, dependency failure, and unexpected failure?
  • Are internal exception names and stack traces absent from responses?
  • Are error codes stable enough for clients to act on?
  • Is the same correlation identifier available to the client and logs without exposing sensitive data?
  • Does a 404 versus 403 response accidentally reveal whether a protected resource exists?

My detailed review of Spring Boot production error handling covers handler structure and response design. The go-live gate should test those responses from the outside, not only unit-test handler methods.

Transaction Boundaries Need a Failure Story

@Transactional is not a distributed transaction. Spring Framework's declarative transaction management can manage supported local resources and rollback rules, but it does not propagate the transaction context across an ordinary remote API call.

This shape deserves scrutiny:

JAVA
@Transactional
public void submitOrder(OrderRequest request) {
    Order order = orderRepository.save(mapOrder(request));
    paymentClient.charge(order.paymentRequest());
    eventPublisher.publish(order.toEvent());
}

Several questions are hidden inside it:

  • What happens to the database transaction while payment is slow?
  • What happens if payment succeeds but the database transaction later rolls back?
  • What happens if the event publish fails after payment succeeds?
  • Can a caller retry without charging twice?
  • Which identifier connects all three operations?

A safer design depends on the required consistency. It may use explicit states, an idempotency key, an outbox, compensation, or an asynchronous workflow. The checklist should not prescribe one pattern without the domain requirement.

For every write path I review:

  • transaction scope and isolation assumptions;
  • rollback behavior for checked and unchecked exceptions;
  • optimistic or pessimistic concurrency where required;
  • uniqueness constraints that protect invariants;
  • remote calls inside the transaction;
  • event publication relative to commit;
  • repair behavior after a partial outcome.

Outbound Calls Need Budgets, Not Just Timeouts

An outbound HTTP call consumes part of the caller's total deadline. If a request has a two-second user-facing target, three dependencies cannot each wait two seconds and still satisfy it.

Current Spring Boot HTTP client configuration supports shared and client-group settings for connection and read timeouts. A representative configuration is:

YAML
spring:
  http:
    clients:
      connect-timeout: 500ms
      read-timeout: 2s
      redirects: dont-follow

Those numbers are examples, not universal recommendations. I select them from measured latency, caller deadlines, and dependency behavior.

The dependency review covers:

ConcernEvidence I want
Connection budgetExplicit connect timeout and pool acquisition behavior
Response budgetRead timeout aligned with the end-to-end deadline
Retry safetyIdempotency plus bounded attempts and backoff
CapacityPool size, queueing, and saturation metric
PayloadRequest and response size limits
RoutingApproved hosts, redirects, DNS, and SSRF controls
IdentityCredential scope, rotation, and transport security

Retries are not automatically resilient. A retry can multiply load during an outage or repeat a side effect. The service needs a reason for retrying, a stopping condition, and a metric that shows retry volume.

Database Readiness Goes Beyond Connectivity

A successful connection does not prove that the data path is ready.

I review:

  • migration ordering and compatibility with the currently deployed version;
  • index support for critical queries;
  • query plans with realistic data distribution;
  • connection-pool size relative to instance count and database capacity;
  • statement and lock timeouts;
  • lazy loading outside transaction boundaries;
  • large result sets and unbounded exports;
  • backup restoration and data-repair procedures.

For risky schema changes, I prefer compatibility across deploys: add the new shape, deploy compatible code, migrate or backfill with observation, switch reads, and remove the old shape later. “Rollback” cannot restore data that a destructive migration already removed.

Health Checks Must Match Platform Decisions

Spring Boot Actuator provides production features including health, metrics, auditing, and management endpoints. Its liveness and readiness health groups can map to platform probes.

The distinction matters:

  • Liveness asks whether the process is stuck and should be restarted.
  • Readiness asks whether this instance should receive new traffic.

Spring Boot's documentation intentionally avoids adding every external dependency to readiness by default. If all instances become unready because one shared database is unavailable, the platform may remove every endpoint without improving the database. The correct dependency set depends on how the application can degrade and how traffic is routed.

Review items:

  • Keep management endpoints authenticated or isolated appropriately.
  • Expose only the endpoints operations actually needs.
  • Ensure health details do not reveal credentials, hosts, or internal topology.
  • Test startup, readiness transition, and graceful shutdown under the real platform.
  • Confirm in-flight requests have enough time to finish during termination.

Observability Should Answer User-Journey Questions

OpenTelemetry distinguishes traces, metrics, and logs because each signal answers a different question. I do not consider a service observable merely because all three are emitted.

For each critical journey, I want:

  • a success and failure counter;
  • a latency distribution rather than only an average;
  • dependency latency and error classification;
  • saturation signals for pools, executors, and queues;
  • trace propagation across supported boundaries;
  • structured logs for important decisions and state changes;
  • a business signal that confirms the feature completed its purpose.

Avoid user IDs, request IDs, or raw URLs as unrestricted metric labels. High-cardinality attributes increase telemetry cost and can exhaust aggregation capacity. My article on Java production metrics goes deeper into histograms, percentiles, and service-level objectives.

Security Review the Paths That Change State

The REST security review should cover more than authentication middleware.

I trace one allowed and one denied request through:

  1. identity validation;
  2. authorization at the business resource;
  3. input validation;
  4. state change and audit record;
  5. response filtering;
  6. logging and telemetry redaction.

Then I review high-risk boundaries:

  • object-level authorization;
  • mass assignment and over-posting;
  • injection into queries, templates, or commands;
  • unrestricted outbound URLs and redirects;
  • secrets and personal data in logs;
  • rate and resource limits;
  • dependency and container vulnerability management;
  • administrative and repair endpoints.

For services that fetch user-influenced URLs, see my Spring Boot SSRF mitigation playbook.

Test Recovery, Not Only Startup

A production checklist becomes useful when it results in an exercise.

Before go-live, I would run representative failure tests such as:

  • the database accepts connections but a critical query times out;
  • the payment API returns slowly, then fails;
  • the broker is unavailable after a database commit;
  • a duplicate request arrives concurrently;
  • one application instance is terminated during a request;
  • the new database field exists but backfill is incomplete;
  • a feature flag must be disabled;
  • the previous application version is redeployed against the new schema.

For each test, record the user-visible outcome, telemetry, alert, operator action, and data-repair requirement. A runbook that has never been exercised is still a hypothesis.

My Final Go-Live Gate

I use this short gate for the final conversation:

GatePassing evidence
ContractCritical journeys, owners, and dependencies are named
DataMigrations and partial-failure behavior are understood
DependencyDeadlines, retries, pools, and credentials are bounded
APIValidation, authorization, and error contracts are tested
ObservationDashboards and alerts reflect user impact
DeploymentReadiness, shutdown, and compatibility are verified
RecoveryRollback, disablement, replay, or repair has an owner

If one gate cannot pass, I want an explicit risk acceptance and follow-up owner rather than a silent assumption.

Closing Principle

Spring Boot provides strong building blocks, but production readiness is the argument that connects them to a real service contract.

The checklist succeeds when configuration fails clearly, state transitions remain explainable, dependencies consume bounded budgets, telemetry reflects user journeys, and recovery does not depend on one engineer's memory. Defaults are useful starting points. Evidence is what makes them production decisions.

Related posts