This is a baseline Spring Boot application. Use as boilerplate to quickly build new ideas.
To be used in conunction with expo-baseline
- Java: 25
- Spring Boot: 4.0.0
- Build Tool: Gradle
- Database: PostgreSQL (managed by Docker Compose)
- Database Migrations: Liquibase
- API Documentation: Springdoc OpenAPI
- Testing: JUnit 5, Testcontainers
Ensure you have the following installed:
- Java Development Kit (JDK): Version 25
- Docker: For running the PostgreSQL database via Docker Compose.
- Gradle: Although
./gradlewwrapper is included, familiarity is helpful.
-
Clone the Repository:
git clone https://github.com/your-username/spring-boot-baseline.git cd spring-boot-baseline -
Build the Project:
./gradlew clean build
This will compile the code, run tests, and assemble the application.
The application uses Docker Compose to manage its PostgreSQL database. When you run bootRun, Spring Boot will automatically start the services defined in compose.yaml.
./gradlew bootRunOnce started, the application will be available at http://localhost:8080.
Running liquibase diffChangeLog fails in a Spring Boot 4 (Java 25) project.
- Error 1:
java.lang.NoSuchMethodError: '...MetadataBuildingOptions.getIdentifierGeneratorFactory()' - Error 2:
java.lang.NoClassDefFoundError: picocli/CommandLine$IVersionProvider
- API Mismatch: Spring Boot 4 uses Hibernate 7, but the latest Liquibase extension (
liquibase-hibernate6) is only compatible with Hibernate 6. Hibernate 7 removed internal APIs that the extension relies on. - Gradle Auto-Upgrade: Even when specifying Hibernate 6 dependencies for Liquibase, the Spring Boot Dependency Management plugin forces them to upgrade to Hibernate 7 to match the project's Spring Boot version.
- Missing CLI Parser: One more issue to solve was isolating the configuration removes transitive dependencies, causing
picocli(required by Liquibase) to be missing.
We must create an "Isolation Bubble" for the Liquibase task:
- Isolate Configuration: Set
extendsFrom = []forliquibaseRuntimeto prevent inheriting the main app's Hibernate 7 jars. - Force Downgrade: Use
resolutionStrategy { force ... }to override Spring Boot's BOM and strictly enforce Hibernate 6.6.x. - Manual Dependencies: Manually re-add
picocliand a Spring Boot 3.x starter (which uses Hibernate 6) to the runtime configuration. - Package Scanning: Change
referenceUrlto scan packages (hibernate:com.example...) instead of the Spring context (hibernate:spring:...) to avoid loading the Spring Boot 4 app context in the Hibernate 6 environment.
These workarounds should be removed once liquibase-hibernate7 gets released.
This codebase uses Liquibase for managing database schema changes. There are two primary workflows for managing migrations:
This is the standard, safest approach for sensitive or complex data migrations.
- Process: You manually write the YAML/SQL changelog files describing the schema changes.
- Pros: precise control, self-documenting, no surprises.
- Cons: Slower, prone to human error (typos).
This approach leverages the liquibase-gradle-plugin to automatically generate migrations by comparing Java Entities to the current database state.
- Process:
- Update your Java Entities (e.g., add a field to
UserEntity). - Run the diff command.
- Liquibase generates the YAML for you.
- You review the generated YAML and commit it.
- Update your Java Entities (e.g., add a field to
- Pros: Extremely fast, reduces syntax errors, keeps DB and Java in sync.
- Cons: Generated names or constraints might need minor tweaking; requires a running local DB.
liquibase-gradle-plugin gives developers the best of both worlds: the speed of ddl-auto (via diff generation) with the safety and version control of physical migration files.
Prerequisite: Ensure local database is running (usually via ./gradlew bootRun or just docker compose up).
-
Make changes to Java entities.
-
Run the Diff command:
./gradlew diffChangelog -PchangeLogFile=src/main/resources/db/changelog/changes/$(date +%Y%m%d%H%M%S)_new_change.yaml(Note: Name the file whatever you want. The above command uses a timestamp.)
-
Review the generated file in
src/main/resources/db/changelog/changes/. -
Register it in
src/main/resources/db/changelog/db.changelog-master.yaml(Liquibase does not auto-register new files in the master). -
Restart the application to apply the change.
- Automatic on Startup: Liquibase migrations are automatically applied when the Spring Boot application starts (
./gradlew bootRun). The application checks the database for pending changesets and applies them in order.
All unit and integration tests (including those that use Testcontainers for a PostgreSQL database) can be executed using:
./gradlew testAPI documentation is generated using the springdoc-openapi-gradle-plugin. This plugin starts the application in the background, fetches the OpenAPI JSON, and then shuts down the application.
To generate the openapi.json file:
./gradlew generateOpenApiDocsThe generated file will be located at build/api-spec/openapi.json. This file is useful for generating API clients for frontend applications or for sharing your API contract.
- Swagger UI: Access the interactive API documentation at
http://localhost:8080/swagger-ui.html - API Documentation (JSON): The raw OpenAPI JSON can be found at
http://localhost:8080/v3/api-docs - User API:
http://localhost:8080/api/users(GET request to retrieve all users)
When changes are made to the backend's API endpoints or DTOs that affect the external contract, the OpenAPI specification needs to be regenerated. This ensures that frontend clients (like the expo-baseline application) can update their generated API code.
To generate the openapi.json file:
./gradlew generateOpenApiDocsThe generated file will be located at build/api-spec/openapi.json. This file should then be copied to the expo-baseline project to regenerate its API client.