Chapter 4: Basic Annotations Spring Boot
Introduction
Annotations are at the heart of Spring Boot. They allow configuring the application declaratively, without XML, making the code more readable and maintainable.
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand the role of the main Spring Boot annotations
- Use
@RestController,@Service,@Repository - Master HTTP mapping annotations
- Inject dependencies with
@Autowired - Create Spring beans
What is an Annotation?
Definition
An annotation is a form of metadata that provides information about the code without being part of it directly.
Format:
@AnnotationName
@AnnotationName(parameter=value)
Example:
@RestController
@RequestMapping("/api")
public class UserController {
// ...
}
Why Annotations?
Advantages:
- More concise code
- Configuration close to the code
- Type-safe (compile-time checking)
- Facilitates refactoring
@SpringBootApplication
The Master Annotation
package com.example.greeting;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GreetingApplication {
public static void main(String[] args) {
SpringApplication.run(GreetingApplication.class, args);
}
}
Composition of @SpringBootApplication
@SpringBootApplication is a meta-annotation that combines three annotations:
@SpringBootApplication
=
@SpringBootConfiguration
+ @EnableAutoConfiguration
+ @ComponentScan
Detail of Each Annotation
1. @SpringBootConfiguration
Role: Marks the class as a source of bean definitions
Equivalent to:
@Configuration
Allows defining beans:
@SpringBootApplication
public class GreetingApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
2. @EnableAutoConfiguration
Role: Enables Spring Boot's auto-configuration
Example: If Spring Boot detects:
spring-boot-starter-web→ Configures Tomcat, Spring MVCspring-boot-starter-data-jpa→ Configures Hibernate, DataSourcePostgreSQL Driver→ Configures PostgreSQL
Auto-configuration:
3. @ComponentScan
Role: Scans the package and sub-packages for Spring components
Scanned package:
package com.example.greeting; // Base package
// Spring will scan:
// - com.example.greeting
// - com.example.greeting.controller
// - com.example.greeting.service
// - etc.
Detected components:
@Component@Controller/@RestController@Service@Repository@Configuration
Stereotype Annotations
Component Hierarchy
@Component
Role: Generic annotation for any Spring component
Usage:
import org.springframework.stereotype.Component;
@Component
public class EmailValidator {
public boolean isValid(String email) {
return email.contains("@");
}
}
When to use: For utility classes that do not fit into other categories
@Controller
Role: Marks a class as an MVC controller (returns views)
Usage:
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/")
public String home(Model model) {
model.addAttribute("message", "Welcome!");
return "home"; // Returns the view "home.html"
}
}
Architecture:
@RestController
Role: REST controller (returns JSON/XML data)
Equivalent to:
@RestController
=
@Controller + @ResponseBody
Usage:
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
@RestController
public class GreetingController {
@GetMapping("/greeting")
public Greeting greeting() {
return new Greeting(1L, "Hello, World!");
}
}
Architecture:
Difference @Controller vs @RestController:
| Aspect | @Controller | @RestController |
|---|---|---|
| Return | View name (String) | Data (JSON/XML) |
| Usage | Traditional web applications | REST APIs |
| Serialization | No | Automatic (Jackson) |
| @ResponseBody | Required on each method | Implicit |
@Service
Role: Marks a class containing business logic
Usage:
import org.springframework.stereotype.Service;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class GreetingService {
private final AtomicLong counter = new AtomicLong();
private static final String template = "Hello, %s!";
public Greeting createGreeting(String name) {
return new Greeting(
counter.incrementAndGet(),
String.format(template, name)
);
}
}
Responsibilities:
- Business logic
- Complex calculations
- Orchestration of repositories
- Transactions
@Repository
Role: Marks a data access class
Usage with JPA:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Spring Data JPA automatically generates the implementation
}
Responsibilities:
- Database queries
- CRUD operations
- Custom queries
Advantage: Automatic translation of SQL exceptions into Spring exceptions
HTTP Mapping Annotations
Overview
@RequestMapping
Role: Generic annotation for mapping HTTP requests
Syntax:
@RequestMapping(value = "/path", method = RequestMethod.GET)
Usage at the class level:
@RestController
@RequestMapping("/api/users")
public class UserController {
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public User getUser(@PathVariable Long id) {
// GET /api/users/123
return userService.findById(id);
}
}
@GetMapping
Role: Shortcut for GET requests
Equivalent to:
@GetMapping("/path")
=
@RequestMapping(value = "/path", method = RequestMethod.GET)
Examples:
@RestController
@RequestMapping("/api")
public class ProductController {
// GET /api/products
@GetMapping("/products")
public List<Product> getAllProducts() {
return productService.findAll();
}
// GET /api/products/123
@GetMapping("/products/{id}")
public Product getProduct(@PathVariable Long id) {
return productService.findById(id);
}
// GET /api/products?category=books
@GetMapping("/products")
public List<Product> getProductsByCategory(@RequestParam String category) {
return productService.findByCategory(category);
}
}
@PostMapping
Role: Shortcut for POST requests (creation)
Usage:
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}
Request:
POST /api/users
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com"
}
@PutMapping
Role: Shortcut for PUT requests (full update)
Usage:
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
return userService.update(id, user);
}
Request:
PUT /api/users/123
Content-Type: application/json
{
"name": "John Doe Updated",
"email": "john.updated@example.com"
}
@DeleteMapping
Role: Shortcut for DELETE requests (deletion)
Usage:
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
Request:
DELETE /api/users/123
@PatchMapping
Role: Shortcut for PATCH requests (partial update)
Usage:
@PatchMapping("/{id}")
public User partialUpdate(@PathVariable Long id, @RequestBody Map<String, Object> updates) {
return userService.partialUpdate(id, updates);
}
Request:
PATCH /api/users/123
Content-Type: application/json
{
"email": "newemail@example.com"
}
Summary Table
| HTTP Method | Annotation | Action | Idempotent |
|---|---|---|---|
| GET | @GetMapping | Read | ✅ |
| POST | @PostMapping | Create | ❌ |
| PUT | @PutMapping | Update (full) | ✅ |
| PATCH | @PatchMapping | Update (partial) | ❌ |
| DELETE | @DeleteMapping | Delete | ✅ |
Parameter Annotations
@PathVariable
Role: Extract a value from the URL
Syntax:
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
// URL: /users/123
// id = 123
return userService.findById(id);
}
Multiple variables:
@GetMapping("/users/{userId}/orders/{orderId}")
public Order getOrder(
@PathVariable Long userId,
@PathVariable Long orderId
) {
return orderService.findByUserAndId(userId, orderId);
}
Different name:
@GetMapping("/users/{id}")
public User getUser(@PathVariable("id") Long userId) {
return userService.findById(userId);
}
@RequestParam
Role: Extract query string parameters
Syntax:
@GetMapping("/users")
public List<User> getUsers(@RequestParam String name) {
// URL: /users?name=John
// name = "John"
return userService.findByName(name);
}
Optional parameter:
@GetMapping("/users")
public List<User> getUsers(
@RequestParam(required = false) String name
) {
if (name != null) {
return userService.findByName(name);
}
return userService.findAll();
}
Default value:
@GetMapping("/users")
public List<User> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
return userService.findAll(page, size);
}
Multiple parameters:
@GetMapping("/users")
public List<User> searchUsers(
@RequestParam String name,
@RequestParam int age,
@RequestParam String city
) {
// URL: /users?name=John&age=30&city=Montreal
return userService.search(name, age, city);
}
@RequestBody
Role: Map the HTTP request body to a Java object
Usage:
@PostMapping("/users")
public User createUser(@RequestBody User user) {
return userService.save(user);
}
HTTP Request:
POST /users
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com",
"age": 30
}
Received Java Object:
User user = new User();
user.setName("John Doe");
user.setEmail("john@example.com");
user.setAge(30);
@RequestHeader
Role: Extract an HTTP header
Usage:
@GetMapping("/secure")
public String secureEndpoint(
@RequestHeader("Authorization") String token
) {
// token = "Bearer eyJhbGciOiJIUzI1NiIs..."
return "Authenticated!";
}
Comparison Table
| Annotation | Source | Example URL | Usage |
|---|---|---|---|
| @PathVariable | URL path | /users/123 | Resource identifiers |
| @RequestParam | Query string | /users?name=John | Filters, pagination |
| @RequestBody | HTTP body | POST/PUT body | Data submission |
| @RequestHeader | HTTP headers | Authorization: Bearer token | Authentication |
Dependency Injection
@Autowired
Role: Automatically injects a dependency
Types of Injection
1. Constructor Injection (Recommended)
@RestController
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/users")
public List<User> getUsers() {
return userService.findAll();
}
}
Since Spring 4.3: @Autowired is optional if a single constructor
@RestController
public class UserController {
private final UserService userService;
// @Autowired not necessary
public UserController(UserService userService) {
this.userService = userService;
}
}
Advantages:
- Immutability (final)
- Easier testing
- Mandatory (no NullPointerException)
2. Setter Injection
@RestController
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
Advantages:
- Optional dependencies
- Possible reconfiguration
Disadvantages:
- Mutable
- Risk of NullPointerException
3. Field Injection (Not Recommended)
@RestController
public class UserController {
@Autowired
private UserService userService;
}
Disadvantages:
- Hard to test
- Strong coupling
- Cannot create an immutable object
Recommendation: Always use constructor injection
Complete Architecture Example
// Entity
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// Getters/Setters
}
// Repository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
// Service
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public List<User> findAll() {
return userRepository.findAll();
}
public User save(User user) {
return userRepository.save(user);
}
}
// Controller
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}
@PostMapping
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}
Dependency Flow:
Practical Exercises
Exercise 1: Create a REST Controller
Objective: Use @RestController and @GetMapping
Instructions:
- Create a class
HelloController - Annotate with
@RestController - Create a method
sayHello()that returns "Hello, Spring Boot!" - Annotate with
@GetMapping("/hello") - Test with the browser
Exercise 2: Use @PathVariable
Objective: Extract URL parameters
Instructions:
- Create an endpoint
/greet/\{name\} - Return "Hello, {name}!"
- Test with
/greet/John
Exercise 3: Create a Service
Objective: Separate business logic
Instructions:
- Create a class
CalculatorServicewith@Service - Add a method
add(int a, int b) - Create a
CalculatorController - Inject the service via constructor
- Create an endpoint
/add?a=5&b=3
Exercise 4: Complete Architecture
Objective: Create a 3-layer architecture
Instructions:
- Create a class
Product(model) - Create
ProductServicewith an in-memory list - Create
ProductController - Implement:
- GET /products (all)
- GET /products/{id}
- POST /products
Review Questions
-
What annotations make up @SpringBootApplication?
-
What is the difference between @Controller and @RestController?
-
When to use @Service vs @Component?
-
What is the difference between @PathVariable and @RequestParam?
-
Why is constructor injection recommended?
-
What does the @RequestBody annotation do?
-
Which HTTP method corresponds to @PostMapping?
-
What is the purpose of @Repository?
Chapter Summary
Key Points
@SpringBootApplicationcombines configuration, auto-configuration, and scanning@RestController=@Controller+@ResponseBody@Servicefor business logic,@Repositoryfor data access- Mapping annotations:
@GetMapping,@PostMapping, etc. @PathVariablefor URL,@RequestParamfor query strings- Constructor injection recommended
Best Practices
- Use specialized annotations (@Service, @Repository) rather than @Component
- Constructor injection with final
- One controller per REST resource
- Clearly separate responsibilities (Controller → Service → Repository)
- Name endpoints consistently (REST conventions)
Next Steps
Congratulations! You have completed Module 2.
In Module 3 (coming soon), you will delve into:
- Inversion of Control in detail
- Bean lifecycle
- Scopes
- Advanced configuration
Put into Practice
Before moving to the next module, make sure to:
- Complete Lab 2.1: Greeting Application
- Redo the exercises from each chapter
- Create your own mini-project to consolidate your knowledge