A secure Spring Boot 4 REST API built around a simple blog-style domain model containing users, posts, and comments. The application supports JWT authentication, protected endpoints, JPA persistence, and both H2 and MySQL database configurations.
This API provides public registration and login endpoints. Post and comment endpoints are protected and require a valid JWT bearer token.
The project is suitable for learning, prototyping, Postman API testing, and use as a foundation for a larger backend application.
- Java 25 runtime
- Spring Boot 4.0.2
- Spring Framework 7.0.3
- Spring Security
- Spring Data JPA and Hibernate
- H2 Database for in-memory development
- MySQL 8 for persistent storage
- Maven Wrapper
- JJWT for JSON Web Tokens
- Lombok
Java version note: Spring Boot 4.0 requires Java 17 or later and provides first-class support for Java 25. This project is currently configured and tested with JDK 25. Java 17 or newer can also be used if the Maven compiler configuration permits it.[web:143][web:146]
Install the following software:
- JDK 25, or Java 17 or newer.
- MySQL Server 8 if using the MySQL profile.
- MySQL Workbench, optional.
- Postman.
- Visual Studio Code or IntelliJ IDEA.
Verify the active Java version:
java -versionExpected output should contain Java 25, for example:
java version "25..."
Verify the Java compiler:
javac -versionsrc/
βββ main/
β βββ java/com/example/demo/
β β βββ DemoApplication.java
β β βββ config/
β β β βββ SecurityConfig.java
β β βββ controller/
β β β βββ AuthController.java
β β β βββ PostController.java
β β β βββ CommentController.java
β β βββ dto/
β β β βββ AuthRequest.java
β β β βββ AuthenticationResponse.java
β β β βββ PostResponse.java
β β β βββ CommentResponse.java
β β βββ model/
β β β βββ User.java
β β β βββ Post.java
β β β βββ Comment.java
β β βββ repository/
β β β βββ UserRepository.java
β β β βββ PostRepository.java
β β β βββ CommentRepository.java
β β βββ security/
β β βββ JwtService.java
β β βββ JwtAuthenticationFilter.java
β β βββ UserDetailsServiceImpl.java
β βββ resources/
β βββ application.properties
βββ test/
βββ java/com/example/demo/
βββ DemoApplicationTests.java
The application uses stateless JWT bearer authentication. Passwords are hashed with BCrypt before they are stored.
Public endpoints:
POST /api/auth/register
POST /api/auth/login
Protected endpoints require:
Authorization: Bearer YOUR_JWT_TOKEN
POST http://localhost:8080/api/auth/register
Content-Type: application/jsonBody:
{
"email": "razak@domain.com",
"password": "123456"
}Response:
{
"token": "<jwt-token>"
}Copy the token and use it in the Authorization header for protected requests.
POST http://localhost:8080/api/auth/login
Content-Type: application/jsonBody:
{
"email": "razak@domain.com",
"password": "123456"
}Response:
{
"token": "<jwt-token>"
}GET /api/posts
GET /api/posts/{id}
GET /api/posts/my
POST /api/posts
PUT /api/posts/{id}
DELETE /api/posts/{id}Create a post:
POST http://localhost:8080/api/posts
Authorization: Bearer YOUR_JWT_TOKEN
Content-Type: application/jsonBody:
{
"title": "Welcome to the API",
"body": "This is a sample post created through the API."
}Example response:
{
"id": 1,
"title": "Welcome to the API",
"body": "This is a sample post created through the API.",
"authorId": 1,
"authorEmail": "razak@domain.com"
}The author is obtained from the JWT. Do not send an author object when creating a post.
GET /api/comments
GET /api/comments/{id}
POST /api/comments
PUT /api/comments/{id}
DELETE /api/comments/{id}Create a comment for post 1:
POST http://localhost:8080/api/comments
Authorization: Bearer YOUR_JWT_TOKEN
Content-Type: application/jsonBody:
{
"text": "This comment belongs to the sample post.",
"post": {
"id": 1
}
}Example response:
{
"id": 1,
"text": "This comment belongs to the sample post.",
"postId": 1,
"authorId": 1,
"authorEmail": "razak@domain.com"
}DTO responses are used so the API does not expose passwords or serialize the complete recursive JPA relationships.
H2 is useful for quick local testing. It stores data in memory and resets the data when the application stops.
Open:
src/main/resources/application.properties
Use this configuration:
spring.datasource.url=jdbc:h2:mem:demo;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.show-sql=true
jwt.secret=ThisIsADevelopmentJWTSecretKey1234567890
jwt.expiration=86400000
server.port=8080Optional H2 console:
http://localhost:8080/h2-console
Use the following H2 console values:
JDBC URL: jdbc:h2:mem:demo
User: sa
Password: leave empty
Create the database in MySQL:
CREATE DATABASE IF NOT EXISTS springdb;For local development, create a dedicated user:
CREATE USER IF NOT EXISTS 'springapp'@'localhost'
IDENTIFIED BY 'ChangeThisPassword123!';
GRANT ALL PRIVILEGES ON springdb.*
TO 'springapp'@'localhost';
FLUSH PRIVILEGES;Test the credentials:
& "C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql.exe" -u springapp -p springdbReplace the H2 datasource section in application.properties with:
spring.datasource.url=jdbc:mysql://localhost:3306/springdb?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.username=springapp
spring.datasource.password=ChangeThisPassword123!
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.jpa.show-sql=true
jwt.secret=ThisIsADevelopmentJWTSecretKey1234567890
jwt.expiration=86400000
server.port=8080Use your real MySQL password. Do not commit real credentials or JWT secrets to source control.
Do not use both H2 and MySQL datasource sections simultaneously. Keep only one active datasource configuration.
The project must include Spring Web, Spring Security, Spring Data JPA, JJWT, Lombok, and the database driver being used.
For MySQL, make sure pom.xml contains:
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>For H2, make sure it contains:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>If Java is already configured in Windows, use only:
.\mvnw.cmd spring-boot:runIf it is not configured, set it for the current PowerShell session:
$env:JAVA_HOME = "C:\Program Files\Java\jdk-25.0.2"
$env:PATH = "$env:JAVA_HOME\bin;$env:PATH"
.\mvnw.cmd spring-boot:runFor a permanent setup, add JAVA_HOME and %JAVA_HOME%\bin to the Windows user environment variables. Then reopen VS Code or PowerShell.
From the project root:
.\mvnw.cmd clean install -DskipTests.\mvnw.cmd spring-boot:runA successful startup includes:
Started DemoApplication
ReadinessState changed to ACCEPTING_TRAFFIC
The application listens at:
http://localhost:8080
Keep the terminal open while the server is running. Stop it with Ctrl+C.
Run the test suite:
.\mvnw.cmd testIf tests try to connect to MySQL and fail, configure a separate H2 datasource under:
src/test/resources/application.properties
For a quick build without running tests:
.\mvnw.cmd clean install -DskipTestsCheck that the username and password in application.properties match a working MySQL login. A dedicated springapp user is recommended instead of root.
Confirm that MySQL is running and listening on port 3306.
Create the database:
CREATE DATABASE springdb;Confirm the com.mysql:mysql-connector-j dependency exists, then run:
.\mvnw.cmd clean install -DskipTestsUse a numeric value without an inline comment:
jwt.expiration=86400000Login again and send the token exactly as:
Authorization: Bearer eyJ...
There must be one space between Bearer and the token.
- H2 data is reset when the application restarts.
- MySQL data persists between application restarts.
- Passwords are hashed before persistence.
- DTO responses intentionally exclude passwords and recursive entity relationships.
- JWT authentication is stateless.
- A user can update or delete only their own posts and comments.
spring.jpa.hibernate.ddl-auto=updateis convenient for development. Use database migrations such as Flyway or Liquibase in production.
This project is an internal/demo API sample. Extend, adapt, and distribute it according to your organization or personal project standards.