Skip to main content

Project Structure

Choosing a project structure is like picking a house blueprint. It dictates where your code "lives" and how it socializes with neighbors.

Let's check how we can group related classes in Java and what are the recommended approaches.

info

Throughout this document, all examples are Java-based. However, the concepts discussed can be applied to Kotlin and Groovy projects.

Packages & the main Class Rule

Packages are Java’s way of grouping classes (like folders).

Critical Rule: Your main class (annotated with @SpringBootApplication) defines the root package.

  • All other packages you create must be subpackages of this root (e.g., if your main class is in dev.pollito.spring_java, create dev.pollito.spring_java.domain, not dev.pollito.domain).
  • Why? Spring Boot automatically scans classes in the root package and its subpackages. Classes outside won’t be detected unless explicitly configured.
    • Avoid fighting against this default unless necessary: Spring Boot relies on this hierarchy.

Default Project Structure

When you generate a Spring Boot project (more on how to generate a project in the Spring Initializr section), you get a standardized folder and file structure.

File Tree
your-project/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── dev/pollito/spring_java/
│ │ │ └── SpringJavaApplication.java
│ │ └── resources/
│ │ ├── static/
│ │ ├── templates/
│ │ └── application.properties (or application.yml)
│ └── test/
│ └── java/dev/pollito/spring_java/
│ └── SpringJavaApplicationTests.java
├── .gitignore
├── pom.xml (or build.gradle)
└── HELP.md
  • SpringJavaApplication.java: The entry point of your app. Annotated with @SpringBootApplication to enable autoconfiguration.
  • application.properties (or application.yml): Central configuration file for database URLs, server ports, logging, etc.
  • static/ and templates/, empty by default. Used for web assets:
    • static/: Serves images, CSS, JS directly.
    • templates/: Server-rendered HTML (if using Thymeleaf, Mustache, etc.).
  • pom.xml (Maven) or build.gradle (Gradle): Defines dependencies and plugins.
  • test/: Preconfigured for JUnit tests (More about this in the Unit Testing section). It has a skeleton test class SpringJavaApplication.java that verifies the app context loads.

Picking an Architecture

Let's imagine a simple application that:

  1. Exposes a GET /api/films/{filmId} REST API endpoint.
  2. Gets the films information from a Sakila H2 database.
  3. Supplements that data with information from an external API via FeignClient.
info

All the "Sakila" logic will exist inside the sakila package, representing a bounded context.

  • In larger systems, each domain can live in its own top‑level package following whatever architectural rules.

Follow Clean Architecture

  • Dependencies always point inward.
  • The domain being the most independent layer, and each outer layer depends on the inner ones.

src/test folder is omitted for simplicity.

File Tree
src/main/java/dev/pollito/spring_java
├── SpringJavaApplication.java

└── sakila
├── domain
│ ├── model
│ │ └── Film.java # Enterprise Business Rules
│ ├── repository
│ │ └── FilmRepository.java # Enterprise Business Rules
│ └── service
│ └── FilmService.java # Enterprise Business Rules

├── application
│ └── usecase
│ └── FindFilmByIdUseCase.java # Application Business Rules

├── infrastructure
│ ├── persistence
│ │ ├── entity
│ │ │ └── FilmEntity.java # Interface Adapters
│ │ ├── repository
│ │ │ └── H2FilmRepository.java # Frameworks & Drivers
│ │ └── mapper
│ │ └── FilmEntityMapper.java # Interface Adapters
│ │
│ ├── external
│ │ └── feign
│ │ ├── ExternalFilmClient.java # Frameworks & Drivers
│ │ └── ExternalFilmResponse.java# Interface Adapters
│ │
│ └── configuration
│ └── FeignConfig.java # Frameworks & Drivers

└── interfaces
└── rest
├── controller
│ └── FilmController.java # Interface Adapters
└── dto
└── FilmResponse.java # Interface Adapters
Clean Arch Folder Structure Ba7b6b79c0653a444aacce72439a59fe

Follow Hexagonal Architecture

  • External actors interacting with the application (driver side).
  • Domain core implementing business logic.
  • External services used by the application (driven side).
  • The ports (interfaces) ensure loose coupling between these layers, making the application more maintainable and testable.

src/test folder is omitted for simplicity.

File Tree
src/main/java/dev/pollito/spring_java
├── SpringJavaApplication.java

└── sakila
├── domain
│ ├── model
│ │ └── Film.java # Business/Domain logic
│ ├── service
│ │ └── FilmService.java # Business/Domain logic
│ └── port
│ ├── in
│ │ └── FindFilmByIdPort.java # Primary Port
│ └── out
│ ├── FilmPersistencePort.java # Secondary Port
│ └── ExternalFilmInfoPort.java # Secondary Port

├── adapter
│ ├── in
│ │ └── rest
│ │ ├── FilmController.java # Primary Adapter
│ │ └── dto
│ │ └── FilmResponse.java # Primary Adapter
│ │
│ └── out
│ ├── persistence
│ │ ├── entity
│ │ │ └── FilmEntity.java # Secondary Adapter
│ │ ├── repository
│ │ │ └── H2FilmRepository.java # Secondary Adapter
│ │ └── FilmPersistenceAdapter.java # Secondary Adapter
│ │
│ └── external
│ └── feign
│ ├── ExternalFilmClient.java # Secondary Adapter
│ ├── ExternalFilmResponse.java # Secondary Adapter
│ └── ExternalFilmAdapter.java # Secondary Adapter

└── configuration
└── FeignConfig.java
Hexagonal Arch Folder Structure A09a39a5a766b46f75a8bbe9c8ed2b0c

Is It Mandatory to Follow an Architecture?

No. Spring doesn’t enforce names or layers. You could write everything in a single class. But these conventions solve the readability problem: developers instantly understand a class by its name.

Personal Recommendations

I feel that Hexagonal Architecture makes coding easier to understand (or at least is the one that quickly clicked on me). Having said that, I don't follow it word by word, and that's fine.

  • Nobody really follows one architecture to the finest details: Every place I worked tried to follow Clean Architecture but deviated somewhere mid-development and now is whatever.
  • Is ok to bend the rules: Small projects might combine layers.
  • Consistency > perfection: Agree with your team on a structure and stick to it. Refactor later if needed.
    • If your team uses different terms (e.g., DataManager instead of Repository), consistency matters more than the name itself.