Files
Claude 83d245e726 hexagonal: capture the real missing-scan-base-packages failure as a transcript
A follow-up to the hexagonal module commit: the post's self-correction pass caught a
console block quoting that exception from memory instead of a committed transcript, so
this captures it for real (by removing @EnableJpaRepositories/@EntityScan, running the
test, and restoring them) and links the README to it. A regular git commit --amend +
force-push would have kept this at one commit for the module, matching every other
module in this repo, but this session's git safety controls do not allow a force-push,
so this lands as its own small commit instead.
2026-10-03 20:35:59 +00:00

5.2 KiB

hexagonal

Companion project for the article Hexagonal Architecture (Ports and Adapters) in Spring Boot 4 on ankurm.com.

There is deliberately no docs/ folder: the deeper material lives in collapsible "going deeper" sections inside the article itself, next to the paragraph each one extends.

This is also the one project in this repo that is its own small Maven reactor rather than a single self-contained project. That is not a style choice -- it is what makes "the core has no Spring dependency" something Maven enforces, not a naming convention a reviewer has to remember to check. Run mvn -pl core dependency:tree yourself and compare it to core/output/01-core-dependency-tree.txt.

Versions

Spring Boot 4.1.1
Spring Framework 7.0.9
JDK 25 (Temurin 25.0.4.1+1)
Maven 3.9
H2 2.4.240 (the version Boot 4.1.1 manages)
Hibernate ORM 7.4.5.Final

Modules

Module What it is Depends on Spring on its compile classpath?
core/ the domain model, the inbound/outbound ports, WalletService nothing in this reactor no -- mvn -pl core dependency:tree has zero Spring artifacts
persistence-adapter/ the outbound adapter: WalletEntity, SpringDataWalletRepository, JpaWalletRepositoryAdapter core yes -- Spring Data JPA, H2
web-adapter/ the inbound adapter: WalletController, WalletExceptionHandler core yes -- Spring MVC
app/ the composition root: HexagonalApplication, the only @SpringBootApplication in this project core, persistence-adapter, web-adapter yes -- it wires everything together

Quickstart

export JAVA_HOME=/path/to/jdk-25
cd hexagonal
mvn clean install          # builds all four modules in order, runs every test

To run the real application:

cd app
mvn spring-boot:run
curl -X POST "http://localhost:8080/wallets/w1/deposit?amountCents=500"

(there's no endpoint to create a wallet first -- the H2 schema starts empty; the application test seeds one directly through WalletRepository before calling HTTP, which is itself worth reading as an example of testing through the port rather than through a seed script.)

Captured output

File What it shows
core/output/00-core-tests-no-spring.txt WalletServiceTest's 4 tests, no Spring context anywhere, finishing in milliseconds
core/output/01-core-dependency-tree.txt the full mvn dependency:tree for hexagonal-core -- JUnit and AssertJ, nothing else
persistence-adapter/output/00-jpa-adapter-tests.txt @DataJpaTest against a real H2 database: a save-then-find round trip and an unknown-id lookup
web-adapter/output/00-webmvctest-mockmvc.txt @WebMvcTest with the ports mocked: the real JSON a deposit and a rejected withdrawal return
app/output/00-end-to-end-http.txt the full stack over real HTTP: deposit, withdraw, an overdraft rejected with 409, and the balance reloaded straight from the database to cross-check the HTTP response
app/output/01-missing-scan-base-packages-failure.txt the real NoSuchBeanDefinitionException that comes back if @EnableJpaRepositories/@EntityScan are removed from HexagonalApplication -- captured by actually removing them and running the test, not reconstructed from memory

A note for anyone copying this reactor shape

Two things cost real time while building this that are worth stating plainly rather than leaving a reader to rediscover them:

  • Spring Boot 4 split most @...Test annotations out of spring-boot-test-autoconfigure into their own small artifacts, each under a new package too (@DataJpaTest is now org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest in artifact spring-boot-data-jpa-test; @WebMvcTest is org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest in spring-boot-webmvc-test). TestRestTemplate survived as itself but moved to org.springframework.boot.resttestclient.TestRestTemplate in artifact spring-boot-resttestclient, and it is no longer auto-configured on @SpringBootTest(webEnvironment = RANDOM_PORT) -- app's test needs an explicit @AutoConfigureTestRestTemplate.
  • In a reactor where the @SpringBootApplication class's package is a sibling of the persistence adapter's package rather than an ancestor, Spring Data JPA's repository scan and JPA's entity scan both default to the main class's own package -- not scanBasePackages. HexagonalApplication needs an explicit @EnableJpaRepositories(basePackages = "...") and @EntityScan(basePackages = "...") pointing at com.ankurm.hexagonal.persistence, or the adapter bean fails to wire with no hint that scanning is the cause.

Licence

MIT -- see the root LICENSE.