Skip to main content

Spring Boot Annotations

Introduction

Spring Boot annotations are special markers added to Java classes, methods, fields, and other program elements to provide additional information to the Spring Framework. These annotations simplify application development by reducing boilerplate code and configuration. They are a fundamental part of Spring Boot's "convention over configuration" philosophy, allowing developers to focus more on business logic rather than infrastructure setup.

In this guide, we'll explore the most commonly used Spring Boot annotations, understand their purpose, and see how they work in real applications. By the end of this tutorial, you'll have a solid understanding of how to use Spring Boot annotations to build efficient and maintainable applications.

Core Spring Boot Annotations

@SpringBootApplication

The @SpringBootApplication annotation is the starting point of any Spring Boot application. It combines three annotations:

  • @Configuration: Tags the class as a source of bean definitions
  • @EnableAutoConfiguration: Tells Spring Boot to start adding beans based on classpath settings
  • @ComponentScan: Tells Spring to look for components in the current package and its sub-packages
java
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

In this example, DemoApplication is the entry point of our Spring Boot application. The @SpringBootApplication annotation enables auto-configuration and component scanning, while the SpringApplication.run() method bootstraps the application.

@Component, @Service, @Repository, and @Controller

These annotations define the role of a component in your application:

  • @Component: Generic stereotype for any Spring-managed component
  • @Service: Indicates that the class provides business services
  • @Repository: Indicates that the class is a data access object (DAO)
  • @Controller: Indicates that the class is a web controller, processing HTTP requests
java
// Component example
@Component
public class EmailNotifier {
public void sendEmail(String to, String subject, String body) {
// Email sending logic
}
}

// Service example
@Service
public class UserService {
private final UserRepository userRepository;

public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}

public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
}

// Repository example
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByEmail(String email);
}

// Controller example
@Controller
public class UserController {
private final UserService userService;

public UserController(UserService userService) {
this.userService = userService;
}

@GetMapping("/users/{id}")
public String getUser(@PathVariable Long id, Model model) {
model.addAttribute("user", userService.findById(id));
return "user-details";
}
}

While all these annotations mark a class as a Spring-managed component, they have different semantic meanings, helping to clarify the role of each component in your application architecture.

Dependency Injection Annotations

@Autowired

The @Autowired annotation enables automatic dependency injection. Spring looks for matching beans and injects them where this annotation is applied.

java
@Service
public class ProductService {

@Autowired
private ProductRepository productRepository;

public List<Product> getAllProducts() {
return productRepository.findAll();
}
}

You can use @Autowired on constructors, setters, or fields. However, constructor injection is generally considered best practice:

java
@Service
public class ProductService {

private final ProductRepository productRepository;

@Autowired // Optional on constructors since Spring 4.3
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}

public List<Product> getAllProducts() {
return productRepository.findAll();
}
}

@Qualifier

When multiple beans of the same type are available, use @Qualifier to specify which one to inject:

java
@Service
public class NotificationService {

private final MessageSender messageSender;

@Autowired
public NotificationService(@Qualifier("emailSender") MessageSender messageSender) {
this.messageSender = messageSender;
}

public void sendNotification(String message) {
messageSender.send(message);
}
}

@Component("emailSender")
public class EmailSender implements MessageSender {
@Override
public void send(String message) {
// Send email
}
}

@Component("smsSender")
public class SmsSender implements MessageSender {
@Override
public void send(String message) {
// Send SMS
}
}

@Value

Use @Value to inject values from properties files or environment variables:

java
@Service
public class EmailService {

@Value("${email.from}")
private String fromEmail;

@Value("${email.host}")
private String emailHost;

@Value("${email.port:25}") // Default value 25 if property not found
private int emailPort;

public void sendEmail(String to, String subject, String body) {
System.out.println("Sending email from " + fromEmail + " using " + emailHost + ":" + emailPort);
// Email sending logic
}
}

In your application.properties file:

properties
[email protected]
email.host=smtp.example.com
email.port=587

Spring MVC Annotations

@RestController

@RestController combines @Controller and @ResponseBody, making it ideal for building RESTful web services:

java
@RestController
@RequestMapping("/api/products")
public class ProductController {

private final ProductService productService;

public ProductController(ProductService productService) {
this.productService = productService;
}

@GetMapping
public List<Product> getAllProducts() {
return productService.getAllProducts();
}

@GetMapping("/{id}")
public ResponseEntity<Product> getProductById(@PathVariable Long id) {
Product product = productService.getProductById(id);
if (product != null) {
return ResponseEntity.ok(product);
} else {
return ResponseEntity.notFound().build();
}
}

@PostMapping
public ResponseEntity<Product> createProduct(@RequestBody Product product) {
Product createdProduct = productService.saveProduct(product);
return ResponseEntity
.created(URI.create("/api/products/" + createdProduct.getId()))
.body(createdProduct);
}
}

@RequestMapping and HTTP Method Annotations

Spring provides specialized annotations for different HTTP methods:

  • @GetMapping: Handles HTTP GET requests
  • @PostMapping: Handles HTTP POST requests
  • @PutMapping: Handles HTTP PUT requests
  • @DeleteMapping: Handles HTTP DELETE requests
  • @PatchMapping: Handles HTTP PATCH requests
java
@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.getAllUsers();
}

@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}

@PostMapping
public User createUser(@RequestBody User user) {
return userService.saveUser(user);
}

@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
user.setId(id);
return userService.updateUser(user);
}

@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
}
}

@PathVariable, @RequestParam, and @RequestBody

These annotations help extract data from HTTP requests:

  • @PathVariable: Extracts values from the URI path
  • @RequestParam: Extracts query parameters
  • @RequestBody: Extracts data from the request body
java
@RestController
@RequestMapping("/api/products")
public class ProductController {

// Path variable example: /api/products/42
@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id) {
return productService.getProduct(id);
}

// Request parameters example: /api/products/search?name=laptop&category=electronics
@GetMapping("/search")
public List<Product> searchProducts(
@RequestParam String name,
@RequestParam(required = false) String category) {
if (category != null) {
return productService.findByNameAndCategory(name, category);
} else {
return productService.findByName(name);
}
}

// Request body example
@PostMapping
public Product createProduct(@RequestBody Product product) {
return productService.saveProduct(product);
}
}

Data Persistence Annotations

@Entity and @Table

@Entity marks a class as a JPA entity, while @Table specifies the table name:

java
@Entity
@Table(name = "users")
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, length = 100)
private String name;

@Column(unique = true, nullable = false)
private String email;

@Column(name = "created_at")
private LocalDateTime createdAt;

// Getters and setters
}

@Id and @GeneratedValue

@Id marks a field as the primary key, and @GeneratedValue specifies how it should be generated:

java
@Entity
public class Product {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;
private BigDecimal price;

// Getters and setters
}

Spring Data Repository Annotations

Spring Data provides repository interfaces that simplify data access:

java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {

// Custom query methods
List<User> findByEmail(String email);

List<User> findByNameContainingIgnoreCase(String name);

@Query("SELECT u FROM User u WHERE u.createdAt > :date")
List<User> findRecentUsers(@Param("date") LocalDateTime date);
}

Configuration Annotations

@Configuration and @Bean

@Configuration marks a class as a source of bean definitions, while @Bean defines a Spring bean:

java
@Configuration
public class AppConfig {

@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}

@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper;
}
}

@PropertySource and @ConfigurationProperties

@PropertySource specifies a properties file, while @ConfigurationProperties binds properties to a configuration class:

java
@Configuration
@PropertySource("classpath:mail.properties")
public class MailConfig {

@Value("${mail.host}")
private String host;

@Value("${mail.port}")
private int port;

@Bean
public JavaMailSender mailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(host);
mailSender.setPort(port);
return mailSender;
}
}

Using @ConfigurationProperties for type-safe configuration:

java
@Configuration
@ConfigurationProperties(prefix = "app.mail")
public class MailSettings {

private String host;
private int port;
private String username;
private String password;
private boolean auth;
private boolean starttls;

// Getters and setters
}

In your application.properties:

properties
app.mail.host=smtp.example.com
app.mail.port=587
[email protected]
app.mail.password=secret
app.mail.auth=true
app.mail.starttls=true

Practical Example: Building a Simple REST API

Let's put everything together by building a simple task management API:

Task Entity

java
@Entity
@Table(name = "tasks")
public class Task {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String title;

private String description;

@Column(name = "is_completed")
private boolean completed;

@Column(name = "due_date")
private LocalDate dueDate;

// Getters and setters
}

Task Repository

java
@Repository
public interface TaskRepository extends JpaRepository<Task, Long> {
List<Task> findByCompleted(boolean completed);
List<Task> findByDueDateBefore(LocalDate date);
}

Task Service

java
@Service
public class TaskService {

private final TaskRepository taskRepository;

public TaskService(TaskRepository taskRepository) {
this.taskRepository = taskRepository;
}

public List<Task> getAllTasks() {
return taskRepository.findAll();
}

public Task getTaskById(Long id) {
return taskRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(
HttpStatus.NOT_FOUND, "Task not found with id: " + id));
}

public Task createTask(Task task) {
return taskRepository.save(task);
}

public Task updateTask(Long id, Task taskDetails) {
Task task = getTaskById(id);
task.setTitle(taskDetails.getTitle());
task.setDescription(taskDetails.getDescription());
task.setCompleted(taskDetails.isCompleted());
task.setDueDate(taskDetails.getDueDate());
return taskRepository.save(task);
}

public void deleteTask(Long id) {
taskRepository.deleteById(id);
}

public List<Task> getCompletedTasks() {
return taskRepository.findByCompleted(true);
}

public List<Task> getOverdueTasks() {
return taskRepository.findByDueDateBefore(LocalDate.now());
}
}

Task Controller

java
@RestController
@RequestMapping("/api/tasks")
public class TaskController {

private final TaskService taskService;

public TaskController(TaskService taskService) {
this.taskService = taskService;
}

@GetMapping
public List<Task> getAllTasks() {
return taskService.getAllTasks();
}

@GetMapping("/{id}")
public Task getTaskById(@PathVariable Long id) {
return taskService.getTaskById(id);
}

@PostMapping
public ResponseEntity<Task> createTask(@RequestBody Task task) {
Task createdTask = taskService.createTask(task);
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(createdTask.getId())
.toUri();
return ResponseEntity.created(location).body(createdTask);
}

@PutMapping("/{id}")
public Task updateTask(@PathVariable Long id, @RequestBody Task task) {
return taskService.updateTask(id, task);
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteTask(@PathVariable Long id) {
taskService.deleteTask(id);
return ResponseEntity.noContent().build();
}

@GetMapping("/completed")
public List<Task> getCompletedTasks() {
return taskService.getCompletedTasks();
}

@GetMapping("/overdue")
public List<Task> getOverdueTasks() {
return taskService.getOverdueTasks();
}
}

Application Class

java
@SpringBootApplication
public class TaskManagementApplication {
public static void main(String[] args) {
SpringApplication.run(TaskManagementApplication.class, args);
}

@Bean
public CommandLineRunner demoData(TaskRepository taskRepository) {
return args -> {
// Add some sample tasks
taskRepository.save(new Task("Learn Spring Boot", "Study annotations and core concepts", false, LocalDate.now().plusDays(7)));
taskRepository.save(new Task("Build REST API", "Create a task management API", false, LocalDate.now().plusDays(14)));
taskRepository.save(new Task("Write tests", "Add unit and integration tests", false, LocalDate.now().plusDays(21)));
};
}
}

Summary

Spring Boot annotations significantly simplify Java application development by reducing boilerplate code and providing a convention-over-configuration approach. In this guide, we've covered the most important annotations:

  1. Core annotations like @SpringBootApplication, @Component, @Service, @Repository, and @Controller
  2. Dependency injection annotations like @Autowired, @Qualifier, and @Value
  3. Spring MVC annotations for building web applications and RESTful services
  4. Data persistence annotations for working with databases
  5. Configuration annotations for customizing application behavior

By understanding and effectively using these annotations, you can build robust, maintainable Spring Boot applications with minimal configuration.

Additional Resources

Exercises

  1. Create a simple Spring Boot application that uses @RestController to expose a "Hello, World!" endpoint.
  2. Implement a service with constructor injection that uses an external configuration property.
  3. Build a complete CRUD application with entities, repositories, services, and controllers.
  4. Create a custom configuration class with @ConfigurationProperties.
  5. Implement a custom Spring Data repository with specific query methods.

By completing these exercises, you'll strengthen your understanding of Spring Boot annotations and become more comfortable using them in real-world applications.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)