Spring Boot project structure explained
Introduction
Random packages make refactors painful. A layered layout (web → service → domain/persistence) matches how Spring Boot documentation and tutorials think, and how reviews in jobs read code. This post defines the structure we use in this series.
Real-world explanation
@SpringBootApplication(on your main class) is@Configuration+@EnableAutoConfiguration+ component scan from that class’s package downward. PutApplicationin a root package likecom.example.demoserviceand place everything else under it. Do not hide controllers in a path that is not a subpackage.- Layers (simple mental model)
- Web — HTTP, JSON, status codes, validation at the boundary
- Service — use cases, transactions, orchestration
- Repository — JPA to PostgreSQL
- Domain / entity — JPA-mapped entities; sometimes a separate
domainpackage for pure business rules - DTO — request/response shapes, separate from entities (covered in article 14)
Recommended layout
com.example.demoservice
Application.java
config/ — @Configuration, beans, WebClient, security bits
web/ — @RestController, exception handlers
service/ — @Service, interfaces + impls
repository/ — Spring Data JpaRepository
model/ — JPA @Entity
dto/ — request/response records or classesMinimal example: empty layers as placeholders
# packages only, classes added in next posts
com.example.demoservice.web.HelloController
com.example.demoservice.service.HelloService
com.example.demoservice.repository.ThingRepository
com.example.demoservice.model.ThingA tiny controller to prove the layout works:
package com.example.demoservice.web;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class HealthController {
@GetMapping("/health")
public String health() {
return "ok";
}
}src/main/resources
application.yml(orproperties) — one place for datasource URL, JPA, logging, AI keys (later, via env)- Optional
static/andtemplates/for web UI (we focus on JSON API in this series)
src/test
- Mirror
mainfor unit tests. Use@SpringBootTestsparingly; prefer slice tests and@WebMvcTestwhere possible (later post).
Common mistakes
- Putting
Applicationin a subpackage so that other packages are siblings and not scanned. Fix:Applicationparent package must cover all component packages. - Fat “util” packages with 200 mix-and-match static calls—makes testing hard. Prefer small service classes.
- Exposing entities directly in JSON—use DTOs (article 14) so API and schema evolve independently.
Best practices
- One responsibility per class name (
UserControllernotApi). - Interfaces for services are optional; many teams use concrete
@Servicefirst and extract interfaces when a second implementation appears. - Keep
configsmall—only cross-cutting things.
Final summary
Use a root Application under com...project, then web, service, repository, model, dto, config. The next two articles configure the app: application.yml and best practices for that configuration.