Spring Boot Application Template: Documentation
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
- Technology stack
- Getting started
- Configuration
- Default accounts
- Security
- Authentication API
- Roles, permissions and endpoints
- API rate limiting
- Errors
- Docker
- Deployment checklist
- Testing and code coverage
- Project structure
- More documentation
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; otherwiseX-Forwarded-Foris ignored so it cannot be spoofed. - Only
/actuator/healthand/actuator/infoare 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 (
jwtExpirationTimeapplication 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
productionprofile with MySQL. Swagger UI and/v3/api-docsare switched off in it. - Set
JWT_KEY_STORE,JWT_KEY_STORE_PASSWORD,JWT_KEY_ALIASandREMEMBER_ME_KEY. - Serve over HTTPS (reverse proxy or load balancer) and set
FORWARD_HEADERS_STRATEGY. - Configure SMTP and
BASE_URLfor verification e-mails. - Change or delete the seeded accounts.
- Health probes:
/actuator/health/livenessand/actuator/health/readiness. The port can be set withPORT.
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
- Repository and README
- Reference docs: Getting Started, Installation, Authentication, API, User Roles, Docker, Deployment, Testing, Architecture, Changelog
- Report a bug or request a feature