Lab: Build a Spring Boot Task API
Build a Task API from Spring Initializr and PostgreSQL schema through MyBatis, validation, errors, transactions, tests, Docker, CI, deployment, and operations.
Learning objectives
- Build one complete backend vertical slice with intentional layer boundaries
- Carry the same feature through data correctness, tests, packaging, delivery, and operations
Outcome
You will build a credential-free local Task API with create, read, keyset list, complete, and delete behavior. The final artifact has validation, RFC 9457 errors, ownership, transactions, PostgreSQL integration tests, a non-root container, health checks, CI gates, and a deployment/rollback runbook.
Lab architecture
Product and API requirements
- An authenticated user can create, read, list, complete, and delete only their tasks
- Title is required and at most 160 characters; dueAt is an optional instant
- Completion is allowed only from OPEN and is idempotent at the state level
- List order is createdAt DESC, id DESC with opaque keyset cursor
- Every expected failure has a stable machine code and traceId
- No test or local build requires production credentials
| Endpoint | Behavior |
|---|---|
| POST /v1/tasks | Create; 201 + Location |
| GET /v1/tasks/{id} | Owner-scoped read |
| GET /v1/tasks?cursor=&limit= | Stable keyset page |
| POST /v1/tasks/{id}/completion | Conditional OPEN → DONE transition |
| DELETE /v1/tasks/{id} | Owner-scoped deletion policy |
Definition of done
- Schema constraints are the final correctness boundary
- Controllers contain no business or SQL logic
- Mapper SQL uses bound parameters and explicit columns
- Rollback, authorization, keyset order, and concurrent completion are tested
- Image runs as a non-root user and exits gracefully on SIGTERM
- A fresh environment can deploy, smoke-test, observe, and roll back the documented artifact
Milestones
Complete these in order. Each milestone has a verifiable outcome, not only a task list.
1. Bootstrap and configuration
Goal: Create a reproducible Java 21 Spring Boot application with explicit typed configuration.
Tasks
- Generate Maven project with Web, Validation, Security, Actuator, PostgreSQL, MyBatis, and test dependencies
- Add feature-based packages
- Bind application properties for page limits and security settings
- Create local/test/prod profiles without secrets
Acceptance criteria
- Application starts with Java 21
- Invalid required configuration fails startup with a useful message
- Repository contains no credential
Tests
- Context smoke test with test configuration
- Configuration binding validation test
Common risks
- Copying dependency versions that conflict with the Spring Boot BOM
- Committing local database passwords
Production notes
- Boot 3.5 requires Java 17+; this lab standardizes on Java 21
- MyBatis starter 3.x supports Boot 3.2–3.5
2. Schema and migration
Goal: Make PostgreSQL protect task identity, ownership, valid status, title, and stable list access.
Tasks
- Create app_user and task migrations
- Add named primary, foreign, check, and not-null constraints
- Add owner/status/created/id list index
- Seed only test/local identities
Acceptance criteria
- Invalid title, status, or owner fails at the database
- Keyset list query can use the intended index
- Migration applies to an empty database
Tests
- Migration integration test
- Constraint failure tests
- Representative EXPLAIN review
Common risks
- Application-only enum validation
- Index order that does not match predicates and ordering
Production notes
- Use a migration tool in the implementation; run migrations as a controlled deployment job
- Design future changes with expand/contract compatibility
3. Controller, service, and MyBatis vertical slice
Goal: Implement create and owner-scoped read through every layer.
Tasks
- Define request/response records
- Implement thin controller
- Implement service use cases and mapping
- Create typed mapper interface, resultMap, insert, and select SQL
Acceptance criteria
- POST returns 201 and Location
- GET returns the expected owner-scoped task
- SQL has explicit columns and bound parameters
Tests
- Service unit tests
- MockMvc create/read contract tests
- PostgreSQL mapper integration tests
Common risks
- Returning TaskRow as the API response
- Using raw substitution in mapper XML
Production notes
- Interpret affected rows and missing ownership deliberately to avoid information disclosure
4. Validation and error contract
Goal: Create predictable syntax, business, conflict, not-found, and infrastructure failure behavior.
Tasks
- Add Jakarta Bean Validation
- Add business-state checks
- Implement @RestControllerAdvice with ProblemDetail
- Add stable codes, field errors, instance, and traceId
Acceptance criteria
- Malformed input is 400
- Unknown or inaccessible task follows the chosen 404 policy
- Unexpected failures do not leak stack or SQL details
Tests
- Validation matrix
- Problem-details JSON contract test
- Unexpected exception redaction test
Common risks
- Duplicating handlers per controller
- Logging the same exception at every layer
Production notes
- RFC 9457 obsoletes RFC 7807; keep extension fields stable and documented
5. Transactions, list, and concurrency
Goal: Implement stable pagination and an atomic OPEN-to-DONE transition.
Tasks
- Add keyset list query and opaque cursor codec
- Add transactional completion use case
- Use conditional UPDATE with owner and expected status
- Choose repeated-completion response semantics
Acceptance criteria
- Pages contain no duplicates with equal timestamps
- Concurrent completion never creates invalid state
- Rollback leaves no partial audit row
Tests
- Keyset pagination integration test
- Rollback test
- Bounded concurrent completion test
Common risks
- Offset paging under concurrent inserts
- Read-then-write race without conditional SQL
Production notes
- Keep the transaction short and do not send notifications inside it
6. Security and ownership
Goal: Authenticate test users and enforce authorization at request, method, and query boundaries.
Tasks
- Configure SecurityFilterChain
- Choose session authentication for the lab browser client
- Enable CSRF for unsafe cookie-authenticated requests
- Resolve actor identity without trusting request ownerId
Acceptance criteria
- Anonymous requests are 401
- Wrong owner cannot read or mutate a task
- CSRF-less unsafe browser request is rejected
- Admin behavior is explicitly scoped
Tests
- Spring Security MockMvc matrix
- Horizontal-access integration test
- Session fixation/logout tests
Common risks
- Accepting ownerId from request body
- Disabling CSRF because responses are JSON
Production notes
- A real deployment needs account lifecycle, adaptive password hashes, abuse controls, and secret rotation
7. Container and CI
Goal: Create one reproducible non-root artifact that passes automated quality gates.
Tasks
- Add multi-stage Dockerfile and .dockerignore
- Run as explicit non-root UID
- Add compile, unit, integration, build, and scan gates
- Document full-SHA pinning for third-party actions
Acceptance criteria
- Image contains the runtime artifact, not source or build cache
- Container process is non-root
- CI needs no production secret
- Failed tests prevent image promotion
Tests
- Container starts and serves readiness
- Image user inspection
- CI workflow validation
Common risks
- Mutable latest image identity
- Broad workflow permissions
- Docker health check replacing orchestrator readiness design
Production notes
- Promote by digest and automate reviewed base-image updates
8. Deploy, observe, and recover
Goal: Deploy with safe migration, health, smoke, logging, metrics, alerts, graceful shutdown, and rollback evidence.
Tasks
- Configure Nginx or platform routing and HTTPS
- Secure Actuator and separate readiness/liveness
- Add structured correlation logs and core metrics
- Write deployment, smoke, database-pool incident, and rollback runbooks
Acceptance criteria
- Only ready instances receive traffic
- SIGTERM drains accepted work
- Smoke test verifies create/read/list/complete
- Previous image can be restored without schema incompatibility
Tests
- Post-deploy smoke test
- Probe failure simulation
- Pool-exhaustion drill
- Rollback exercise
Common risks
- Exposing all Actuator endpoints publicly
- Alerting without an owner or action
- Assuming backup exists without restore evidence
Production notes
- Record dashboard, alert, restore, and rollback evidence in the project README or runbook
Retrieval check
Before you continue
- Explain the main idea in your own words without rereading the page.
- Name one production problem this knowledge helps prevent.
- Describe where you would apply it in a Spring Boot project.
Source metadata
Primary references checked on 2026-07-16. Version scope is stated at the top of this page.
Spring Boot 3.5.16 System Requirements (opens in a new tab)
Spring · official-docs
Annotated Controllers (opens in a new tab)
Spring · official-docs
MyBatis Spring Boot Starter Reference (opens in a new tab)
MyBatis · official-docs
PostgreSQL 18 Documentation (opens in a new tab)
PostgreSQL Global Development Group · official-docs
Testing Spring Boot Applications (opens in a new tab)
Spring · official-docs
Docker Build Best Practices (opens in a new tab)
Docker · official-docs