Stage 20 - Production Checklist for Spring Boot APIs
Backend code becomes hard to maintain when important behavior is hidden in random controller methods. This article focuses on health checks, graceful shutdown, and Docker config. The topic looks technical, but the real question is practical: what happens when a real client sends a real request and something is incomplete, too large, forbidden, slow, or inconsistent?
A service is production-ready when it can be started, stopped, monitored, diagnosed, and rolled back without guesswork. The checklist turns hidden operational assumptions into visible requirements.

The picture to keep in your head
Think about a small online shop. A user opens the frontend, clicks a button, and the browser calls the backend. The backend receives HTTP data, converts it into Java objects, checks rules, touches the database, and returns JSON. If each step has a clear owner, the system is understandable. If every step is mixed in one method, the first production incident becomes painful.
For this topic the sequence is:
- Build image.
- run with env config.
- health check.
- metrics.
| Part | Responsibility |
|---|---|
| Controller | Receives HTTP data and returns the public response. |
| Service | Runs the business use case and protects business rules. |
| Repository/adapter | Talks to persistence or external systems. |
| DTO/contract | Defines what the outside world may send or receive. |
Concrete Spring example
management.endpoint.health.probes.enabled=true
management.endpoints.web.exposure.include=health,info,metrics,prometheus
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=30s
# Docker memory should be explicit in deployment, not guessed after an outage.
The code is intentionally small because the important part is the boundary. The controller should not quietly decide business rules that belong in the service. The service should not depend on servlet objects. The repository should not know which JSON field name the frontend expects. When the boundary is clean, the same behavior can be documented, tested, and changed without touching every layer.
Why it matters
In a demo project, many shortcuts look harmless. Returning all records is fine when there are five rows. Returning an entity is fine until it contains a hidden field. Logging without a request id is fine until several users fail at the same time. Running against a manually created database is fine until staging has a different schema. Production work is mostly about removing these hidden assumptions before they become incidents.
The simplest useful rule is this: make the behavior explicit at the edge, enforce it in the right layer, and keep the public contract stable. If the frontend knows the request format, response format, and error behavior, it can work confidently. If tests exercise the same contract, refactoring becomes safer. If logs and configuration reflect the same design, operations become less dependent on guesswork.
Common mistakes
- Putting the whole use case into a controller method.
- Letting persistence details leak into the API contract.
- Handling the happy path but leaving failure behavior undefined.
- Using a local shortcut that cannot work in staging or production.
Understanding checklist
- I can draw the sequence for this topic from request to response.
- I can explain which layer owns the decision.
- I can name the production problem this topic prevents.
Self-check questions
- What happens if this rule is implemented differently in every endpoint?
- Which part of the behavior should be documented for API clients?
- Which test would prove the rule works, not only that the happy path works?