Spring Boot Project Structure Explained

A Spring Boot project looks simple on the surface but has a specific structure with specific conventions. Understanding it upfront saves hours of confusion later.


The Standard Layout

order-service/
├── pom.xml
├── mvnw                         # Maven Wrapper script (Unix)
├── mvnw.cmd                     # Maven Wrapper script (Windows)
├── .mvn/
│   └── wrapper/
│       └── maven-wrapper.properties
└── src/
    ├── main/
    │   ├── java/
    │   │   └── com/devopsmonk/orderservice/
    │   │       └── OrderServiceApplication.java
    │   └── resources/
    │       ├── application.properties
    │       ├── static/
    │       └── templates/
    └── test/
        ├── java/
        │   └── com/devopsmonk/orderservice/
        │       └── OrderServiceApplicationTests.java
        └── resources/

This is the Maven Standard Directory Layout — Maven expects code here, and Spring Boot layers its own conventions on top.


pom.xml — Build Configuration

The Maven Project Object Model (POM). It defines:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.0.0</version>
</parent>

Spring Boot parent — inherits a curated list of dependency versions. You add starters without version numbers; the parent ensures they’re all compatible.

<groupId>com.devopsmonk</groupId>
<artifactId>order-service</artifactId>
<version>0.0.1-SNAPSHOT</version>

Coordinates — your project’s unique identifier in the Maven ecosystem. groupId is typically your organization’s reverse domain name. artifactId is the project name. version uses SNAPSHOT for work-in-progress, a release version (like 1.0.0) for releases.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

Dependencies — every library your project needs. Starters are Spring Boot’s bundled dependency sets. We cover them next.


src/main/java — Application Code

All your Java source code lives here. Spring Boot has one critical convention: the main application class must be in the root package, and all other classes must be in that package or sub-packages.

Why this matters: Component Scanning

@SpringBootApplication triggers a component scan starting from the package of the main class. It finds all @Component, @Service, @Repository, and @Controller classes in that package tree.

com.devopsmonk.orderservice      ← @SpringBootApplication here
├── OrderServiceApplication.java
├── controller/
│   └── OrderController.java     ← @RestController — found ✓
├── service/
│   └── OrderService.java        ← @Service — found ✓
└── repository/
    └── OrderRepository.java     ← @Repository — found ✓

If you put a class outside the root package, component scanning misses it:

com.devopsmonk                   ← outside the root package
└── SomeHelper.java              ← @Component — NOT found ✗

As your application grows, organize by feature (not layer):

com.devopsmonk.orderservice/
├── OrderServiceApplication.java
├── order/
│   ├── OrderController.java
│   ├── OrderService.java
│   ├── OrderRepository.java
│   └── Order.java
├── customer/
│   ├── CustomerController.java
│   ├── CustomerService.java
│   └── Customer.java
└── shared/
    └── ApiError.java

Feature-based packaging (orders together, customers together) is easier to navigate than layer-based (all controllers together, all services together). It also makes it easier to extract features into separate services later.


src/main/resources — Configuration and Static Files

application.properties

The primary configuration file. Every Spring Boot property can be set here:

# Server
server.port=8080
server.shutdown=graceful

# Application
spring.application.name=order-service

# Database
spring.datasource.url=jdbc:postgresql://localhost:5432/orders
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASSWORD}

# JPA
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=false

You can use application.yml instead if you prefer YAML:

server:
  port: 8080
  shutdown: graceful
spring:
  application:
    name: order-service
  datasource:
    url: jdbc:postgresql://localhost:5432/orders

YAML is more readable for nested properties. Both formats are equivalent. Pick one and stick with it — don’t mix them in the same project.

Profile-specific configuration

Spring Boot supports multiple configuration files for different environments:

resources/
├── application.properties           # base (always loaded)
├── application-dev.properties       # loaded when profile=dev
├── application-prod.properties      # loaded when profile=prod
└── application-test.properties      # loaded when profile=test

Activate a profile:

java -jar app.jar --spring.profiles.active=prod

or as environment variable:

SPRING_PROFILES_ACTIVE=prod java -jar app.jar

Profile-specific properties override the base application.properties. This is how you have different database URLs for dev vs. prod.

static/

Static web files served directly — CSS, JavaScript, images, HTML files that don’t go through a template engine. Access them directly: http://localhost:8080/style.css if you put style.css in static/.

templates/

Server-side HTML templates (Thymeleaf, FreeMarker, Mustache). Only relevant if you’re building a traditional server-rendered web app. For REST APIs (what we’re building), this folder stays empty.


src/test/java — Test Code

Tests mirror the main source structure. Each class in main/java typically has a corresponding test in test/java:

main/java/
└── com/devopsmonk/orderservice/
    ├── order/
    │   ├── OrderController.java
    │   └── OrderService.java

test/java/
└── com/devopsmonk/orderservice/
    ├── order/
    │   ├── OrderControllerTest.java
    │   └── OrderServiceTest.java
    └── OrderServiceApplicationTests.java

OrderServiceApplicationTests.java is generated by Spring Initializr:

@SpringBootTest
class OrderServiceApplicationTests {

    @Test
    void contextLoads() {
        // Verifies the Spring context starts without errors
    }
}

This is a smoke test — if the application context fails to start (misconfiguration, missing beans), this test fails. Keep it.


Maven Wrapper (mvnw)

The mvnw script and .mvn/wrapper/ directory are the Maven Wrapper. They download the correct Maven version automatically so everyone on the team builds with the same Maven version — regardless of what’s installed globally.

./mvnw spring-boot:run   # run the app
./mvnw test              # run tests
./mvnw package           # build the JAR
./mvnw clean package     # clean build artifacts, then build the JAR

Always use ./mvnw rather than mvn for consistency.


How to Structure a Real Application

As the order-service grows through this series, the structure will look like this:

com.devopsmonk.orderservice/
├── OrderServiceApplication.java
│
├── order/
│   ├── api/
│   │   ├── OrderController.java        @RestController
│   │   ├── CreateOrderRequest.java     DTO (input)
│   │   └── OrderResponse.java         DTO (output)
│   ├── domain/
│   │   ├── Order.java                  @Entity
│   │   ├── OrderItem.java              @Entity
│   │   └── OrderStatus.java           enum
│   ├── service/
│   │   └── OrderService.java          @Service
│   └── repository/
│       └── OrderRepository.java       @Repository / JpaRepository
│
├── customer/
│   ├── api/
│   ├── domain/
│   ├── service/
│   └── repository/
│
└── shared/
    ├── exception/
    │   ├── OrderNotFoundException.java
    │   └── GlobalExceptionHandler.java
    └── config/
        └── SecurityConfig.java

Key rule: never let controllers reference repositories directly. The layer structure should be: Controller → Service → Repository. Services contain business logic; controllers handle HTTP concerns only.


Summary

DirectoryPurpose
pom.xmlBuild config, dependency management
src/main/javaApplication code — main class at root package
src/main/resources/application.propertiesConfiguration
src/main/resources/static/Static files (CSS, JS, images)
src/main/resources/templates/Server-side HTML templates
src/test/javaTest code, mirrors main structure
mvnwMaven Wrapper — use this instead of mvn

The two most important rules:

  1. Main application class belongs in the root package
  2. All other classes must be in the root package or a sub-package

Next: How Auto-Configuration Works →

Previous: ← Your First Spring Boot Application