DEV Community

Cover image for Scalable Spring Boot Project — A Feature-Based Structure That Grows With You
Karthik Korrayi
Karthik Korrayi

Posted on

Scalable Spring Boot Project — A Feature-Based Structure That Grows With You

If you’ve ever opened an old Spring Boot project and felt like you were stepping into a jungle — controllers in one corner, entities in another, and utility classes sprinkled like confetti — you’re not alone.

When I started working on enterprise-grade apps, I realized something fast:

A messy project doesn’t just slow development — it multiplies confusion.

That’s why I shifted to a feature-based, layered structure — one that keeps things modular, readable, and ready for scale.

This guide walks through a modern Spring Boot project layout, explaining how each folder fits in and why this structure makes collaboration a joy instead of a headache.


🧭 The Big Idea: Organize by Feature, Not by Layer

Traditionally, Spring Boot projects were structured like this:

controller/
service/
repository/
model/
Enter fullscreen mode Exit fullscreen mode

It looked tidy — until the app grew. When you had features like users, orders, payments, and notifications, suddenly you had a dozen controllers and twice as many services in the same folders.

Finding where User logic lived meant jumping between directories and scanning through long lists of unrelated files.

The fix?

Organize by feature, but keep each feature layered internally.

This hybrid model gives you modularity and clear separation of concerns.


🧱 The Structure: Clean, Scalable, and Future-Proof

Here’s the layout I use for most modern Spring Boot applications:

src/
 └── main/
     ├── java/
     │   └── com.example.project/
     │       ├── user/
     │       │   ├── controller/
     │       │   ├── service/
     │       │   ├── repository/
     │       │   ├── entity/
     │       │   ├── dto/
     │       │   └── config/
     │       ├── product/
     │       ├── common/
     │       │   ├── exception/
     │       │   ├── util/
     │       │   └── constants/
     │       ├── config/
     │       └── MainApplication.java
     └── resources/
         ├── application.yml
         ├── application-prod.yml
         └── logback-spring.xml
Enter fullscreen mode Exit fullscreen mode

🧩 Folder-by-Folder Breakdown

🧑‍💻 user/, product/, etc. — Feature Modules

Each feature (like User or Product) is a self-contained package.

Inside each feature:

  • controller → Handles HTTP requests/responses
  • service → Contains business logic
  • repository → Talks to the database
  • entity → Maps database tables
  • dto → Transfers data between layers
  • config → Feature-specific configuration

This makes it easy to find everything related to a single feature — and just as easy to test or refactor it later.


🧭 controller/ — The Entry Point

Controllers define REST endpoints and interact with clients.

@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @GetMapping
    public List<UserDTO> getAllUsers() {
        return userService.getAllUsers();
    }

    @PostMapping
    public UserDTO createUser(@RequestBody UserDTO dto) {
        return userService.createUser(dto);
    }
}
Enter fullscreen mode Exit fullscreen mode

✅ Keep them thin — delegate logic to the service layer.

❌ Don’t put database or validation logic here.


⚙️ service/ — Where Business Logic Lives

The service layer is the heart of your feature.

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
    private final ModelMapper modelMapper;

    public List<UserDTO> getAllUsers() {
        return userRepository.findAll()
                .stream()
                .map(user -> modelMapper.map(user, UserDTO.class))
                .toList();
    }

    public UserDTO createUser(UserDTO dto) {
        UserEntity entity = modelMapper.map(dto, UserEntity.class);
        return modelMapper.map(userRepository.save(entity), UserDTO.class);
    }
}
Enter fullscreen mode Exit fullscreen mode

✅ Reusable, stateless, and testable.

✅ Keeps business logic isolated.


🏗️ repository/ — Data Access Layer

Handles all database operations via Spring Data JPA.

public interface UserRepository extends JpaRepository<UserEntity, Long> {
    Optional<UserEntity> findByEmail(String email);
}
Enter fullscreen mode Exit fullscreen mode

✅ One per entity.

✅ Minimal boilerplate.


🧱 entity/ — Database Representation

Entities define how your data maps to tables.

@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;
}
Enter fullscreen mode Exit fullscreen mode

✅ Keep entities simple and annotation-driven.

✅ Avoid exposing entities directly in APIs — use DTOs.


💼 dto/ — Data Transfer Objects

DTOs define what enters or leaves your APIs.

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {
    private Long id;
    private String name;
    private String email;
}
Enter fullscreen mode Exit fullscreen mode

✅ Protects internal structure.

✅ Keeps responses clean and secure.


🧰 common/ — Shared Code

Used across multiple features:

  • exception/ → Global exception handler, custom exceptions
  • util/ → Helpers like date formatting or validation
  • constants/ → App-wide constants and enums

✅ Reduces duplication.

✅ Centralizes cross-cutting concerns.


🔐 config/ — Global Configuration

Handles app-wide settings:

  • Security (JWT, OAuth2)
  • Swagger/OpenAPI
  • CORS
  • Bean definitions
  • Profiles

✅ Keeps non-business logic separate.

✅ Keeps MainApplication.java clean.


🧾 resources/ — Configurations

Stores environment and logging configs:

  • application.yml → Default configuration
  • application-prod.yml → Production overrides
  • logback-spring.xml → Logging setup

✅ Clean separation of environments.


🐳 DevOps Support Files

At the project root:

  • Dockerfile → Container setup (or JIB)
  • docker-compose.yml → Local orchestration
  • .env → Environment variables
  • pom.xml → Dependencies & lifecycle
  • README.md → Project documentation

✅ Plug-and-play for CI/CD pipelines.


💬 Why This Structure Works

  1. Feature Modularity → Each feature is self-contained.
  2. Clean Separation → Controller → Service → Repository flow is clear.
  3. Scalable → Add new modules easily.
  4. Team Friendly → Different devs can safely own different features.
  5. CI/CD Ready → Works perfectly with Docker, JIB, and pipelines.

🚀 Final Thoughts

A well-structured Spring Boot app isn’t just “nice to have” — it’s what lets you move fast without breaking things.

When your features, configs, and utilities live in harmony, your app (and your team) scales effortlessly.

Clean architecture isn’t about rules — it’s about respect: for your future self, your teammates, and your product.


📁 TL;DR — Folder Overview

Folder Purpose
user/, product/ Self-contained features
controller/ Handles REST endpoints
service/ Core business logic
repository/ Database interaction
entity/ Database model classes
dto/ Data transfer objects
common/ Utilities, exceptions, constants
config/ Global app configuration
resources/ Environment & logging config
.env, Dockerfile, docker-compose.yml DevOps setup

💡 Pro Tip:

If your feature grows large (e.g., user/auth, user/profile), simply nest submodules inside — the structure scales naturally.

Happy coding! 🌿

Top comments (0)