Chapter 2.3☕ 14 min read

Stereotype Annotations

Same bean, different hat. Semantics matter in enterprise code.

01The Concept: Layered Architecture

The Hyderabad Software Office Roles Analogy:

In an IT company, everyone doesn't do the same job. There are roles:

  • Receptionist (@Controller): Talks to the client. Sends outsiders to the right room. (Handles web requests).
  • Developer (@Service): Writes the actual business logic. Calculations and rules happen here.
  • Database Admin (@Repository): Fetches data from the database and saves it.
  • Utility Worker (@Component): Does general work with no specific role (like date formatting).

Technically, whether you write @Component, @Service, or @Repository — Spring treats them all the same (as a Bean). But they have different names for semantics (so the developer reading the code knows what the class does).

02How Stereotype Annotations Work
  1. @Component: The parent annotation. Tells Spring "Make me a bean".
  2. @Service: Tells Spring "I hold business logic". Functionally identical to @Component.
  3. @Repository: Tells Spring "I talk to the database". Has a special superpower: it catches raw SQL exceptions and converts them into Spring's unified DataAccessException.
  4. @Controller: Used for web layers (returns HTML views in traditional MVC).
03Full Working Code: Layered Architecture
package com.devinhyderabad;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.stereotype.Repository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;

@SpringBootApplication
public class Application implements CommandLineRunner {
@Autowired
private OrderController orderController;

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

@Override
public void run(String... args) throws Exception {
orderController.placeOrder("Laptop");
}
}

@Component
class OrderController {
@Autowired
private OrderService orderService;

public void placeOrder(String item) {
System.out.println("Controller: Received order for " + item);
orderService.processOrder(item);
}
}
04Controller → Service → Repository Flow
@Service
class OrderService {
@Autowired
private OrderRepository orderRepository;

public void processOrder(String item) {
System.out.println("Service: Applying 10% discount for " + item);
orderRepository.saveToDb(item);
}
}

@Repository
class OrderRepository {
public void saveToDb(String item) {
System.out.println("Repository: Saved " + item + " to H2 Database.");
}
}

The flow is: Controller → Service → Repository. A @Service should never directly call another @Service if it breaks domain boundaries, and a Controller must never touch a Repository directly.

05Why It Matters / Interview Note

Interview Question: "If @Component, @Service, and @Repository all do the same thing (create beans), why do we have three different annotations?"

Answer: They exist for Separation of Concerns and readability. However, @Repository is technically different — it acts as a marker to enable automatic exception translation. Spring intercepts raw SQLException thrown by database drivers and converts them into Spring's consistent DataAccessException hierarchy.

Enterprise Note: Strict architecture rules dictate Controller → Service → Repository flow. Breaking this leads to tightly coupled, untestable code.

Key Takeaways

  • ✅ @Component, @Service, @Repository all create beans — semantics differ
  • ✅ @Repository auto-translates SQLExceptions to Spring DataAccessException
  • ✅ Layered architecture: Controller → Service → Repository
  • ✅ Always follow the layered flow to keep code decoupled and testable