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.xmlis 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:
<?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.
Standard directories
src/main/java— Application code organised by package (controller,service,repository,model).src/main/resources—application.yml, static files, Flyway migrations, andMETA-INFauto-config.src/test/java— Unit and integration tests mirroring the main package structure.target/— Build output (gitignored); contains the executable JAR aftermvn package.
Package-by-feature layout
For the BookStore API, organise by layer inside a root package:
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
./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.
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:
<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-parentmanaging dependency versions. - Source lives in
src/main/java, configuration insrc/main/resources, tests insrc/test/java. spring-boot-maven-pluginproduces a fat executable JAR with all dependencies embedded.- Organise BookStore code by layer:
controller,service,repository,model,config. ./mvnwis the portable build entry point;packagethenjava -jaris the deploy path.- Use
dependency:treeto diagnose version conflicts before they reach production.
Test yourself
Answer these before moving on — recall is what makes it stick.