00:00

Spring Boot Annotations Explained with Examples

Annotations are the language you use to talk to Spring Boot. A single word such as @Service or @GetMapping tells Spring what a class is for, which objects it needs, and how it should behave. This tutorial walks through the annotations you will use every day, grouped by purpose, with a short, practical example for each one.

1. What Are Annotations?

An annotation is a label that starts with @ and adds information to a class, method, field or parameter. The annotation itself does nothing. Spring reads these labels when the application starts and acts on them: it creates objects, connects them together, maps URLs to methods, and wraps methods in transactions.

Before annotations, the same setup lived in long XML files. Annotations keep the configuration right next to the code it describes, which makes applications shorter and easier to read.

2. Core Spring Boot Annotations

2.1 @SpringBootApplication

This is the starting point of every Spring Boot application. Put it on the class that contains the main method:

LibraryApplication.java
package com.javabytechie.library;

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

@SpringBootApplication
public class LibraryApplication {

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

@SpringBootApplication is a combination of three annotations: @Configuration + @EnabledAutoConfiguration + ComponentScan

AnnotationWhat it does
@SpringBootConfigurationMarks the class as a source of bean definitions (a special form of @Configuration)
@EnableAutoConfigurationLets Spring Boot configure beans automatically based on your dependencies
@ComponentScanFinds your annotated classes in this package and all sub-packages

2.2 @EnableAutoConfiguration

Auto-configuration looks at the libraries on your classpath and the properties you have set, then creates sensible beans for you. For example:

  • With spring-boot-starter-web, it starts an embedded Tomcat server and sets up Spring MVC.
  • With spring-boot-starter-data-jpa and a database driver, it creates a DataSource and an EntityManagerFactory.
  • If you define your own bean of the same type, Spring Boot steps back and uses yours.

You rarely write this annotation yourself, because @SpringBootApplication already includes it.

2.3 @ComponentScan

Component scanning searches packages for classes marked with @Component, @Service, @Repository, @Controller or @Configuration, and registers them as beans. By default it starts from the package of your main class. Add it explicitly only when some classes live outside that package:

LibraryApplication.java
@SpringBootApplication
@ComponentScan(basePackages = {"com.javabytechie.library", "com.javabytechie.shared"})
public class LibraryApplication {
    // ...
}

Tip: keep your main class in the root package, such as com.javabytechie.library, so every other package is scanned automatically.

3. Stereotype Annotations

Stereotype annotations tell Spring that a class should be managed as a bean, and they also describe the role the class plays. Spring creates one instance of each and injects it wherever it is needed.

AnnotationLayerTypical use
@ComponentAnyGeneral helper classes
@ServiceBusinessBusiness rules and use cases
@RepositoryData accessReading and writing data
@ControllerWeb (pages)Returning HTML views
@RestControllerWeb (APIs)Returning JSON or XML

3.1 @Component

The generic stereotype. Use it for helper classes that do not fit any of the other layers:

SlugGenerator.java
@Component
public class SlugGenerator {

    public String toSlug(String title) {
        return title.trim()
                .toLowerCase()
                .replaceAll("[^a-z0-9]+", "-")
                .replaceAll("(^-|-$)", "");
    }
}

3.2 @Service

Marks a class that holds business logic. Technically it behaves like @Component, but the name makes your design clear to every reader:

BookService.java
@Service
public class BookService {

    private final BookRepository bookRepository;

    public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    public Book findByIsbn(String isbn) {
        return bookRepository.findByIsbn(isbn)
                .orElseThrow(() -> new BookNotFoundException(isbn));
    }
}

3.3 @Repository

Marks a class that talks to a database. Besides registering the bean, it converts database-specific exceptions into Spring’s DataAccessException hierarchy, so the rest of your code does not depend on a particular database driver:

JdbcBookRepository.java
@Repository
public class JdbcBookRepository {

    private final JdbcClient jdbcClient;

    public JdbcBookRepository(JdbcClient jdbcClient) {
        this.jdbcClient = jdbcClient;
    }

    public int countAll() {
        return jdbcClient.sql("SELECT COUNT(*) FROM books")
                .query(Integer.class)
                .single();
    }
}

If you use Spring Data JPA interfaces such as JpaRepository, you do not need this annotation. Spring Data registers those repositories for you.

3.4 @Controller

Used in web applications that return HTML pages. The method returns the name of a template, not the response itself:

HomeController.java
@Controller
public class HomeController {

    @GetMapping("/")
    public String home(Model model) {
        model.addAttribute("message", "Welcome to the library");
        return "home"; // renders templates/home.html
    }
}

3.5 @RestController

Used for REST APIs. It combines @Controller and @ResponseBody, so whatever a method returns is written straight to the response, usually as JSON:

BookController.java
@RestController
@RequestMapping("/api/books")
public class BookController {

    private final BookService bookService;

    public BookController(BookService bookService) {
        this.bookService = bookService;
    }

    @GetMapping("/{isbn}")
    public Book getBook(@PathVariable String isbn) {
        return bookService.findByIsbn(isbn); // converted to JSON automatically
    }
}

4. Dependency Injection Annotations

Dependency injection means a class receives the objects it needs instead of creating them itself. Spring looks at what a bean needs and supplies the matching beans automatically.

4.1 @Autowired and constructor injection

There are three ways to inject a dependency. Constructor injection is the recommended one:

LoanService.java
@Service
public class LoanService {

    private final BookService bookService;
    private final MemberService memberService;

    // Only one constructor, so @Autowired is optional
    public LoanService(BookService bookService, MemberService memberService) {
        this.bookService = bookService;
        this.memberService = memberService;
    }
}
StyleExampleRecommended?
ConstructorDependencies passed into the constructorYes: fields can be final and the class is easy to test
Setter@Autowired on a setter methodOnly for optional dependencies
Field@Autowired directly on a fieldNo: hides dependencies and makes testing harder

4.2 @Qualifier

When two beans have the same type, Spring cannot guess which one you want. Give each bean a name and pick one with @Qualifier:

Notifiers.java
public interface Notifier {
    void send(String to, String message);
}

@Component("emailNotifier")
public class EmailNotifier implements Notifier { /* ... */ }

@Component("smsNotifier")
public class SmsNotifier implements Notifier { /* ... */ }

@Service
public class ReminderService {

    private final Notifier notifier;

    public ReminderService(@Qualifier("smsNotifier") Notifier notifier) {
        this.notifier = notifier;
    }
}

4.3 @Primary

Marks the bean to use by default when several of the same type exist. Other classes can still choose a different one with @Qualifier:

EmailNotifier.java
@Primary
@Component("emailNotifier")
public class EmailNotifier implements Notifier {
    // used wherever a Notifier is injected without a qualifier
}

4.4 @Lazy

By default, Spring creates all singleton beans at startup. @Lazy delays creation until the bean is first used, which helps with rarely used, expensive objects:

ReportGenerator.java
@Lazy
@Component
public class ReportGenerator {
    // created only when another bean first calls it
}

5. Configuration and Property Annotations

5.1 @Configuration and @Bean

Use @Bean methods inside a @Configuration class to create beans from classes you cannot annotate yourself, such as classes from a third-party library:

AppConfig.java
@Configuration
public class AppConfig {

    @Bean
    public Clock clock() {
        return Clock.systemUTC();
    }

    @Bean
    public RestClient catalogClient(RestClient.Builder builder) {
        return builder.baseUrl("https://catalog.example.com").build();
    }
}

The method name becomes the bean name, and the return value becomes the bean. Parameters of a @Bean method, like builder above, are injected automatically.

5.2 @Value

Injects a single value from application.properties, environment variables or command-line arguments:

application.properties
library.name=City Library
library.max-loans=5
LibraryInfo.java
@Component
public class LibraryInfo {

    private final String name;
    private final int maxLoans;

    public LibraryInfo(@Value("${library.name}") String name,
                       @Value("${library.max-loans:3}") int maxLoans) {
        this.name = name;
        this.maxLoans = maxLoans;
    }
}

The part after the colon, :3, is a default used when the property is missing.

5.3 @ConfigurationProperties

When you have several related settings, bind them all to one type-safe object instead of using many @Value annotations:

application.properties
library.loan.max-books=5
library.loan.duration-days=14
library.loan.late-fee-per-day=0.50
LoanProperties.java
@ConfigurationProperties(prefix = "library.loan")
public record LoanProperties(int maxBooks, int durationDays, BigDecimal lateFeePerDay) {
}
LibraryApplication.java
@SpringBootApplication
@ConfigurationPropertiesScan
public class LibraryApplication {
    // ...
}

Spring Boot matches max-books to maxBooks automatically. @ConfigurationPropertiesScan finds and registers every properties class in your packages.

6. Profiles and Conditional Beans

These annotations decide whether a bean is created at all, based on the environment.

6.1 @Profile

Creates a bean only when a specific profile is active. A common use is a fake implementation for local development:

PaymentConfig.java
@Configuration
public class PaymentConfig {

    @Bean
    @Profile("dev")
    public PaymentGateway fakePaymentGateway() {
        return new FakePaymentGateway(); // always approves, no real money
    }

    @Bean
    @Profile("prod")
    public PaymentGateway stripePaymentGateway(StripeProperties properties) {
        return new StripePaymentGateway(properties);
    }
}

Activate a profile with spring.profiles.active=dev in your properties, or with --spring.profiles.active=prod on the command line.

6.2 @ConditionalOnProperty

Creates a bean only when a property has a certain value. This works well as a simple feature switch:

RecommendationService.java
@Service
@ConditionalOnProperty(name = "library.recommendations.enabled", havingValue = "true")
public class RecommendationService {
    // exists only when library.recommendations.enabled=true
}

Spring Boot has many other conditions, such as @ConditionalOnMissingBean and @ConditionalOnClass. Auto-configuration itself is built from them.

7. Web and REST Annotations

This complete controller shows the most common web annotations working together:

BookController.java
@RestController
@RequestMapping("/api/books")
public class BookController {

    private final BookService bookService;

    public BookController(BookService bookService) {
        this.bookService = bookService;
    }

    @GetMapping
    public List<Book> search(@RequestParam(defaultValue = "") String title,
                             @RequestParam(defaultValue = "0") int page) {
        return bookService.search(title, page);
    }

    @GetMapping("/{id}")
    public Book getById(@PathVariable Long id) {
        return bookService.findById(id);
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Book create(@RequestBody BookRequest request) {
        return bookService.create(request);
    }

    @PutMapping("/{id}")
    public Book update(@PathVariable Long id, @RequestBody BookRequest request) {
        return bookService.update(id, request);
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable Long id) {
        bookService.delete(id);
    }
}
AnnotationPurposeExample request
@RequestMappingCommon URL prefix for the whole class/api/books/...
@GetMappingRead dataGET /api/books/7
@PostMappingCreate dataPOST /api/books
@PutMappingReplace or update dataPUT /api/books/7
@PatchMappingPartially update dataPATCH /api/books/7
@DeleteMappingDelete dataDELETE /api/books/7
@PathVariableReads a value from the URL path7 in /api/books/7
@RequestParamReads a query parameter?title=java&page=2
@RequestBodyConverts the JSON body into a Java object{"title": "..."}
@ResponseStatusSets the HTTP status of a successful response201 Created

7.1 @RequestHeader

Reads an HTTP header, such as a language or a client ID:

GreetingController.java
@GetMapping("/greeting")
public String greeting(@RequestHeader(name = "Accept-Language", defaultValue = "en") String language) {
    return language.startsWith("fr") ? "Bonjour" : "Hello";
}

7.2 @ResponseBody

Writes a method’s return value directly into the response. You only need it in a plain @Controller, because @RestController already applies it to every method:

HealthController.java
@Controller
public class HealthController {

    @GetMapping("/ping")
    @ResponseBody
    public String ping() {
        return "pong"; // sent as text, not treated as a template name
    }
}

7.3 @CrossOrigin

Allows a browser application on another domain, such as a React front end, to call this controller:

BookController.java
@CrossOrigin(origins = "https://library-ui.example.com")
@RestController
@RequestMapping("/api/books")
public class BookController {
    // ...
}

8. Error Handling Annotations

Instead of writing try/catch blocks in every controller, handle exceptions in one place with @RestControllerAdvice and @ExceptionHandler:

BookNotFoundException.java
public class BookNotFoundException extends RuntimeException {

    public BookNotFoundException(Long id) {
        super("Book " + id + " was not found");
    }
}
GlobalExceptionHandler.java
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BookNotFoundException.class)
    public ProblemDetail handleNotFound(BookNotFoundException ex) {
        ProblemDetail problem = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
        problem.setTitle("Book not found");
        return problem;
    }
}

Now any controller can simply throw BookNotFoundException, and the client always receives a consistent 404 response:

Response
{
  "type": "about:blank",
  "title": "Book not found",
  "status": 404,
  "detail": "Book 42 was not found",
  "instance": "/api/books/42"
}

9. JPA and Database Annotations

These annotations come from Jakarta Persistence (JPA) and map Java classes to database tables:

Book.java
@Entity
@Table(name = "books")
public class Book {

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

    @Column(nullable = false, length = 200)
    private String title;

    @Column(unique = true, length = 13)
    private String isbn;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author_id")
    private Author author;

    @Enumerated(EnumType.STRING)
    private BookStatus status;

    // constructors, getters and setters
}
Author.java
@Entity
@Table(name = "authors")
public class Author {

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

    private String name;

    @OneToMany(mappedBy = "author")
    private List<Book> books = new ArrayList<>();

    // constructors, getters and setters
}
AnnotationMeaning
@EntityThis class is stored in the database
@TableSets the table name (defaults to the class name)
@IdThe primary key field
@GeneratedValueThe database generates the key value
@ColumnColumn details such as name, length, nullable and unique
@ManyToOne / @OneToManyRelationships between entities (many books, one author)
@JoinColumnThe foreign key column used by a relationship
@Enumerated(EnumType.STRING)Stores an enum by name, which is safer than the default number

For a deeper look at repositories and queries, see the Spring Data JPA Overview tutorial.

10. Transaction and AOP Annotations

10.1 @Transactional

Runs a method inside a database transaction. If the method throws an unchecked exception, every change it made is rolled back, so data is never left half-updated:

LoanService.java
@Service
public class LoanService {

    private final BookRepository bookRepository;
    private final LoanRepository loanRepository;

    public LoanService(BookRepository bookRepository, LoanRepository loanRepository) {
        this.bookRepository = bookRepository;
        this.loanRepository = loanRepository;
    }

    @Transactional
    public Loan borrow(Long bookId, Long memberId) {
        Book book = bookRepository.findById(bookId).orElseThrow();
        book.markAsBorrowed();                                  // update 1
        return loanRepository.save(new Loan(book, memberId));   // update 2
        // both updates are saved together, or neither is
    }

    @Transactional(readOnly = true)
    public List<Loan> activeLoans(Long memberId) {
        return loanRepository.findByMemberIdAndReturnedFalse(memberId);
    }
}

Use readOnly = true for methods that only read data. It lets Hibernate skip change tracking, which saves time and memory.

10.2 @Aspect

Aspect-oriented programming (AOP) lets you run the same code around many methods without repeating it, for example logging or timing. This aspect logs how long every service method takes:

TimingAspect.java
@Aspect
@Component
public class TimingAspect {

    private static final Logger log = LoggerFactory.getLogger(TimingAspect.class);

    @Around("within(@org.springframework.stereotype.Service *)")
    public Object logDuration(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return joinPoint.proceed();
        } finally {
            long took = System.currentTimeMillis() - start;
            log.info("{} took {} ms", joinPoint.getSignature().toShortString(), took);
        }
    }
}

Aspects need AspectJ support on the classpath: add spring-boot-starter-aop (called spring-boot-starter-aspectj in Spring Boot 4). Other advice annotations include @Before, @After and @AfterThrowing.

11. Validation Annotations

Add @Valid to a request body, and Spring checks the constraint annotations on its fields before your method runs:

BookRequest.java
public record BookRequest(

        @NotBlank(message = "Title is required")
        @Size(max = 200)
        String title,

        @Pattern(regexp = "\\d{13}", message = "ISBN must have 13 digits")
        String isbn,

        @NotNull
        @Positive
        Long authorId
) {
}
BookController.java
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Book create(@Valid @RequestBody BookRequest request) {
    return bookService.create(request);
}
AnnotationChecks that the value...
@NotNullis not null
@NotBlankis text with at least one non-space character
@Sizehas a length or size within a range
@Min / @Maxis a number within limits
@Positiveis greater than zero
@Emaillooks like an email address
@Patternmatches a regular expression

Validation is covered in depth, including custom validators and error responses, in the Spring Boot Validation tutorial.

12. Scheduling and Async Annotations

12.1 @EnableScheduling and @Scheduled

Run a method automatically on a schedule. First switch scheduling on:

SchedulingConfig.java
@Configuration
@EnableScheduling
public class SchedulingConfig {
}
OverdueLoanJob.java
@Component
public class OverdueLoanJob {

    private final LoanService loanService;

    public OverdueLoanJob(LoanService loanService) {
        this.loanService = loanService;
    }

    // Every weekday at 08:00
    @Scheduled(cron = "0 0 8 * * MON-FRI")
    public void sendOverdueReminders() {
        loanService.remindOverdueMembers();
    }

    // Every 10 minutes, counted from the end of the previous run
    @Scheduled(fixedDelay = 10, timeUnit = TimeUnit.MINUTES)
    public void refreshAvailability() {
        loanService.refreshAvailability();
    }
}

Scheduled methods must return void and take no parameters.

12.2 @EnableAsync and @Async

Runs a method in a background thread, so the caller does not wait for it. This is useful for slow work such as sending emails:

AsyncConfig.java
@Configuration
@EnableAsync
public class AsyncConfig {
}
EmailService.java
@Service
public class EmailService {

    @Async
    public void sendWelcomeEmail(String address) {
        // runs in a separate thread; the caller continues immediately
    }

    @Async
    public CompletableFuture<Integer> sendNewsletter(List<String> addresses) {
        int sent = 0;
        // ... send each email and count it
        return CompletableFuture.completedFuture(sent);
    }
}

Return CompletableFuture when the caller needs a result later. A void method is “fire and forget”.

13. Caching Annotations

Caching stores the result of a slow method so the next call with the same arguments returns instantly. Enable it once:

CacheConfig.java
@Configuration
@EnableCaching
public class CacheConfig {
}
BookCatalogService.java
@Service
public class BookCatalogService {

    @Cacheable("books")
    public Book findByIsbn(String isbn) {
        // slow call to a database or remote API; runs only on a cache miss
        return loadFromCatalog(isbn);
    }

    @CachePut(value = "books", key = "#book.isbn")
    public Book update(Book book) {
        return saveToCatalog(book); // refreshes the cached copy
    }

    @CacheEvict(value = "books", key = "#isbn")
    public void delete(String isbn) {
        removeFromCatalog(isbn); // removes the cached copy
    }
}
AnnotationWhat it does
@CacheableReturns the cached value if present; otherwise runs the method and caches the result
@CachePutAlways runs the method and updates the cache with the result
@CacheEvictRemoves one entry, or all entries with allEntries = true

Without extra setup, Spring Boot uses a simple in-memory cache. Add a library such as Caffeine or Redis for size limits, expiry times or a shared cache.

14. Bean Lifecycle and Event Annotations

14.1 @PostConstruct and @PreDestroy

Run code right after a bean is ready, or just before the application shuts down:

CatalogLoader.java
@Component
public class CatalogLoader {

    private final Map<String, String> genres = new HashMap<>();

    @PostConstruct
    public void loadGenres() {
        genres.put("FIC", "Fiction");
        genres.put("SCI", "Science");
        // dependencies are already injected here
    }

    @PreDestroy
    public void cleanUp() {
        genres.clear();
        // close files, connections or thread pools here
    }
}

These annotations come from the jakarta.annotation package.

14.2 @EventListener

Lets beans react to events without knowing who published them. This keeps features loosely connected:

BookReturnedEvent.java
public record BookReturnedEvent(Long bookId, Long memberId) {
}
ReturnService.java
@Service
public class ReturnService {

    private final ApplicationEventPublisher events;

    public ReturnService(ApplicationEventPublisher events) {
        this.events = events;
    }

    public void returnBook(Long bookId, Long memberId) {
        // ... update the loan
        events.publishEvent(new BookReturnedEvent(bookId, memberId));
    }
}
WaitingListNotifier.java
@Component
public class WaitingListNotifier {

    @EventListener
    public void onBookReturned(BookReturnedEvent event) {
        // tell the next member on the waiting list that the book is available
    }

    @EventListener(ApplicationReadyEvent.class)
    public void onStartup() {
        // runs once the application is fully started
    }
}

15. Testing Annotations

Spring Boot offers a full-application test annotation plus lighter slice annotations that load only one layer, so tests start faster.

AnnotationLoadsUse it to test
@SpringBootTestThe whole applicationEnd-to-end flows across layers
@WebMvcTestControllers, JSON conversion, validationURLs, status codes and response bodies
@DataJpaTestJPA entities and repositoriesQueries and mappings (each test is rolled back)
@MockitoBeanReplaces one bean with a Mockito mockIsolating the code under test

15.1 @SpringBootTest

LoanServiceIntegrationTest.java
@SpringBootTest
class LoanServiceIntegrationTest {

    @Autowired
    private LoanService loanService;

    @Test
    void borrowingABookCreatesALoan() {
        Loan loan = loanService.borrow(1L, 100L);

        assertThat(loan.getId()).isNotNull();
    }
}

15.2 @WebMvcTest with @MockitoBean

BookControllerTest.java
@WebMvcTest(BookController.class)
class BookControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockitoBean
    private BookService bookService;

    @Test
    void returnsBookAsJson() throws Exception {
        when(bookService.findById(7L)).thenReturn(new Book(7L, "Clean Code", "9780132350884"));

        mockMvc.perform(get("/api/books/7"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.title").value("Clean Code"));
    }
}

@MockitoBean replaced @MockBean in Spring Boot 3.4. If you are on an older version, use @MockBean the same way.

15.3 @DataJpaTest

BookRepositoryTest.java
@DataJpaTest
class BookRepositoryTest {

    @Autowired
    private BookRepository bookRepository;

    @Test
    void findsBookByIsbn() {
        bookRepository.save(new Book(null, "Refactoring", "9780134757599"));

        assertThat(bookRepository.findByIsbn("9780134757599")).isPresent();
    }
}

16. Quick Reference Table

Keep this table handy as a cheat sheet:

CategoryAnnotations
Core@SpringBootApplication, @EnableAutoConfiguration, @ComponentScan
Stereotypes@Component, @Service, @Repository, @Controller, @RestController
Dependency injection@Autowired, @Qualifier, @Primary, @Lazy
Configuration@Configuration, @Bean, @Value, @ConfigurationProperties
Conditions@Profile, @ConditionalOnProperty, @ConditionalOnMissingBean
Web@RequestMapping, @GetMapping, @PostMapping, @PathVariable, @RequestParam, @RequestBody, @ResponseStatus
Errors@RestControllerAdvice, @ExceptionHandler
JPA@Entity, @Table, @Id, @GeneratedValue, @Column, @ManyToOne, @OneToMany
Transactions and AOP@Transactional, @Aspect, @Around
Validation@Valid, @NotNull, @NotBlank, @Size, @Email
Scheduling and async@EnableScheduling, @Scheduled, @EnableAsync, @Async
Caching@EnableCaching, @Cacheable, @CachePut, @CacheEvict
Lifecycle and events@PostConstruct, @PreDestroy, @EventListener
Testing@SpringBootTest, @WebMvcTest, @DataJpaTest, @MockitoBean