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:
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
| Annotation | What it does |
|---|---|
@SpringBootConfiguration | Marks the class as a source of bean definitions (a special form of @Configuration) |
@EnableAutoConfiguration | Lets Spring Boot configure beans automatically based on your dependencies |
@ComponentScan | Finds 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-jpaand a database driver, it creates aDataSourceand anEntityManagerFactory. - 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:
@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.
| Annotation | Layer | Typical use |
|---|---|---|
@Component | Any | General helper classes |
@Service | Business | Business rules and use cases |
@Repository | Data access | Reading and writing data |
@Controller | Web (pages) | Returning HTML views |
@RestController | Web (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:
@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:
@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:
@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:
@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:
@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:
@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;
}
}
| Style | Example | Recommended? |
|---|---|---|
| Constructor | Dependencies passed into the constructor | Yes: fields can be final and the class is easy to test |
| Setter | @Autowired on a setter method | Only for optional dependencies |
| Field | @Autowired directly on a field | No: 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:
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:
@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:
@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:
@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:
library.name=City Library
library.max-loans=5
@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:
library.loan.max-books=5
library.loan.duration-days=14
library.loan.late-fee-per-day=0.50
@ConfigurationProperties(prefix = "library.loan")
public record LoanProperties(int maxBooks, int durationDays, BigDecimal lateFeePerDay) {
}
@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:
@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:
@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:
@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);
}
}
| Annotation | Purpose | Example request |
|---|---|---|
@RequestMapping | Common URL prefix for the whole class | /api/books/... |
@GetMapping | Read data | GET /api/books/7 |
@PostMapping | Create data | POST /api/books |
@PutMapping | Replace or update data | PUT /api/books/7 |
@PatchMapping | Partially update data | PATCH /api/books/7 |
@DeleteMapping | Delete data | DELETE /api/books/7 |
@PathVariable | Reads a value from the URL path | 7 in /api/books/7 |
@RequestParam | Reads a query parameter | ?title=java&page=2 |
@RequestBody | Converts the JSON body into a Java object | {"title": "..."} |
@ResponseStatus | Sets the HTTP status of a successful response | 201 Created |
7.1 @RequestHeader
Reads an HTTP header, such as a language or a client ID:
@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:
@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:
@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:
public class BookNotFoundException extends RuntimeException {
public BookNotFoundException(Long id) {
super("Book " + id + " was not found");
}
}
@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:
{
"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:
@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
}
@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
}
| Annotation | Meaning |
|---|---|
@Entity | This class is stored in the database |
@Table | Sets the table name (defaults to the class name) |
@Id | The primary key field |
@GeneratedValue | The database generates the key value |
@Column | Column details such as name, length, nullable and unique |
@ManyToOne / @OneToMany | Relationships between entities (many books, one author) |
@JoinColumn | The 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:
@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:
@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:
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
) {
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Book create(@Valid @RequestBody BookRequest request) {
return bookService.create(request);
}
| Annotation | Checks that the value... |
|---|---|
@NotNull | is not null |
@NotBlank | is text with at least one non-space character |
@Size | has a length or size within a range |
@Min / @Max | is a number within limits |
@Positive | is greater than zero |
@Email | looks like an email address |
@Pattern | matches 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:
@Configuration
@EnableScheduling
public class SchedulingConfig {
}
@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:
@Configuration
@EnableAsync
public class AsyncConfig {
}
@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:
@Configuration
@EnableCaching
public class CacheConfig {
}
@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
}
}
| Annotation | What it does |
|---|---|
@Cacheable | Returns the cached value if present; otherwise runs the method and caches the result |
@CachePut | Always runs the method and updates the cache with the result |
@CacheEvict | Removes 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:
@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:
public record BookReturnedEvent(Long bookId, Long memberId) {
}
@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));
}
}
@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.
| Annotation | Loads | Use it to test |
|---|---|---|
@SpringBootTest | The whole application | End-to-end flows across layers |
@WebMvcTest | Controllers, JSON conversion, validation | URLs, status codes and response bodies |
@DataJpaTest | JPA entities and repositories | Queries and mappings (each test is rolled back) |
@MockitoBean | Replaces one bean with a Mockito mock | Isolating the code under test |
15.1 @SpringBootTest
@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
@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
@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:
| Category | Annotations |
|---|---|
| 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 |
17. Frequently Asked Questions (FAQ)
Q1. What is the difference between @Component and @Bean?
@Component | @Bean | |
|---|---|---|
| Placed on | A class | A method inside a @Configuration class |
| Who creates the object | Spring, using the constructor | Your method, with new or a builder |
| Works for library classes | No, you cannot edit their source | Yes |
Use @Component (or @Service, @Repository) for your own classes. Use @Bean when you need to create and configure an object from a library, or when creation needs custom logic.
Q2. What happens if two beans of the same type exist and I do not use @Qualifier?
Spring cannot decide which one to inject, so the application fails to start with a NoUniqueBeanDefinitionException, saying it expected one matching bean but found two.
Fix it in one of three ways:
- Mark one bean as the default with
@Primary. - Choose a bean at the injection point with
@Qualifier("beanName"). - Inject all of them as a
ListorMap, when you really need every implementation.
public NotificationService(List<Notifier> notifiers) {
this.notifiers = notifiers; // every Notifier bean, e.g. email and SMS
}
Q3. Do I need to write @Autowired on a constructor?
Not if the class has only one constructor. Since Spring 4.3, a single constructor is used for injection automatically.
You only need @Autowired when a class has several constructors, to tell Spring which one to use. With Lombok, @RequiredArgsConstructor generates that single constructor for all final fields, so the class needs no constructor code at all.
Q4. If a bean has @Primary and the injection point has @Qualifier, which one wins?
@Qualifier wins. @Primary only sets the default for injection points that do not say what they want. A @Qualifier is an explicit request, so Spring always honors it.
// EmailNotifier is @Primary
public OrderService(Notifier notifier) { } // gets EmailNotifier
public AlertService(@Qualifier("smsNotifier") Notifier notifier) { } // gets SmsNotifier
Q5. Why do I get NoSuchBeanDefinitionException for a class that has @Service?
The class exists, but Spring never registered it. The usual causes:
- It is outside the scanned packages. Component scanning starts at the package of your
@SpringBootApplicationclass. A class incom.example.sharedis not found if the main class is incom.example.app. - It is excluded by a condition, such as
@Profile("prod")while thedevprofile is active. - The annotation is on the interface instead of the implementing class.
- You are inside a slice test like
@WebMvcTest, which does not load services. Provide them with@MockitoBean.
Q6. When should I use @Value and when @ConfigurationProperties?
@Valueis fine for one or two unrelated settings.@ConfigurationPropertiesis better for a group of related settings. It is type-safe, supports lists and nested objects, can be validated with@Validated, and IDEs can auto-complete the property names.
A good rule: once you have three or more properties with the same prefix, move them into a @ConfigurationProperties class.
Q7. What is the default scope of a Spring bean, and is it thread-safe?
The default scope is singleton: Spring creates one instance and shares it everywhere, across all requests and threads.
Spring does not make it thread-safe for you. Singletons are safe when they hold no changing state, which is true for most services. Avoid fields that store per-request data:
@Service
public class CartService {
private List<Item> items = new ArrayList<>(); // WRONG: shared by every user
public void add(Item item) {
items.add(item);
}
}
Keep per-request data in method parameters or local variables, or store it in a database or the session. If you truly need a new instance every time, use @Scope("prototype").
Q8. What is the difference between @PathVariable and @RequestParam?
@PathVariable | @RequestParam | |
|---|---|---|
| Reads from | The URL path | The query string |
| Example URL | /books/42 | /books?author=orwell |
| Typical use | Identifying one specific resource | Filtering, sorting and paging |
| Optional? | Rarely | Often, with defaultValue |
Q9. How do I make a request parameter optional?
There are three ways, depending on what you want when the parameter is missing:
// 1. Use a default value
@GetMapping("/books")
public List<Book> list(@RequestParam(defaultValue = "0") int page) { ... }
// 2. Allow null
@GetMapping("/books/search")
public List<Book> search(@RequestParam(required = false) String author) { ... }
// 3. Use Optional
@GetMapping("/books/filter")
public List<Book> filter(@RequestParam Optional<String> genre) { ... }
Without one of these, a missing parameter returns HTTP 400 Bad Request. Note that required = false does not work with primitive types like int, because they cannot be null. Use Integer or a defaultValue instead.
Q10. Why does my @Transactional method not roll back?
Check these common causes:
- Self-invocation. A method calling another
@Transactionalmethod in the same class bypasses the Spring proxy, so no transaction starts. - The method is
private. Proxies cannot intercept private methods. - A checked exception was thrown. By default, only unchecked exceptions (
RuntimeExceptionandError) trigger a rollback. - The exception was caught inside the method and never reached the proxy.
- The class is not a Spring bean, for example it was created with
new.
Q11. How do I roll back a transaction on a checked exception?
Use the rollbackFor attribute:
@Transactional(rollbackFor = PaymentFailedException.class)
public void checkout(Order order) throws PaymentFailedException {
orderRepository.save(order);
paymentGateway.charge(order); // throws the checked PaymentFailedException
}
To roll back on every exception, use rollbackFor = Exception.class. The opposite attribute, noRollbackFor, keeps the changes even when a specific exception is thrown.
Q12. Where should I put @Transactional: controller, service or repository?
Put it on the service layer. A service method represents one business operation, which is exactly what should succeed or fail as a whole.
- Controller: too high. It mixes web concerns with database work and keeps transactions open longer than needed.
- Repository: too low. Each call gets its own transaction, so a service that calls two repositories cannot save both changes together.
Q13. Why does my @Async method still run in the same thread?
The usual reasons are:
@EnableAsyncis missing from your configuration.- The method is called from the same class, which bypasses the proxy, just like with
@Transactional. - The method is
private.
To confirm, log Thread.currentThread().getName() inside the method. An async call shows a name such as task-1 instead of the web request thread.
Q14. How do I write a cron expression for @Scheduled?
Spring cron expressions have six fields, starting with seconds:
second minute hour day-of-month month day-of-week
| Expression | Runs |
|---|---|
0 0 * * * * | At the start of every hour |
0 */15 * * * * | Every 15 minutes |
0 30 9 * * MON-FRI | At 09:30 on weekdays |
0 0 0 1 * * | At midnight on the first day of each month |
Add zone = "Asia/Kolkata" (or your time zone) so the job does not depend on the server’s clock settings. Spring also accepts shortcuts like @daily and @hourly.
Q15. What is the difference between fixedRate and fixedDelay?
fixedRatestarts the task at a regular interval measured from the start of the previous run. If a run takes longer than the interval, the next one starts as soon as it finishes.fixedDelaywaits a fixed time after the previous run finishes, so runs never overlap and always have a gap between them.
Choose fixedRate for steady sampling, such as collecting metrics every minute. Choose fixedDelay for jobs whose duration varies, such as processing a queue. Add initialDelay to wait before the first run.
Q16. Do I need @Repository on Spring Data JPA interfaces?
No. Interfaces that extend JpaRepository or CrudRepository are detected and registered by Spring Data automatically, and exception translation is applied for you.
public interface BookRepository extends JpaRepository<Book, Long> {
Optional<Book> findByIsbn(String isbn);
}
Adding @Repository does no harm and some teams keep it for readability, but it is not required. It is needed on your own repository classes, such as ones built with JdbcClient.
Q17. How do I turn off a specific auto-configuration?
Use the exclude attribute of @SpringBootApplication. For example, to start an application that has JPA on the classpath without connecting to a database:
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class LibraryApplication {
// ...
}
You can also do it in application.properties, which is handy when it applies only to one profile:
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
To see which auto-configurations were applied and why, start the app with --debug and read the conditions report.
Q18. Can I create my own annotation that combines other annotations?
Yes. These are called composed annotations, and Spring itself uses them: @RestController is just @Controller plus @ResponseBody. You can bundle annotations you always use together:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1")
@CrossOrigin(origins = "https://library-ui.example.com")
public @interface ApiV1Controller {
}
@ApiV1Controller
public class MemberController {
// has all three annotations, served under /api/v1
}
This keeps settings consistent across many classes and lets you change them in one place.
Q19. Why is my @SpringBootTest test suite slow?
@SpringBootTest starts the entire application, which takes time. Spring caches the context and reuses it between test classes, but only when their configuration is identical. Each difference creates a new context:
- A different set of
@MockitoBeanfields in each test class. - Different
@ActiveProfilesorproperties. - Using
@DirtiesContext, which throws the cached context away.
To speed things up, use slice tests (@WebMvcTest, @DataJpaTest) where possible, keep integration test configuration consistent, perhaps through a shared base class, and avoid @DirtiesContext unless you really need it.
Q20. @MockBean is deprecated. What should I use instead?
Use @MockitoBean, and @MockitoSpyBean instead of @SpyBean. They come from Spring Framework 6.2, which ships with Spring Boot 3.4, and work the same way:
import org.springframework.test.context.bean.override.mockito.MockitoBean;
@WebMvcTest(BookController.class)
class BookControllerTest {
@MockitoBean
private BookService bookService;
}
In most tests, the change is just the import and the annotation name. @MockBean was deprecated in Spring Boot 3.4 and removed in Spring Boot 4, so switch before upgrading.