Spring Boot Project Structure for Clean Architecture
Fast recall
Quick revision
Core answer
domain (entities + value objects), application (use cases + ports), adapter (in/web, out/persistence, out/external). Spring Boot lives in the adapter packages and a config package; the domain and application packages have zero Spring imports. Wire dependencies via @Configuration beans. Enforce package boundaries with ArchUnit in CI. Optionally split into Maven/Gradle modules for compile-time enforcement.Practical packaging that enforces Clean Architecture's dependency rule, where Spring fits, and how to enforce boundaries automatically.
Spring annotations leaking into domain/application packages, or one giant `service` package with everything mixed.
Multi-module Maven setup for compile-time boundary enforcement, ArchUnit rules, and the trade-off vs single-module pragmatic structure.
Interview-ready guide
Interview answer
Recommended single-module structure
Package by responsibility, not technical layer:
- domain/ — Entities + Value Objects (innermost). Order, OrderItem, Money. No imports from anywhere else.
- application/ — Use Cases + Ports. port/in for use case interfaces + commands/responses, port/out for repository/gateway interfaces, service/ for interactors. Imports only from domain.
- adapter/ — in/web for controllers, in/messaging for Kafka consumers, out/persistence for JPA gateway, out/payment for Stripe. Imports application + domain.
- config/ — Spring wiring. @Configuration with @Bean factories. Imports from everywhere.
Where Spring lives
Spring annotations only in adapter and config. @RestController, @KafkaListener in adapter/in/. @Repository in adapter/out/. @Configuration, @Bean in config/.
Domain and application are pure Java. Entities are records or POJOs (no @Entity). Use cases are plain classes with constructor injection (no @Service). Ports are interfaces with no annotations.
Wiring: @Bean factory in @Configuration creates new PlaceOrderInteractor(orderRepo, inventory). Spring sees the @Bean and wires the implementation it finds for each port.
Persistence adapter — JPA without polluting domain
Domain Order is a record. OrderJpaEntity is a separate @Entity-annotated class with @Table, @Id, @OneToMany. OrderSpringRepo extends JpaRepository
Schema changes in JPA don't touch the domain.
Multi-module Maven
For larger teams: separate modules domain/, application/, adapter-in-web/, adapter-out-persistence/, adapter-out-payment/, bootstrap/. Domain depends on nothing. Application depends on domain. Adapters depend on application + their framework. Bootstrap depends on all.
Domain literally cannot import Spring — not on the classpath. Adapters cannot bypass application. Boundary violations become compile errors, not runtime ones.
Cost: more poms. Benefit: enforcement at compile time.
Enforcing with ArchUnit (single-module)
Tests like noClasses().that().resideInAPackage('..domain..').should().dependOnClassesThat().resideInAnyPackage('..application..', '..adapter..', 'org.springframework..').
Verify @RestController only in adapter/in/web, etc. Run in CI; broken architecture fails the build.
Testing strategy by layer
Domain tests: plain JUnit, no Spring, no DB. Test entity invariants. Use case tests: plain JUnit, in-memory port implementations. Test orchestration, business rules, error paths. Milliseconds. Web adapter: @WebMvcTest slice. Verify HTTP ↔ use-case-DTO mapping. Persistence adapter: Testcontainers + real Postgres. Verify mappings + queries. External: WireMock for HTTP gateways. Integration: minimal @SpringBootTest for end-to-end smoke tests of critical flows.
Common mistakes
- Package by technical layer (controllers/, services/, repositories/) — mixes domains, loses locality
- Spring annotations leaking inward — @Service on interactor, @Entity on domain object
- One giant application package without feature sub-packages
- JPA entities reused as domain entities to avoid mappers — saves a file, couples schema to domain
- No CI enforcement — without ArchUnit/module split, discipline drifts in PRs
- @SpringBootApplication in application — belongs in bootstrap/config
Recommendation
Default to a three-package single-module structure (domain / application / adapter / config) for most Spring Boot Clean Architecture projects. Keep domain and application framework-free; wire dependencies via @Configuration. Enforce boundaries with ArchUnit in CI. Graduate to a multi-module Maven setup once the team is large enough that compile-time enforcement saves more time than it costs in build configuration. Map JPA entities separately from domain entities — the small mapping tax buys schema independence and clean tests.
Spring Boot Project Structure for Clean Architecture
Direct Answer
Organize a Spring Boot Clean Architecture project around three (or four) packages by responsibility, not by technical layer: domain (entities + value objects), application (use cases + ports), adapter (in/web, out/persistence, out/external). Spring Boot lives in the adapter packages and a config package; the domain and application packages have zero Spring imports. Wire dependencies via @Configuration beans. Enforce package boundaries with ArchUnit in CI. Optionally split into Maven/Gradle modules for compile-time enforcement.
Common Mistakes & Pitfalls
Spring annotations leaking into domain/application packages, or one giant service package with everything mixed.
Check your understanding
Answer these without looking back at the article.
- Show an ArchUnit test that prevents the domain from importing Spring.
- Multi-module Maven — walk through the pom.xml for each module.
- When does the cost of mapping domain ↔ JPA entity become more than it's worth?
- Where do you put cross-cutting concerns like authentication and audit logging?
Related interview prep categories
1Want a guided curriculum order?
These modules are also sequenced in our flagship backend interview track — open it when you prefer an ordered path instead of browsing by topic.