Photo by Trnava University / Unsplash

Spring Boot Application Template: Documentation

Spring Boot Oct 1, 2026

This page documents the Spring Boot Application Template, a repository you can fork with everything already set up to bootstrap a monolithic Spring Boot web application: a server-rendered web UI and a secured REST API sharing one database. Delete the sample code (or keep it) and add your own.

The detailed reference lives in the repository's documents folder; this page is the overview.

Contents

Features

  • Spring Boot 4.1 on Java 21, Spring Security 7, Spring Data JPA, Thymeleaf with Bootstrap 5
  • Web UI with form login, remember-me, CSRF protection, dark mode and internationalization (English, Spanish)
  • REST API secured with RS256 signed JWT access tokens and rotating, single-use refresh tokens, or HTTP Basic
  • Sign-up with e-mail verification, login throttling per client IP
  • Role and permission based access control, RBAC user management API
  • API rate limiting per API key with Bucket4j, HATEOAS links, RFC 9457 problem details for errors
  • Flyway migrations for H2 and MySQL, JPA auditing, caching
  • OpenAPI 3 / Swagger UI, Actuator with Prometheus metrics
  • Docker image and Docker Compose with MySQL, GitHub Actions CI, integration tests, JaCoCo coverage

Technology stack

Area Technology
Language and build Java 21, Maven (wrapper included)
Framework Spring Boot 4.1 (Spring Framework 7)
Security Spring Security 7, OAuth2 resource server (JWT, RS256)
Persistence Spring Data JPA, Hibernate 7, Flyway, MySQL 8, H2
Web Spring MVC, Thymeleaf, Thymeleaf Layout Dialect, Bootstrap 5, MDB UI Kit, Bootstrap Table
API springdoc-openapi, Spring HATEOAS, Bucket4j, ModelMapper
Operations Spring Boot Actuator, Micrometer / Prometheus, Docker, GitHub Actions, JaCoCo

Getting started

Requirements: Java 21. Nothing else is needed for the default test profile, which uses an in-memory H2 database that Flyway creates and seeds on startup.

git clone https://github.com/AnanthaRajuC/Spring-Boot-Application-Template.git
cd Spring-Boot-Application-Template
./mvnw spring-boot:run        # http://localhost:8080/sbat/index
./mvnw verify                 # tests + coverage report in target/site/jacoco/index.html

On Windows use mvnw.cmd. To build and run the jar instead:

./mvnw package -DskipTests
java -jar target/spring-boot-application-template-latest.jar
What Where
Web UI http://localhost:8080/sbat/index
Swagger UI / OpenAPI document (not in production) /swagger-ui.html, /v3/api-docs
H2 console (test profile, localhost only) /h2-console, JDBC URL jdbc:h2:mem:sbat, user sa, empty password
Health /actuator/health

Configuration

Shared settings live in application.properties; the test, dev, qa, staging and production profile files only override what differs. Everything environment specific is read from environment variables, or from a git-ignored .env file in the project root (copy .env.example).

Variable Purpose
SPRING_PROFILES_ACTIVE test (default, H2), dev, qa, staging, production (MySQL)
DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD MySQL connection
JWT_KEY_STORE, JWT_KEY_STORE_PASSWORD, JWT_KEY_ALIAS RSA key used to sign access tokens. Required in production: without it a temporary key is generated at startup and tokens stop working on restart
REMEMBER_ME_KEY Secret for remember-me cookies, random per start when unset
MAIL_HOST, MAIL_PORT, MAIL_USERNAME, MAIL_PASSWORD SMTP server for sign-up verification e-mails (Mailtrap works well for testing)
BASE_URL Public URL used in verification links
FORWARD_HEADERS_STRATEGY native or framework behind a reverse proxy, so the client IP and https are detected

Create the JWT signing key store once, and keep it out of the repository:

mkdir -p secrets
keytool -genkeypair -alias sbat -keyalg RSA -keysize 2048 -validity 3650 \
        -storetype PKCS12 -keystore secrets/jwt.p12 -dname "CN=sbat"
JWT_KEY_STORE=file:secrets/jwt.p12
JWT_KEY_STORE_PASSWORD=the-password-you-chose
JWT_KEY_ALIAS=sbat

Earlier versions of the repository committed a key store (redditclone.jks). It has been removed and must never be reused.

Default accounts

Seeded by the Flyway migrations, all with the password password. Change or remove them before exposing an instance.

Username Role Permissions
johndoe, janedoe ROLE_PERSON PERSON_CREATE, PERSON_READ, PERSON_UPDATE, PERSON_DELETE
Admin1, Admin2 ROLE_ADMIN PERSON_*, RBAC_USER_CREATE, RBAC_USER_READ
AdminTrainee1, AdminTrainee2 ROLE_ADMINTRAINEE PERSON_READ, RBAC_USER_CREATE

Security

Two Spring Security filter chains are configured:

Web UI REST API (/api/**, /rbac/**, /actuator/**)
Login Form login at /sbat/login, optional remember-me (21 days) POST /api/v1/auth/login returns a JWT, or HTTP Basic
State HTTP session, 10 minute timeout, one session per user Stateless: Authorization: Bearer <token> on every request
CSRF protection On Not needed (header based authentication)
Failure Redirect to the login page or /403 401 / 403
  • Roles and permissions are read from the database on every request, also for JWT authenticated requests, so changes take effect immediately.
  • After 5 failed logins from the same IP address, that address is blocked for 15 minutes (configurable). Behind a proxy, set FORWARD_HEADERS_STRATEGY; otherwise X-Forwarded-For is ignored so it cannot be spoofed.
  • Only /actuator/health and /actuator/info are public; every other actuator endpoint requires ROLE_ADMIN.

Authentication API

URL Method Remarks
/api/v1/auth/signup POST Creates a disabled account with ROLE_PERSON and sends a verification e-mail
/api/v1/auth/verification/{token} GET Enables the account; expired links remove the unverified account
/api/v1/auth/login POST Returns an access token and a refresh token
/api/v1/auth/refresh/token POST Exchanges a refresh token for a new pair
/api/v1/auth/logout POST Deletes a refresh token
# Login
curl -s -X POST http://localhost:8080/api/v1/auth/login \
     -H 'Content-Type: application/json' \
     -d '{"username":"johndoe","password":"password"}'
{
    "authenticationToken": "eyJhbGciOiJSUzI1NiJ9...",
    "refreshToken": "3d0ca7a8-04c5-4bb2-8fe4-0e26b06c6ef1",
    "expiresAt": "2026-10-01T10:24:59Z",
    "username": "johndoe"
}
# Call the API with the access token
curl -s http://localhost:8080/api/v1/user/username/johndoe -H "Authorization: Bearer $ACCESS_TOKEN"

# Exchange the refresh token for a new pair
curl -s -X POST http://localhost:8080/api/v1/auth/refresh/token \
     -H 'Content-Type: application/json' -d "{\"token\":\"$REFRESH_TOKEN\"}"
  • Access tokens are valid for 5 minutes by default (jwtExpirationTime application setting).
  • Refresh tokens are single use: every refresh returns a new one and the old one stops working. They expire after 7 days (sbat.security.refresh-token-validity).
  • A new access token is always issued for the owner of the refresh token.
  • Sign-up validates the input: a valid e-mail address, a username of 3 to 50 characters and a password of 8 to 100 characters.

Roles, permissions and endpoints

URL Method Requires
/api/v1/person, /api/v1/person/id/{id} GET, POST, PUT, DELETE PERSON or ADMIN, with the matching PERSON_* permission
/api/v1/person/username/{username} GET The user themselves: persons this user created
/api/v1/management/person, /id/{id}, /pageable GET ADMIN or ADMINTRAINEE
/api/v1/management/person, /id/{id} POST, PUT, DELETE ADMIN or ADMINTRAINEE, with the matching PERSON_* permission
/api/v1/user/username/{username} GET The user themselves
/rbac/user, /rbac/user/{username} GET, POST, DELETE ADMIN
/actuator/prometheus, /actuator/metrics GET ADMIN

The RBAC API creates users with roles referenced by name. A role that doesn't exist yet is created, with <RESOURCE>_CREATE, _READ, _UPDATE and _DELETE permissions when none are given. Passwords are stored as BCrypt hashes and never returned.

{
    "username": "trainer1",
    "email": "trainer1@example.com",
    "password": "a-strong-password",
    "enabled": true,
    "accountNonExpired": true,
    "accountNonLocked": true,
    "credentialsNonExpired": true,
    "roles": [ { "name": "ROLE_PERSON" }, { "name": "ROLE_COURSE" } ]
}

API rate limiting

Requests to the person APIs, /api/v1/person/** and /api/v1/management/person/**, must carry an X-api-key header. The key's prefix selects the tier; each key gets its own bucket that refills every 20 minutes.

Tier Requests per 20 minutes API key prefix
FREE 25 any other value
BASIC 50 BX001-
PROFESSIONAL 75 PX001-

Responses include X-Rate-Limit-Remaining. An empty bucket gives 429 Too Many Requests with X-Rate-Limit-Retry-After-Seconds. A missing header gives 400.

Errors

Status Meaning
400 Bad Request Invalid request body, or a missing X-api-key header on rate limited endpoints
401 Unauthorized No token, an invalid or expired token, wrong credentials, a disabled account or a blocked client
403 Forbidden The user's role or permissions don't allow the request
404 Not Found The requested resource doesn't exist
429 Too Many Requests Rate limit exhausted

Errors raised by the REST controllers are returned as RFC 9457 problem details:

{
    "title": "Unauthorized",
    "status": 401,
    "detail": "Invalid Refresh Token",
    "instance": "/api/v1/auth/refresh/token"
}

Docker

The Dockerfile is a multi-stage build (eclipse-temurin:21-jdk to build, eclipse-temurin:21-jre to run as a non-root user), so no local Java is needed. docker-compose.yml starts MySQL 8.4 and the application in the production profile:

export DB_PASSWORD=... DB_ROOT_PASSWORD=... JWT_KEY_STORE_PASSWORD=... REMEMBER_ME_KEY=$(openssl rand -base64 32)
docker compose up --build

The JWT key store is mounted from ./secrets/jwt.p12. Without a database:

docker build -t spring-boot-application-template .
docker run --rm -p 8080:8080 -e SPRING_PROFILES_ACTIVE=test spring-boot-application-template

Deployment checklist

  • Use the production profile with MySQL. Swagger UI and /v3/api-docs are switched off in it.
  • Set JWT_KEY_STORE, JWT_KEY_STORE_PASSWORD, JWT_KEY_ALIAS and REMEMBER_ME_KEY.
  • Serve over HTTPS (reverse proxy or load balancer) and set FORWARD_HEADERS_STRATEGY.
  • Configure SMTP and BASE_URL for verification e-mails.
  • Change or delete the seeded accounts.
  • Health probes: /actuator/health/liveness and /actuator/health/readiness. The port can be set with PORT.

Testing and code coverage

./mvnw test      # run the tests
./mvnw verify    # tests + JaCoCo report in target/site/jacoco/index.html

The integration tests start the whole application on H2 and cover the web login and CSRF protection, JWT and role checks on the API, refresh token rotation, rate limiting, login throttling, RBAC user creation and the sign-up and verification lifecycle. GitHub Actions runs ./mvnw verify and a Docker image build on every pull request.

A Postman collection is available online and in the repository's documents folder.

Project structure

io.github.anantharajuc.sbat
├── core_backend            reusable building blocks, keep them in your project
│   ├── api                 resource paths, rate limiting
│   ├── email               verification e-mails
│   ├── infra               configuration (Jackson, OpenAPI, i18n, MVC), REST error handling
│   ├── monitoring          custom actuator endpoint
│   ├── persistence         auditing, base entities, repositories
│   ├── security            filter chains, JWT keys and tokens, users, roles, permissions
│   ├── service             application settings, sample outbound HTTP client
│   └── user                "current user" API
├── example.crm             sample person domain: delete it, or keep it as a reference
└── web                     web UI controllers

Resources: data/h2db/migrations and data/mysql/migrations hold the Flyway scripts, templates the Thymeleaf layout, fragments and pages, i18n the translated texts.

More documentation

Tags

Anantha Raju C

| Poetry | Music | Cinema | Books | Visual Art | Software Engineering |