Skip to content

victorSeidel/starter-java-spring

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Starter Java Spring

A production-ready Web API starter project built with Java and Spring Boot, designed to be the foundation for new backend applications. It comes pre-configured with JWT authentication via cookies, role-based access control, audit fields, and a clean, layered architecture — so you can skip the boilerplate and start building features right away.

Java Spring Boot MySQL JWT License


📋 Table of Contents


Overview

Starter Java Spring provides a solid, opinionated starting point for building RESTful Web APIs. Rather than setting up security, authentication, and user management from scratch on every new project, this template gives you all of that out of the box, ready to extend.

The project is intentionally database-agnostic at its core - while it ships configured for MySQL, swapping to PostgreSQL, MariaDB, or any other JPA-compatible database requires nothing more than changing the connector dependency and the datasource URL.


Features

  • JWT Authentication via HttpOnly Cookies - stateless authentication using short-lived access tokens (15 minutes) and long-lived refresh tokens (7 days), both stored in HttpOnly cookies to mitigate XSS risks.
  • Role-Based Access Control (RBAC) - two built-in roles (ADMIN and USER) with granular method-level authorization using Spring Security's @PreAuthorize.
  • Refresh Token Strategy - persistent refresh tokens stored in the database, with the ability to invalidate sessions individually.
  • Audit Fields - entities automatically track createdAt, updatedAt, createdBy, updatedBy, and soft-delete via deletedAt, powered by Spring Data JPA Auditing.
  • Soft Delete - deleting a user sets their deletedAt timestamp rather than removing the record, and automatically disables their login.
  • SPA Support - the security configuration allows serving a Single Page Application (React, Vue, etc.) from the same server, with all non-API routes forwarded to index.html.
  • Configurable CORS - allowed origins are externalized to environment variables, making multi-environment deployments straightforward.
  • Global Exception Handling - consistent error responses across the API via GlobalExceptionHandler and custom exception types.

Tech Stack

Layer Technology
Language Java 21
Framework Spring Boot 4.0.6
Security Spring Security + JWT
Persistence Spring Data JPA + Hibernate
Database MySQL (swappable)
Build Tool Maven
Utilities Lombok

Project Structure

src/main/java/com/victorseidel/starter_project/
│
├── audit/
│   ├── Auditing.java                  # Enables JPA auditing (@EnableJpaAuditing)
│   └── AuditorAwareImpl.java          # Resolves the currently authenticated user for audit fields
│
├── configurations/
│   ├── SecurityConfiguration.java     # Security filter chain, CORS, password encoder, session policy
│   └── SpaConfiguration.java          # Forwards unknown routes to index.html for SPA support
│
├── controllers/
│   ├── AuthController.java            # Login, logout, register, and token refresh endpoints
│   └── UserController.java            # CRUD operations for users
│
├── dto/
│   └── user/
│       ├── LoginRequestDTO.java
│       ├── LoginResponseDTO.java
│       ├── RegisterRequestDTO.java
│       ├── UpdateUserRequestDTO.java
│       └── UserResponseDTO.java
│
├── exceptions/
│   ├── BadRequestException.java
│   ├── ForbiddenException.java
│   ├── NotFoundException.java
│   ├── ErrorResponse.java             # Standardized error response body
│   └── GlobalExceptionHandler.java    # Catches exceptions and maps them to HTTP responses
│
├── filters/
│   └── SecurityFilter.java            # Reads JWT from cookies and authenticates the request
│
├── models/
│   ├── RefreshToken.java              # Persistent refresh token entity
│   ├── User.java                      # User entity with audit fields and soft delete
│   └── UserPrincipal.java             # UserDetails wrapper, exposes roles and account state
│
├── repositories/
│   ├── RefreshTokenRepository.java
│   └── UserRepository.java
│
├── services/
│   ├── AuthService.java               # Login, logout, register, and token refresh logic
│   ├── CookieService.java             # Builds, sets, and clears HttpOnly cookies
│   ├── RefreshTokenService.java       # Creates and validates refresh tokens
│   ├── UserPrincipalService.java      # Loads UserDetails by email (used by Spring Security)
│   └── UserService.java              # User CRUD business logic
│
├── types/
│   └── UserRole.java                  # Enum: ADMIN, USER
│
└── utils/
    └── AuthUtil.java                  # Helper to retrieve the authenticated user from the context

Authentication Flow

This project uses a dual-token, cookie-based authentication strategy:

┌─────────────────────────────────────────────────────────────────┐
│                         Login Flow                              │
│                                                                 │
│  Client ──POST /api/auth/login──────────────────► AuthService  │
│                                                        │        │
│                                              Validate password  │
│                                                        │        │
│                                           Generate Access Token │
│                                           (JWT, expires 15min) │
│                                                        │        │
│                                          Generate Refresh Token │
│                                          (stored in DB, 7 days) │
│                                                        │        │
│  Client ◄── Set HttpOnly Cookies (access + refresh) ──┘        │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                      Request Flow                               │
│                                                                 │
│  Client ──Any request (cookie sent automatically)──► Filter    │
│                                                        │        │
│                                         Extract access_token    │
│                                         from cookie            │
│                                                        │        │
│                                         Validate JWT signature  │
│                                         & expiration           │
│                                                        │        │
│                                         Set SecurityContext     │
│                                                        │        │
│  Client ◄── Response ─────────────────────────────────┘        │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                      Refresh Flow                               │
│                                                                 │
│  Client ──POST /api/auth/refresh────────────────► AuthService  │
│          (refresh_token cookie sent automatically)     │        │
│                                                        │        │
│                                        Validate refresh token   │
│                                        against DB record       │
│                                                        │        │
│                                        Issue new access token   │
│                                                        │        │
│  Client ◄── New access_token cookie ──────────────────┘        │
└─────────────────────────────────────────────────────────────────┘

Cookie details:

Cookie Lifetime Path HttpOnly Secure
starter_project_access_token 15 minutes / configurable
starter_project_refresh_token 7 days /api/auth/refresh configurable

The refresh token cookie is scoped to /api/auth/refresh only, so it is never sent to other endpoints - reducing the attack surface.


Roles & Permissions

The project ships with two roles:

Role Granted Authorities
ADMIN ROLE_ADMIN, ROLE_USER
USER ROLE_USER

Default endpoint permissions:

Method Endpoint Required Role
POST /api/auth/login Public
POST /api/auth/register ADMIN
POST /api/auth/logout Authenticated
POST /api/auth/refresh Authenticated (via cookie)
GET /api/users ADMIN
GET /api/users/{id} Owner or ADMIN
PUT /api/users/{id} Owner or ADMIN
DELETE /api/users/{id} Owner or ADMIN

Users can only access or modify their own data. Admins can access any user.


API Endpoints

Auth

POST /api/auth/login

Authenticates a user and sets access + refresh token cookies.

Request body:

{
  "email": "[email protected]",
  "password": "secret"
}

Response: 200 OK with cookies set.


POST /api/auth/register

Creates a new user account. Requires ADMIN role.

Request body:

{
  "name": "John Doe",
  "email": "[email protected]",
  "password": "secret",
  "role": "USER"
}

Response: 201 Created


POST /api/auth/logout

Clears authentication cookies and invalidates the refresh token.

Response: 204 No Content


POST /api/auth/refresh

Issues a new access token using the refresh token cookie.

Response: 200 OK with a new access token cookie.


Users

GET /api/users?page=0&size=20

Returns a paginated list of users. Requires ADMIN role.

GET /api/users/{id}

Returns a single user by ID. Accessible by the user themselves or an admin.

PUT /api/users/{id}

Updates a user's data. Accessible by the user themselves or an admin.

Request body:

{
  "name": "New Name",
  "email": "[email protected]"
}

DELETE /api/users/{id}

Soft-deletes a user (sets deletedAt). Accessible by the user themselves or an admin.


Configuration

All sensitive and environment-specific values are externalized. Create a .env file or set environment variables before running:

Variable Description Default
PORT Server port 3000
DB_HOST Database host localhost
DB_PORT Database port 3306
DB_NAME Database name (required)
DB_USER Database username root
DB_PASS Database password (required)
JWT_SECRET Secret key for signing JWTs my-secret-key ⚠️

⚠️ Always override JWT_SECRET in production with a long, random string. Never use the default.

Other configurable properties in application.properties:

# CORS — comma-separated list of allowed origins
app.cors.allowed-origins=http://localhost:3000

# Refresh token expiration
app.token.refresh.expiration-days=7

# Cookie settings
app.cookie.secure=true          # Set to true in production (requires HTTPS)
app.cookie.same-site=Strict

Running Locally

Prerequisites

  • Java 21+
  • Maven 4.0+
  • MySQL (or compatible database)

Steps

  1. Clone the repository:

    git clone https://github.com/victorSeidel/starter-java-spring.git
    cd starter-java-spring
  2. Create your database:

    CREATE DATABASE starter_db;
  3. Set environment variables:

    DB_NAME=starter_db
    DB_PASS=yourpassword
    JWT_SECRET=a-very-long-and-random-secret-key
  4. Run the application:

    ./mvnw spring-boot:run

    The API will be available at http://localhost:3000.

Changing the Database

To use a different database (e.g., PostgreSQL):

  1. Replace the MySQL connector in pom.xml:

    <!-- Remove: -->
    <dependency>
         <groupId>com.mysql</groupId>
         <artifactId>mysql-connector-j</artifactId>
         <scope>runtime</scope>
     </dependency>
    
    <!-- Add: -->
    <dependency>
         <groupId>org.postgresql</groupId>
         <artifactId>postgresql</artifactId>
         <scope>runtime</scope>
    </dependency>
  2. Update application.properties:

    spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME}
    spring.datasource.driver-class-name=org.postgresql.Driver

No other changes are required - JPA/Hibernate handles the rest.


Docker

Dockerfile

FROM eclipse-temurin:21-jdk-alpine AS build
WORKDIR /app
COPY . .
RUN ./mvnw clean package -DskipTests

FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 3000
ENTRYPOINT ["java", "-jar", "app.jar"]

docker-compose.yml

services:
  db:
    image: mysql:8
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASS}
      MYSQL_DATABASE: ${DB_NAME}
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql

  api:
    build: .
    restart: always
    ports:
      - "${PORT:-3000}:3000"
    environment:
      PORT: ${PORT:-3000}
      DB_HOST: db
      DB_PORT: 3306
      DB_NAME: ${DB_NAME}
      DB_USER: ${DB_USER:-root}
      DB_PASS: ${DB_PASS}
      JWT_SECRET: ${JWT_SECRET}
    depends_on:
      - db

volumes:
  mysql_data:

.env (example)

PORT=3000

DB_HOST=localhost
DB_PORT=3306
DB_NAME=
DB_USER=root
DB_PASS=

JWT_SECRET=my-secret-key

Running with Docker Compose:

cp .env.example .env        # fill in your values
docker compose up --build

License

This project is licensed under the MIT License.

About

Starter Web API project built with Java, Spring Boot and database integration

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors