A controller calls a service.
The service calls a repository.
And you, the developer, are stuck wondering why every feature takes seven files to understand.
It's the holy trinity of boilerplate: Controller → Service → Repository.
You've probably typed it a thousand times, and it feels clean — until your app hits a few thousand lines and suddenly nothing makes sense anymore.
The problem isn't Spring Boot itself. The problem is that this layered architecture pattern is outdated — a relic of enterprise Java from 20 years ago. And the longer we cling to it, the harder it gets to build software that's actually readable.

The Illusion of Clean Architecture
We were told that having three layers is "clean." It "separates concerns," they said.
But let's look at what most real-world Spring Boot apps actually do.
You've got something like this:
@RestController
@RequestMapping("/orders")
public class OrderController {
private final OrderService service;
@GetMapping("/{id}")
public OrderDto getOrder(@PathVariable Long id) {
return service.getOrderById(id);
}
}
@Service
public class OrderService {
private final OrderRepository repository;
public OrderDto getOrderById(Long id) {
return repository.findById(id)
.map(OrderMapper::toDto)
.orElseThrow(OrderNotFoundException::new);
}
}
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {}And that's it.
Each layer just passes data to the next one. No actual logic. No decision-making. No domain meaning.
So what's the point of this "Service" layer? It's not separating concerns — it's separating lines of code.
You can't understand the business flow without jumping between three files in three folders. That's not architecture. That's fragmentation disguised as structure.
The Real Cost of the Service Layer
Most developers underestimate how much mental overhead these layers add.
1. It hides your business logic
Your "service" becomes a black box that does… something. Controllers call it. Repositories feed it. But good luck figuring out where an actual rule lives.
2. It enforces unnecessary indirection
A method call to a method call to a method call. The only thing this achieves is confusion — not abstraction.
3. It leads to god classes
Every "service" grows until it handles 20 different responsibilities.
Your once-tidy structure becomes a giant dumping ground called UserService or OrderService with 5,000 lines of code.
4. It slows down feature delivery
Need to add a new feature? Better create three new files, inject dependencies, wire up beans, and touch a half-dozen layers before you even write your first business rule.
All in the name of "clean architecture."
The Solution: Vertical Slice Architecture
There's a better way — and it's not new.
It's called Vertical Slice Architecture, and it turns your codebase from a layer cake into a collection of feature modules that actually make sense.
Instead of organizing by type (controller, service, repository), you organize by feature — each one self-contained, with its controller, handler, and data access if needed.
Here's how it looks:
src/
├── features/
│ ├── orders/
│ │ ├── GetOrder/
│ │ │ ├── GetOrderController.java
│ │ │ ├── GetOrderHandler.java
│ │ │ └── GetOrderRepository.java
│ │ ├── CreateOrder/
│ │ │ ├── CreateOrderController.java
│ │ │ ├── CreateOrderHandler.java
│ │ │ └── CreateOrderRepository.java
│ │ └── domain/
│ │ └── Order.javaEach "slice" is a fully functional, vertical piece of your application. When you want to understand how "Create Order" works, you open that folder and — boom — everything is there.
No more hunting through service packages or tracing method calls across the project.
Why Vertical Slices Work Better
1. You read features, not frameworks
You don't care whether a method is in a service or controller — you care what feature it belongs to. Vertical slices reflect how your business thinks, not how your framework works.
2. No more god services
Logic lives next to the feature it belongs to. You'll never see a UserService with 87 methods again.
3. Testing becomes a breeze
Each slice can be tested independently. Mocking the entire service layer becomes unnecessary because your slice has everything it needs to run.
4. Refactoring is safer
Changing how "Create Order" works? You touch one slice, not ten files across five packages.
5. Perfect fit for CQRS
You can separate commands (writes) and queries (reads) easily, because each is already isolated by design.
What About Shared Logic?
Of course, some code will still be shared — things like Order entities, validation, or domain events.
That's totally fine.
The key difference is: shared logic lives in shared modules, not in a bloated "service" that tries to do everything.
You still keep a domain layer for your core models and rules — but you stop using "service" as a junk drawer.
A Real Example: Deleting the Service Layer
Here's how the "Get Order" feature looks without a service:
@RestController
@RequestMapping("/orders")
public class GetOrderController {
private final GetOrderHandler handler;
@GetMapping("/{id}")
public OrderDto getOrder(@PathVariable Long id) {
return handler.handle(id);
}
}
@Component
public class GetOrderHandler {
private final OrderRepository repository;
public OrderDto handle(Long id) {
return repository.findById(id)
.map(OrderMapper::toDto)
.orElseThrow(OrderNotFoundException::new);
}
}Simple. Explicit. Direct. No "service" middleman pretending to abstract logic that doesn't need abstracting.
When you read this, you immediately understand what's happening. There's no mental overhead. The code is by feature, not by ceremony.
Why Developers Resist This Change
Honestly? Ego and habit.
The three-layer structure feels "professional." It looks organized in UML diagrams. It reminds us of the textbooks and design patterns we were taught.
But real-world maintainability doesn't come from theoretical purity. It comes from clarity — from code that reflects business intent.
Vertical slices look "messy" to people trained in enterprise architecture… until they realize every feature is now self-contained, testable, and obvious.
The Bottom Line
Spring Boot isn't the problem. Our obsession with layers for the sake of layers is.
Deleting your service layer doesn't make your code less structured — it makes it more meaningful.
When you stop forcing everything through Controller → Service → Repository, you start building apps that actually mirror how your domain behaves, not how your framework expects you to organize files.
So go ahead — be a little rebellious.
Delete your service layer. Slice your features vertically. And watch your codebase finally start making sense.
- The Controller → Service → Repository pattern is legacy boilerplate.
- Most "services" are just useless middlemen
- Organize by feature, not by layer.
- Vertical Slice Architecture = simpler, clearer, faster to build and understand.
- Spring Boot works perfectly without the service layer.