LAA Spring Boot microservice template fundamentals
Use this single, detailed learning path to understand and extend the template. Follow the numbered sections in order; each one points to real files and commands you can run right away.
1) Multi-module layout and Gradle basics
- Open
settings.gradleto see the two modules (spring-boot-microservice-api,spring-boot-microservice-service) and the plugin management block that pulls the shared Gradle plugin from GitHub Packages. - Open the root
build.gradleandgradle.propertiesto confirm the sharedgroupandversionthat apply to both modules. - Inspect
spring-boot-microservice-service/build.gradleto see theuk.gov.laa.springboot.laa-spring-boot-gradle-plugin, Lombok, MapStruct, Spring starters, H2, and Jacoco configuration. - Inspect
spring-boot-microservice-api/build.gradleto see the OpenAPI generator wiring, generated source directory addition, and the disabledbootJar/bootRuntasks. - From the repo root, run
./gradlew projectsto verify Gradle sees both modules and their tasks. - Run
./gradlew tasks --group buildto view common tasks; notebootRun,bootJar,test, andintegrationTestcome from the shared plugin. - Build everything with
./gradlew build; this compiles code, runs unit tests, enforces Checkstyle, and produces coverage reports. - If you only need the runnable jar, run
./gradlew :spring-boot-microservice-service:bootJarand find it inspring-boot-microservice-service/build/libs/.
graph TD
A[repo root] --> B[settings.gradle]
A --> C[build.gradle]
A --> D[gradle.properties]
A --> E[spring-boot-microservice-api/]
A --> F[spring-boot-microservice-service/]
E --> E1[open-api-specification.yml]
E --> E2[build.gradle]
F --> F1[src/main/resources/application.yml]
F --> F2[build.gradle]
2) API-first approach
- Open
spring-boot-microservice-api/open-api-specification.ymland read the/api/v1/itemspaths to understand the current contract. - Update the YAML when adding or changing endpoints; include required fields and validation keywords (e.g.,
minLength,maxLength) so they propagate to generated models. - Regenerate sources with
./gradlew :spring-boot-microservice-api:openApiGenerate(or any build task). Generated interfaces and models land underspring-boot-microservice-api/generated/src/main/java. - Note that
sourceSets.main.java.srcDirs += ['generated/src/main/java']adds generated code to the API module; interfaces likeItemsApiand models likeItemandItemRequestBodycome from here. - In the service module, implement the generated interface in your controller (see
spring-boot-microservice-service/src/main/java/uk/gov/justice/laa/springboot/microservice/controller/ItemController.java) so routing and method signatures stay aligned with the spec. - After changing the spec, regenerate, then update controllers, services, mappers, and tests until the build is green—compilation errors will highlight missing implementations.
- To build only the contract, run
./gradlew :spring-boot-microservice-api:build;bootJaris disabled because the API module is not runnable.
flowchart LR
A[open-api-specification.yml] --> B[openApiGenerate task]
B --> C[generated interfaces/models]
C --> D[controller implements ItemsApi]
D --> E[service + mapper + repository]
3) Entry point and configuration
- Open
spring-boot-microservice-service/src/main/java/uk/gov/justice/laa/springboot/microservice/SpringBootMicroserviceApplication.javato see theSpringApplication.runentry point. - Review
spring-boot-microservice-service/src/main/resources/application.ymlfor app metadata, actuator exposure, and the in-memory H2 datasource. Hibernate auto-DDL is disabled viaspring.jpa.hibernate.ddl-auto: none. - Inspect
spring-boot-microservice-service/src/main/resources/schema.sqlanddata.sqlthat create and seed theITEMStable during startup. - Start the app locally with
./gradlew bootRun; Spring Boot loadsapplication.ymlby default. - Override properties via environment variables (e.g.,
SPRING_DATASOURCE_URL) or profile-specific files likeapplication-local.yml, then run with--spring.profiles.active=local. - Keep secrets out of
application.yml; prefer environment variables or externalised configuration for credentials and tokens. - If switching databases (e.g., Postgres), update the datasource driver/URL, remove the H2 console, and introduce a migration tool (Flyway or Liquibase) instead of
schema.sql.
4) Layered REST design
- Read
spring-boot-microservice-service/src/main/java/uk/gov/justice/laa/springboot/microservice/controller/ItemController.javato see a controller implementing the generatedItemsApiinterface. - Notice controllers stay thin: logging, status codes, and delegating to services. Avoid embedding business logic in controllers.
- Open
spring-boot-microservice-service/src/main/java/uk/gov/justice/laa/springboot/microservice/service/ItemService.javato see business rules (existence checks, mapping) separated from HTTP concerns. - Inspect
spring-boot-microservice-service/src/main/java/uk/gov/justice/laa/springboot/microservice/repository/ItemRepository.java, which extendsJpaRepositoryfor persistence operations. - Check
spring-boot-microservice-service/src/main/java/uk/gov/justice/laa/springboot/microservice/entity/ItemEntity.javafor JPA mappings; persistence annotations belong here, not in controllers or services. - When adding an endpoint: update the OpenAPI spec, regenerate code, implement the controller method, add service logic, and extend the repository if needed.
- Express business failures with domain exceptions in the service layer so the global exception handler can return consistent HTTP responses.
sequenceDiagram
participant C as Client
participant Ctrl as Controller
participant S as Service
participant R as Repository
participant DB as Database
C->>Ctrl: HTTP request
Ctrl->>S: call service method
S->>R: repository query/save
R->>DB: SQL
DB-->>R: result
R-->>S: entity/result
S-->>Ctrl: DTO/response info
Ctrl-->>C: HTTP response
5) DTO and mapping patterns
- After OpenAPI generation, explore
spring-boot-microservice-api/generated/src/main/java/uk/gov/justice/laa/springboot/microservice/model/to see DTOs likeItemandItemRequestBody. - Review
spring-boot-microservice-service/src/main/java/uk/gov/justice/laa/springboot/microservice/mapper/ItemMapper.java, which uses MapStruct (@Mapper(componentModel = "spring")) so Spring can inject the generated implementation. - Keep the annotation processor
org.mapstruct:mapstruct-processor:1.6.3inspring-boot-microservice-service/build.gradle; it generates mappers at compile time. - In services, convert entities to DTOs with the mapper (e.g.,
ItemService#getAllItems) before returning data to controllers—never expose entities directly. - When creating or updating entities from requests, either map the DTO or copy fields, then persist via the repository.
- If you add fields to the contract or entity, update the mapper interface; MapStruct will regenerate code and fail the build if mappings are incomplete when stricter policies are enabled.
- For non-trivial mappings (enums, nested objects), add helper methods or
@Mappingannotations to keep transformations declarative and testable.
flowchart LR
A[ItemRequestBody / Item DTOs] --MapStruct--> B[ItemMapper (generated impl)]
B --toEntity--> C[ItemEntity]
C --toDto--> A
6) Persistence fundamentals
- Study
spring-boot-microservice-service/src/main/java/uk/gov/justice/laa/springboot/microservice/entity/ItemEntity.java:@Entity,@Table("ITEMS"), identity@Id, and simple fields. - Review
spring-boot-microservice-service/src/main/java/uk/gov/justice/laa/springboot/microservice/repository/ItemRepository.javaextendingJpaRepository<ItemEntity, Long>; add derived queries (e.g.,findByName) as needed. - Look at
spring-boot-microservice-service/src/main/resources/schema.sqlfor table creation anddata.sqlfor seed data; Spring runs these at startup because Hibernate auto-DDL is off. - Run
./gradlew bootRunand confirm data loads by hitting endpoints or the H2 console at/h2-console(JDBC URLjdbc:h2:mem:itemsDb). - For new entities, create the class, add a repository, extend
schema.sql, and optionally add demo data todata.sql. - Use
@Transactionalon services or methods that need atomic updates; integration tests already mark transactions to roll back between tests. - For production, replace H2 with your target database, update datasource settings, and move to Flyway/Liquibase migrations instead of
schema.sql.
erDiagram
ITEMS {
BIGINT id PK
VARCHAR name
VARCHAR description
}
(data.sql seeds initial rows into ITEMS for demos)
7) Validation and request handling
- Required fields in
open-api-specification.yml(e.g.,name,description) generate Bean Validation annotations on DTOs; missing fields trigger400 Bad Requestbefore controller logic runs. - Keep controller method signatures inherited from
ItemsApi; Spring applies validation annotations on generated models automatically. - Strengthen rules by adding OpenAPI constraints (
minLength,maxLength, patterns). Regenerate sources so Bean Validation annotations stay in sync. - When adding custom validation, annotate controller parameters with
@Validand add explicit annotations on DTOs or entities. - Use
@RequestBodyfor JSON payloads and path variables/params from the generated interface; avoid manual parsing. - See
spring-boot-microservice-service/src/test/java/uk/gov/justice/laa/springboot/microservice/controller/ItemControllerTest.javafor MockMvc tests that expect 400 responses when required fields are missing. - Keep production error messages generic; log sensitive details (at debug level) instead of returning them to clients.
flowchart LR
A[HTTP request] --> B[Spring MVC binding]
B --> C{Validation OK?}
C --No--> D[400 Bad Request auto-response]
C --Yes--> E[Controller]
E --> F[Service]
8) Error handling and logging
- Check
spring-boot-microservice-service/src/main/java/uk/gov/justice/laa/springboot/microservice/exception/ItemNotFoundException.javafor the domain exception used when an item is missing. - Review
spring-boot-microservice-service/src/main/java/uk/gov/justice/laa/springboot/microservice/exception/GlobalExceptionHandler.java, which maps exceptions to HTTP responses (404for missing items,500for generic errors) and logs unexpected failures. - Throw domain-specific exceptions from services instead of returning null or Optionals so the handler can produce clear status codes.
- Use Lombok’s
@Slf4jlogger (as inItemController) to log identifiers and outcomes; avoid logging sensitive payloads. - When adding new exception types, register
@ExceptionHandlermethods in the advice class to control status codes and response bodies. - Choose log levels intentionally: info for lifecycle events, warn for client errors, error for unexpected failures.
- Validate error handling via tests like
spring-boot-microservice-service/src/test/java/uk/gov/justice/laa/springboot/microservice/exception/GlobalExceptionHandlerTest.javaand controller tests asserting 404/500 behaviour.
flowchart LR
A[Service throws ItemNotFoundException] --> B[GlobalExceptionHandler]
B --> C[404 Not Found response]
A2[Service throws unexpected Exception] --> B
B --> D[500 Internal Server Error response + error log]
9) API documentation and discovery
- Confirm the Springdoc dependency exists in
spring-boot-microservice-service/build.gradle(org.springdoc:springdoc-openapi-starter-webmvc-ui). - Start the service with
./gradlew bootRun. - Open
http://localhost:8080/swagger-ui/index.htmlto explore endpoints defined inopen-api-specification.yml. - Access the raw OpenAPI JSON at
http://localhost:8080/v3/api-docsfor client generation or contract testing. - After editing the spec and regenerating code, restart the app so Swagger UI and
/v3/api-docsreflect the changes. - If you later secure the API, configure Springdoc to expose docs appropriately (e.g., require auth headers or restrict roles).
flowchart TD
A[Running service] --> B[/swagger-ui/index.html/]
A --> C[/v3/api-docs JSON/]
B --> D[Try it out to call Items endpoints]
C --> E[Client generation / contract tests]
10) Testing strategy
- Run all tests with
./gradlew test(unit + slice) and./gradlew integrationTest(full stack). CI usually runs./gradlew build, which triggers both. - Review unit tests such as
spring-boot-microservice-service/src/test/java/uk/gov/justice/laa/springboot/microservice/service/ItemServiceTest.javathat mock repositories and mappers. - Inspect controller slice tests in
spring-boot-microservice-service/src/test/java/uk/gov/justice/laa/springboot/microservice/controller/ItemControllerTest.java, which use@WebMvcTest+ MockMvc to verify request mapping, validation, and status codes while mocking services. - Check the integration test
spring-boot-microservice-service/src/integrationTest/java/uk/gov/justice/laa/springboot/microservice/controller/ItemControllerIntegrationTest.java, which boots Spring with H2 and exercises repository interactions and SQL scripts. - Jacoco is configured in
spring-boot-microservice-service/build.gradle; the application class is excluded to focus coverage on custom logic. - When adding endpoints or business rules, add unit tests for services/mappers, controller tests for request/response behaviour, and integration tests for persistence changes.
- Use generated model builders (
Item.builder()) or simple test data builders to keep test setup concise and aligned with the contract.
flowchart TD
C[Unit tests\nItemServiceTest, ItemMapperTest] --> B[WebMvcTest slice tests\nItemControllerTest]
B --> A[Integration tests\nItemControllerIntegrationTest]
11) Build outputs and packaging
- The API module disables
bootJar/bootRunbecause it only ships interfaces and models. Build it with./gradlew :spring-boot-microservice-api:buildif you need the contract artifact. - The service module produces the runnable jar; build it with
./gradlew :spring-boot-microservice-service:bootJaror as part of./gradlew build. - Find the output jar in
spring-boot-microservice-service/build/libs/; this is what the Dockerfile copies into the image. - Code quality tools (Checkstyle, Jacoco, dependency management, test logging, Versions) come from the shared Gradle plugin. Run
./gradlew checkto apply them before packaging. - If publishing artifacts, enable Maven Publish or use the plugin defaults and configure a repository target.
- When renaming the project or group, update
settings.gradle, rootbuild.gradle, andgradle.propertiesso artifact coordinates stay consistent.
flowchart LR
A[./gradlew build] --> B[bootJar in build/libs/]
B --> C[Dockerfile COPY app.jar]
C --> D[docker build image]
D --> E[docker run -p 8080:8080]
D --> F[docker compose up --build]
12) Docker basics
- Build the service jar first:
./gradlew :spring-boot-microservice-service:bootJarso the Docker build can copy it. - Open the root
Dockerfileto see the base JDK image, creation of a non-root user, exposed port8080, and theCOPYof the service jar into/app. - Build an image locally with
docker build -t laa-spring-boot-microservice .from the repo root. - Run the container with
docker run -p 8080:8080 laa-spring-boot-microserviceand hithttp://localhost:8080/api/v1/items. - Use
docker compose up --build(seedocker-compose.yml) for quick local orchestration; add other services (e.g., a database) here as your app grows. - If you rename the service module, update the
Dockerfilejar path and the compose service name/build context accordingly.
13) Local runbook
- Ensure Java (via the Gradle toolchain) is available; install Docker if you plan to containerize locally.
- Start the app:
./gradlew bootRun(uses H2 withschema.sql/data.sql). - List items:
curl http://localhost:8080/api/v1/items(expect five seeded records). - Fetch one item:
curl http://localhost:8080/api/v1/items/1. - Create an item:
bash curl -i -X POST http://localhost:8080/api/v1/items \ -H 'Content-Type: application/json' \ -d '{"name":"New Item","description":"Created from curl."}' - Update an item:
bash curl -i -X PUT http://localhost:8080/api/v1/items/2 \ -H 'Content-Type: application/json' \ -d '{"name":"Updated","description":"Updated description."}' - Delete an item:
curl -i -X DELETE http://localhost:8080/api/v1/items/3. - Check health:
curl http://localhost:8080/actuator/health. - Explore docs at
http://localhost:8080/swagger-ui/index.html; inspect the DB viahttp://localhost:8080/h2-console(JDBC URLjdbc:h2:mem:itemsDb).
flowchart TD
A[bootRun] --> B[GET /api/v1/items -> 200 (5 items)]
A --> C[GET /api/v1/items/1 -> 200 item]
A --> D[POST /api/v1/items -> 201 + Location header]
A --> E[PUT /api/v1/items/{id} -> 204]
A --> F[DELETE /api/v1/items/{id} -> 204]
A --> G[GET /actuator/health -> 200 UP]
14) Code style and annotations
- Review
config/checkstyle/checkstyle.xml(Google Java Style). Generated sources are excluded from Checkstyle in the API module (checkstyleMain.exclude "*") to avoid noise. - Run
./gradlew checkstyleMain checkstyleTestor simply./gradlew checkto catch style issues early. - Lombok is used throughout:
@Data,@Builder,@NoArgsConstructor,@AllArgsConstructoron entities/DTOs and@RequiredArgsConstructor+@Slf4jon services/controllers. Prefer constructor injection over field injection. - Keep annotations layer-specific: HTTP annotations in controllers, validation on DTOs/entities, JPA on entities, and
@Transactionalon service methods where needed. - If you drop Lombok, add explicit getters/setters/builders to keep serialization and tests working.
- Maintain consistent package names (
uk.gov.justice.laa.springboot.microserviceby default) to align withbuild.gradle/gradle.properties.
15) Next steps for customisation
- Rename modules in
settings.gradlefromspring-boot-microservice-*to your app name (e.g.,{app}-api,{app}-service) and update the rootbuild.gradlegroup. - Change package declarations under
spring-boot-microservice-service/src/main/java,src/test/java, andsrc/integrationTest/javafromuk.gov.justice.laa.springboot.microserviceto your namespace; adjust imports accordingly. - Replace
spring-boot-microservice-api/open-api-specification.ymlwith your real contract, regenerate code, and implement the new interfaces in controllers. - Update
spring-boot-microservice-service/src/main/resources/application.ymlvalues (spring.application.name,info.app.*) and remove demoschema.sql/data.sqlonce you migrate to real databases. - Swap H2 for your target database, configure the datasource, and add Flyway/Liquibase migrations.
- Adjust the
Dockerfilejar path anddocker-compose.ymlservice names after renaming modules. - Review any CI workflow paths (if added) that reference
spring-boot-microservice-service/build/and update them to the new module name. - Layer on security, observability, and production configs (Spring Security, centralized logging/metrics) after the fundamentals are stable.