Skills for creating, testing, reviewing, migrating and guarding Spring Boot 4 projects with Java 25: project scaffolding with architecture patterns, Boot 4 test templates, code review, Boot 3 to 4 migration, and a team harness of build gates and agent hooks.
93%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
Use this file to identify the architecture in use and check the code against it. For package layouts, naming, the decision matrix, and upgrade steps, read ../../creating-springboot-projects/references/architecture-guide.md. For code examples, read domain-modeling.md and value-objects-patterns.md.
Review against the architecture the team chose. Do not push a more complex pattern unless the "signs to evolve" below are present.
| Pattern | Markers in the code | Domain model | Boundary enforcement |
|---|---|---|---|
| Layered | Top-level controller/, service/, repository/, domain/ | Anemic JPA entities, logic in services | None |
| Package-by-module | Top-level feature packages (orders/, catalog/), layers inside | Anemic | None |
| Modular monolith | Feature packages + Spring Modulith, ModularityTests, internal/ packages, *API types | Anemic | ApplicationModules.verify() |
| Tomato | Modular monolith + value objects (@Embeddable records), rich entities, StringTo*Converter | Rich; VOs embedded in JPA entities | Modulith |
| DDD + Hexagonal | Per module application/, domain/, infra/ (or adapter/), interfaces/; ports as interfaces; *Handler, *QueryService | Rich; domain separate from persistence | ArchUnit |
Hexagonal code appears in two common layouts: per module (application/domain/infra/interfaces, as in the architecture guide) or ports-and-adapters (domain/port/in, domain/port/out, adapter/in, adapter/out). Accept both. Check the dependency direction, not the folder names.
shared/ is small and generic (IDs, time, errors). Flag business logic in it.ApplicationModules.of(App.class).verify() and passes.*API type). Flag imports of another module's internal package.@ApplicationModuleListener), not direct calls into other modules' services.event_publication table exist (see below).Note: package-private is per Java package, not per Modulith module. A package-private class in order.internal is not visible from order either. verify() enforces the module-level rules at test time.
OrderNumber, Money, Quantity), not String/BigDecimal.@Embeddable, including records.@Service and @Transactional.interfaces and infra depend on application/domain, never the reverse.*Handler vs *QueryService).Flag this:
// Bypasses the Order root; the order total is not recalculated
OrderItem item = orderItemRepository.findById(itemId).orElseThrow();
item.setQuantity(quantity);Fix: order.updateItemQuantity(itemId, quantity) on the loaded Order.
| Mechanism | Runs when | Transaction | Survives a crash |
|---|---|---|---|
@EventListener | Immediately, inside the publisher's transaction | Publisher's | No |
@TransactionalEventListener(phase = AFTER_COMMIT) | After the publisher commits | Writes need @Transactional(propagation = REQUIRES_NEW) | No |
@ApplicationModuleListener | Asynchronously, after the publisher commits | New (REQUIRES_NEW) | Only with an event publication registry |
@ApplicationModuleListener is always @Async + @Transactional(propagation = REQUIRES_NEW) + @TransactionalEventListener. The Event Publication Registry adds persistence and replay. It needs one of spring-modulith-starter-jdbc, -jpa, -mongodb, or -neo4j, plus the backing storage (for JDBC/JPA, the event_publication table, created by Flyway or Liquibase). Without it, an event is lost if the app stops between the commit and the end of the listener. Source: Spring Modulith events.
Check:
Recommend CQRS only when read and write needs differ (reporting, denormalized views, very different load). For simple CRUD it adds cost with no benefit.
@Transactional(readOnly = true). It does not load aggregates to build views.| Current | Sign | Next |
|---|---|---|
| Layered | Related code is hard to find; teams change the same files | Package-by-module |
| Package-by-module | Accidental cross-module dependencies; lost events | Modular monolith |
| Modular monolith | Validation repeated everywhere; bugs from mixed-up primitives | Tomato |
| Tomato | Need to swap infrastructure, CQRS, or separate read/write models | DDD + Hexagonal |