PrepZone Logo
PrepZone

Maven and the Spring Boot Layout

POM structure, build lifecycle, and where every file in a Boot project lives.

Why this matters

  • Knowing where files belong prevents the "everything in one package" anti-pattern that makes BookStore APIs unmaintainable at scale.
  • Maven's build lifecycle (compile, test, package) is what CI pipelines run — misconfigured POMs break deployments silently.
  • The pom.xml is the contract between your code and the libraries it needs; understanding it lets you add JPA or Security without version conflicts.

The BookStore POM skeleton

Every Boot project starts with a parent POM that pins compatible library versions:

Java
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

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

  <groupId>com.example</groupId>
  <artifactId>bookstore-api</artifactId>
  <version>0.1.0-SNAPSHOT</version>
  <name>BookStore API</name>

  <properties>
    <java.version>17</java.version>
  </properties>

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

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

No version numbers on spring-boot-starter-web — the parent BOM supplies them.

bookstore/
src/main/java/.../BookStoreApplication.java@SpringBootApplication entry point
src/main/java/.../controller/REST endpoints
src/main/java/.../service/Business logic
src/main/java/.../repository/Data access
src/main/resources/application.ymlConfiguration
pom.xmlMaven dependencies
Source code lives under src/main/java. Configuration and static assets sit in src/main/resources.

Standard directories

  • src/main/java — Application code organised by package (controller, service, repository, model).
  • src/main/resources — application.yml, static files, Flyway migrations, and META-INF auto-config.
  • src/test/java — Unit and integration tests mirroring the main package structure.
  • target/ — Build output (gitignored); contains the executable JAR after mvn package.

Package-by-feature layout

For the BookStore API, organise by layer inside a root package:

Java
com.example.bookstore/
├── BookStoreApplication.java
├── controller/BookController.java
├── service/BookService.java
├── repository/BookRepository.java
├── model/Book.java
└── config/BookStoreProperties.java

Avoid com.example.bookstore.controller.book.BookController — deep nesting without purpose adds navigation cost.

Build lifecycle commands

Java
./mvnw clean compile          # compile sources only
./mvnw test                   # run unit tests
./mvnw package                # build executable JAR in target/
./mvnw spring-boot:run        # compile and start the app
java -jar target/bookstore-api-0.1.0-SNAPSHOT.jar

The Maven Wrapper (mvnw) ensures every developer and CI agent uses the same Maven version.

The executable JAR

spring-boot-maven-plugin repackages dependencies into a fat JAR with a Main-Class manifest entry. The BookStore JAR is self-contained — one file to deploy.

Java
jar tf target/bookstore-api-0.1.0-SNAPSHOT.jar | head
# BOOT-INF/classes/com/example/bookstore/BookStoreApplication.class
# BOOT-INF/lib/spring-boot-starter-web-3.4.1.jar

BOOT-INF/lib holds every transitive dependency.

Dependency scopes

When each scope applies

  • compile (default) — Available at compile time and packaged into the JAR (spring-boot-starter-web).
  • test — Only on the test classpath (spring-boot-starter-test, Mockito).
  • provided — Compile-time only, supplied by the container (rare in Boot apps).

Multi-module projects

As BookStore grows (catalog service, order service), split into a parent POM with child modules:

Java
<modules>
  <module>bookstore-api</module>
  <module>bookstore-common</module>
</modules>

Each child inherits the Boot parent and shares a ${project.version}. Start with a single module and split only when boundaries are clear.

Gradle alternative

Gradle achieves the same layout with build.gradle.kts. Spring Initializr lets you choose Maven or Gradle at project creation. The directory structure is identical — only the build file differs.

Quick recall

Everything you need if you only revisit this box.

  • Boot projects use Maven (or Gradle) with spring-boot-starter-parent managing dependency versions.
  • Source lives in src/main/java, configuration in src/main/resources, tests in src/test/java.
  • spring-boot-maven-plugin produces a fat executable JAR with all dependencies embedded.
  • Organise BookStore code by layer: controller, service, repository, model, config.
  • ./mvnw is the portable build entry point; package then java -jar is the deploy path.
  • Use dependency:tree to diagnose version conflicts before they reach production.

Test yourself

Answer these before moving on — recall is what makes it stick.