Curated questions covering auto-configuration, dependency injection, REST APIs, JPA, security, actuator, and microservices.
Spring Boot is an opinionated extension of Spring Framework that simplifies application setup. Spring Framework requires manual configuration of beans, data sources, and servers. Spring Boot provides auto-configuration, embedded servers (Tomcat/Jetty), starter dependencies, and production-ready features out of the box.
Auto-configuration automatically configures Spring beans based on the dependencies present on the classpath. For example, if spring-boot-starter-data-jpa is on the classpath and a DataSource bean is defined, Spring Boot auto-configures a JPA EntityManagerFactory. Use @EnableAutoConfiguration or @SpringBootApplication to enable it.
@SpringBootApplication is a convenience annotation combining three annotations.
@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
Dependency Injection (DI) is a design pattern where objects receive their dependencies from an external source. Spring implements DI via the IoC container. Three types: constructor injection (recommended), setter injection, and field injection (@Autowired).
@Service
public class OrderService {
private final PaymentService paymentService;
// Constructor injection (preferred)
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
@Controller returns view names resolved by a ViewResolver (for MVC/Thymeleaf). @RestController is @Controller + @ResponseBody - it serializes return values directly to JSON/XML response body. Use @RestController for REST APIs.
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id); // auto-serialized to JSON
}
}
Starters are curated dependency descriptors that bundle related libraries. Instead of manually adding multiple dependencies, you add one starter and Spring Boot manages compatible versions.
Spring Data JPA reduces boilerplate data access code by providing repository interfaces. Extend JpaRepository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByEmail(String email);
List<User> findByAgeGreaterThan(int age);
@Query("SELECT u FROM User u WHERE u.name LIKE %:name%")
List<User> searchByName(@Param("name") String name);
}
Actuator provides production-ready monitoring and management endpoints. Common endpoints: /actuator/health (app health), /actuator/metrics (JVM, HTTP metrics), /actuator/info (app info), /actuator/env (environment properties), /actuator/beans (all beans). Secure sensitive endpoints in production.
Both configure Spring Boot applications. application.yml uses YAML format (hierarchical, less repetition). application.properties uses flat key=value pairs. Both support profiles (application-dev.yml, application-prod.yml). YAML is preferred for complex nested configurations.
# application.properties
server.port=8080
spring.datasource.url=jdbc:mysql://localhost/mydb
# application.yml equivalent
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost/mydb
Profiles allow different configurations for different environments (dev, test, prod). Activate with spring.profiles.active=prod. Use @Profile("dev") on beans to conditionally register them.
@Configuration
@Profile("dev")
public class DevConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().setType(H2).build();
}
}
@Configuration
public class AppConfig {
@Bean
public ObjectMapper objectMapper() { // third-party class
return new ObjectMapper().findAndRegisterModules();
}
}
Spring Security provides authentication and authorization. For REST APIs, common approaches: HTTP Basic Auth, JWT tokens, or OAuth2. Configure via SecurityFilterChain bean.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated())
.sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
.build();
}
}
@Transactional declaratively manages transactions. Spring creates a proxy that begins a transaction before the method and commits or rolls back after. Auto-rollback on RuntimeException by default.
@Service
public class OrderService {
@Transactional
public void placeOrder(Order order) {
orderRepo.save(order);
inventoryService.deduct(order.getItems());
// auto-rollback if any exception is thrown
}
}
The N+1 problem occurs when fetching N parent entities triggers N additional queries to load their related entities. Fix with JOIN FETCH in JPQL, @EntityGraph, or @BatchSize.
// Problem: 1 query for orders + N queries for each customer
List<Order> orders = orderRepo.findAll();
// Fix: single JOIN FETCH query
@Query("SELECT o FROM Order o JOIN FETCH o.customer")
List<Order> findAllWithCustomer();
@ExceptionHandler handles exceptions in a specific controller. @ControllerAdvice (or @RestControllerAdvice) handles exceptions globally across all controllers.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFound(ResourceNotFoundException ex) {
return new ErrorResponse(404, ex.getMessage());
}
}
DevTools improves development experience with automatic application restart on classpath changes, LiveReload for browser refresh, and relaxed property defaults (disabled caching). Add spring-boot-devtools as a dependency - it is automatically excluded from production builds.
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) { ... }
@GetMapping("/users")
public Page<User> listUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) { ... }
@PostMapping("/users")
public User createUser(@RequestBody UserDto dto) {
return userService.create(dto); // @ResponseBody implicit in @RestController
}
@Entity
public class Order {
@ManyToOne
@JoinColumn(name = "customer_id")
private Customer customer;
}
@Entity
public class Customer {
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL)
private List<Order> orders;
}
Spring Boot provides @SpringBootTest for full integration tests, @WebMvcTest for controller layer tests, @DataJpaTest for repository tests (in-memory DB), and @MockBean to replace beans with Mockito mocks.
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired MockMvc mockMvc;
@MockBean UserService userService;
@Test
void getUser_returnsUser() throws Exception {
given(userService.findById(1L)).willReturn(new User(1L, "Alice"));
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Alice"));
}
}
@Component
public class Tasks {
@Scheduled(cron = "0 0 8 * * MON-FRI")
public void dailyReport() { ... }
@Async
public CompletableFuture<String> processAsync() {
return CompletableFuture.completedFuture("done");
}
}
@ConfigurationProperties(prefix = "app")
@Component
public class AppConfig {
private String name;
private int timeout;
private List<String> allowedOrigins;
// getters/setters
}
// Field injection (avoid)
@Autowired
private UserService userService;
// Constructor injection (preferred)
private final UserService userService;
public MyService(UserService userService) {
this.userService = userService;
}
@Bean @Primary
public DataSource primaryDataSource() { ... }
@Bean
public DataSource secondaryDataSource() { ... }
// Inject specific one
@Autowired @Qualifier("secondaryDataSource")
private DataSource dataSource;
@Bean
@Scope("prototype")
public ExpensiveObject expensiveObject() {
return new ExpensiveObject();
}
@Entity
@Table(name = "users", indexes = {
@Index(name = "idx_email", columnList = "email")
})
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
}
// For WAR deployment
@SpringBootApplication
public class MyApp extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder b) {
return b.sources(MyApp.class);
}
}
@Transactional(readOnly = true)
public List<User> findAllUsers() {
return userRepository.findAll(); // no dirty checking overhead
}
@Transactional // readOnly=false by default
public User createUser(UserDto dto) { ... }
Health indicators report the health of application components. Built-in: DiskSpaceHealthIndicator, DataSourceHealthIndicator, RedisHealthIndicator. Create custom indicators by implementing HealthIndicator.
@Component
public class CustomHealthIndicator implements HealthIndicator {
@Override
public Health health() {
boolean serviceUp = checkExternalService();
return serviceUp
? Health.up().withDetail("service", "available").build()
: Health.down().withDetail("service", "unavailable").build();
}
}
@HttpExchange("/api/users")
public interface UserClient {
@GetExchange("/{id}")
User getUser(@PathVariable Long id);
@PostExchange
User createUser(@RequestBody UserDto dto);
}
@Entity
public class Student {
@ManyToMany
@JoinTable(name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id"))
private Set<Course> courses;
}
@Service
public class OrderService {
private final Counter orderCounter;
public OrderService(MeterRegistry registry) {
this.orderCounter = registry.counter("orders.created");
}
public void createOrder(Order o) {
orderCounter.increment();
orderRepo.save(o);
}
}
Problem Details (RFC 7807, Spring Boot 3) is a standardized error response format. Spring Boot 3 auto-configures ProblemDetail responses for common exceptions when spring.mvc.problemdetails.enabled=true.
// Spring Boot 3 - automatic Problem Details
@ExceptionHandler(UserNotFoundException.class)
public ProblemDetail handleNotFound(UserNotFoundException ex) {
ProblemDetail pd = ProblemDetail.forStatusAndDetail(
HttpStatus.NOT_FOUND, ex.getMessage());
pd.setTitle("User Not Found");
pd.setProperty("userId", ex.getUserId());
return pd;
}
# Build native image
./mvnw -Pnative native:compile
# Run native executable
./target/myapp # starts in ~50ms vs ~3s for JVM
@Cacheable(value = "users", key = "#id")
public User findById(Long id) { return repo.findById(id).orElseThrow(); }
@CachePut(value = "users", key = "#user.id")
public User update(User user) { return repo.save(user); }
@CacheEvict(value = "users", key = "#id")
public void delete(Long id) { repo.deleteById(id); }
Spring Boot 3.2+ supports Java 21 virtual threads (Project Loom). Virtual threads are lightweight (millions possible), managed by the JVM, and block without consuming OS threads. Enable with spring.threads.virtual.enabled=true.
# application.properties
spring.threads.virtual.enabled=true
# Or programmatically
@Bean
public TomcatProtocolHandlerCustomizer<?> virtualThreads() {
return handler -> handler.setExecutor(
Executors.newVirtualThreadPerTaskExecutor());
}
@RestController
@RequestMapping("/api/users")
class UserController {
@GetMapping("/{id}")
User getUser(@PathVariable Long id) { return service.find(id); }
}
@Service
class OrderService {
private final OrderRepository repo;
OrderService(OrderRepository repo) {
this.repo = repo;
}
}
Both files configure Spring Boot applications. application.properties uses key-value lines, while application.yml uses hierarchical YAML. YAML is cleaner for nested configuration, but properties are simple and widely used.
# application.properties
server.port=8081
spring.datasource.url=jdbc:mysql://localhost/app
# application.yml
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost/app
@Profile("dev")
@Bean
DataSource devDataSource() {
return DataSourceBuilder.create().url("jdbc:h2:mem:test").build();
}
public record UserRequest(
@NotBlank String name,
@Email String email
) {}
@PostMapping("/users")
User create(@Valid @RequestBody UserRequest request) {
return service.create(request);
}
Explore 500+ free tutorials across 20+ languages and frameworks.