exit lab
Backend Engineering
Level 4·spring·lab·beginner

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.

480 minutes Java 21, Spring Boot 3.5.16, MyBatis starter 3.x, PostgreSQL 16+ Updated 2026-07-16
Java 21Spring Boot 3.5Spring MVCMyBatisPostgreSQLDocker

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

HTTP client
Spring MVC
Task service
MyBatis mapper
PostgreSQL
Actuator + logs
Text alternative: An HTTP client calls Spring MVC. A Task service owns business rules and transactions, a MyBatis mapper accesses PostgreSQL, and Actuator plus structured logs expose operational signals.

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
EndpointBehavior
POST /v1/tasksCreate; 201 + Location
GET /v1/tasks/{id}Owner-scoped read
GET /v1/tasks?cursor=&limit=Stable keyset page
POST /v1/tasks/{id}/completionConditional 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.