Spring Boot Interview Questions and Answers
These questions cover Spring Bootβs core features and advanced patterns β whatβs tested for Java backend engineer, microservices developer, and enterprise application architect roles.
Core Spring Boot
Q1. What is Spring Boot and how does it differ from Spring Framework?
Spring Framework is a comprehensive dependency injection and programming model β but it requires significant XML or Java configuration to set up a working application.
Spring Boot is an opinionated layer on top of Spring Framework that:
- Auto-configures Spring components based on whatβs on the classpath
- Provides embedded servers (Tomcat, Jetty, Undertow) β no WAR deployment needed
- Offers starter POMs that bundle compatible dependency sets
- Adds production-ready features: Actuator, metrics, health checks
// Spring Boot: zero XML, start in seconds@SpringBootApplication // = @Configuration + @EnableAutoConfiguration + @ComponentScanpublic class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); }}// Run with: java -jar order-service.jar// Embedded Tomcat starts on port 8080 automaticallyQ2. How does Spring Boot auto-configuration work?
Auto-configuration uses @Conditional annotations to configure beans only when certain conditions are met:
// Spring Boot's auto-configuration for DataSource (simplified)@Configuration@ConditionalOnClass(DataSource.class) // Only if DataSource is on classpath@ConditionalOnMissingBean(DataSource.class) // Only if no DataSource bean already exists@EnableConfigurationProperties(DataSourceProperties.class)public class DataSourceAutoConfiguration {
@Bean @ConditionalOnProperty(name = "spring.datasource.url") public DataSource dataSource(DataSourceProperties props) { return DataSourceBuilder.create() .url(props.getUrl()) .username(props.getUsername()) .password(props.getPassword()) .build(); }}All auto-configurations are listed in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. When you add spring-boot-starter-data-jpa, Spring Boot auto-configures DataSource, EntityManagerFactory, and TransactionManager automatically.
Debug auto-configuration: spring.autoconfigure.report=true or run with --debug flag.
Q3. What is the Spring IoC container and dependency injection?
The IoC (Inversion of Control) container manages bean creation, lifecycle, and dependency wiring. Instead of creating dependencies yourself, you declare them and Spring injects them:
// Service layer@Servicepublic class OrderService {
private final OrderRepository orderRepo; private final PaymentClient paymentClient; private final EmailService emailService;
// Constructor injection β preferred (makes dependencies explicit, testable) public OrderService(OrderRepository orderRepo, PaymentClient paymentClient, EmailService emailService) { this.orderRepo = orderRepo; this.paymentClient = paymentClient; this.emailService = emailService; }
@Transactional public Order placeOrder(OrderRequest request) { Order order = orderRepo.save(new Order(request)); paymentClient.charge(order.getTotal(), request.getPaymentMethod()); emailService.sendConfirmation(order); return order; }}
// Spring creates and wires this automatically@Repositorypublic interface OrderRepository extends JpaRepository<Order, Long> { List<Order> findByCustomerIdAndStatus(Long customerId, OrderStatus status); Optional<Order> findByOrderNumber(String orderNumber);}Injection types: Constructor injection (best), setter injection, field injection (@Autowired on field β avoid, untestable).
REST APIs
Q4. How do you build a REST API with Spring Boot?
@RestController@RequestMapping("/api/v1/orders")@RequiredArgsConstructorpublic class OrderController {
private final OrderService orderService;
@GetMapping public ResponseEntity<Page<OrderDto>> listOrders( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size, @RequestParam(required = false) OrderStatus status) {
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending()); return ResponseEntity.ok(orderService.findOrders(status, pageable)); }
@GetMapping("/{id}") public ResponseEntity<OrderDto> getOrder(@PathVariable Long id) { return orderService.findById(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); }
@PostMapping public ResponseEntity<OrderDto> createOrder( @Valid @RequestBody CreateOrderRequest request) { OrderDto created = orderService.create(request); URI location = URI.create("/api/v1/orders/" + created.getId()); return ResponseEntity.created(location).body(created); }
@PatchMapping("/{id}/status") public ResponseEntity<OrderDto> updateStatus( @PathVariable Long id, @RequestBody UpdateStatusRequest request) { return ResponseEntity.ok(orderService.updateStatus(id, request.getStatus())); }
@DeleteMapping("/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) public void deleteOrder(@PathVariable Long id) { orderService.delete(id); }}Q5. How do you handle exceptions in a Spring Boot REST API?
// Global exception handler@RestControllerAdvicepublic class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(new ErrorResponse("NOT_FOUND", ex.getMessage())); }
@ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) { Map<String, String> errors = ex.getBindingResult().getFieldErrors().stream() .collect(Collectors.toMap( FieldError::getField, fe -> fe.getDefaultMessage() != null ? fe.getDefaultMessage() : "invalid" )); return ResponseEntity.badRequest() .body(new ErrorResponse("VALIDATION_FAILED", "Validation failed", errors)); }
@ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleUnexpected(Exception ex) { log.error("Unexpected error", ex); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new ErrorResponse("INTERNAL_ERROR", "An unexpected error occurred")); }}
// Custom exceptionpublic class ResourceNotFoundException extends RuntimeException { public ResourceNotFoundException(String resourceName, Long id) { super(resourceName + " not found with id: " + id); }}Data Access
Q6. How does Spring Data JPA work and what is the JPA entity lifecycle?
@Entity@Table(name = "orders")@EntityListeners(AuditingEntityListener.class)public class Order {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
@Column(nullable = false, unique = true) private String orderNumber;
@ManyToOne(fetch = FetchType.LAZY) // LAZY avoids N+1 @JoinColumn(name = "customer_id") private Customer customer;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true) private List<OrderItem> items = new ArrayList<>();
@Enumerated(EnumType.STRING) private OrderStatus status;
@CreatedDate private Instant createdAt;
@LastModifiedDate private Instant updatedAt;}
// Repository β Spring Data generates implementations automaticallypublic interface OrderRepository extends JpaRepository<Order, Long> {
// Method name parsing List<Order> findByStatusAndCreatedAtAfter(OrderStatus status, Instant since);
// JPQL @Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.customer.id = :customerId") List<Order> findWithItemsByCustomerId(@Param("customerId") Long customerId);
// Native SQL @Query(value = "SELECT * FROM orders WHERE status = ?1 LIMIT ?2", nativeQuery = true) List<Order> findTopByStatus(String status, int limit);
// Projections β select fewer columns @Query("SELECT o.orderNumber as orderNumber, o.status as status FROM Order o") List<OrderSummary> findAllSummaries();}JPA entity lifecycle: Transient (new, not tracked) β Managed (tracked by EntityManager) β Detached (after transaction ends) β Removed (marked for deletion).
Q7. How do you avoid the N+1 query problem in Spring Data JPA?
N+1 happens when loading N entities triggers N additional queries for their lazy-loaded associations:
// Problem: fetching 100 orders β 100 extra queries for customerList<Order> orders = orderRepo.findAll();orders.forEach(o -> System.out.println(o.getCustomer().getName())); // N+1!
// Solution 1: JOIN FETCH in JPQL@Query("SELECT DISTINCT o FROM Order o JOIN FETCH o.customer JOIN FETCH o.items")List<Order> findAllWithDetails();
// Solution 2: Entity graph@EntityGraph(attributePaths = {"customer", "items"})List<Order> findByStatus(OrderStatus status);
// Solution 3: Batch fetching (Hibernate property)# application.propertiesspring.jpa.properties.hibernate.default_batch_fetch_size=20
// Solution 4: Use Blaze-Persistence or DTO projections// to avoid fetching entities at all when you only need specific fieldsSecurity
Q8. How do you implement JWT authentication in Spring Boot?
@Configuration@EnableWebSecuritypublic class SecurityConfig {
private final JwtAuthFilter jwtAuthFilter;
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .csrf(csrf -> csrf.disable()) // Disabled for stateless APIs .sessionManagement(sm -> sm.sessionCreationPolicy(STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**").permitAll() .requestMatchers("/actuator/health").permitAll() .requestMatchers(HttpMethod.GET, "/api/v1/products/**").permitAll() .requestMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) .build(); }}
@Component@RequiredArgsConstructorpublic class JwtAuthFilter extends OncePerRequestFilter {
private final JwtService jwtService; private final UserDetailsService userDetailsService;
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
final String authHeader = request.getHeader("Authorization"); if (authHeader == null || !authHeader.startsWith("Bearer ")) { chain.doFilter(request, response); return; }
String jwt = authHeader.substring(7); String username = jwtService.extractUsername(jwt);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails user = userDetailsService.loadUserByUsername(username); if (jwtService.isTokenValid(jwt, user)) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authToken); } } chain.doFilter(request, response); }}Microservices & Configuration
Q9. What is Spring Boot Actuator and what does it provide?
Actuator exposes production-ready HTTP endpoints for monitoring and management:
management: endpoints: web: exposure: include: health, info, metrics, prometheus, loggers, env base-path: /actuator endpoint: health: show-details: when_authorized metrics: export: prometheus: enabled: trueKey endpoints:
/actuator/healthβ application health (DB, disk, Kafka, custom checks)/actuator/metricsβ JVM, HTTP request, and custom metrics/actuator/prometheusβ Prometheus-format metrics scraping/actuator/infoβ app version, build info/actuator/loggersβ view and change log levels at runtime/actuator/envβ current configuration (careful: may expose sensitive values)/actuator/threaddumpβ thread dump for debugging deadlocks
// Custom health indicator@Componentpublic class ExternalApiHealthIndicator implements HealthIndicator {
private final ExternalApiClient client;
@Override public Health health() { try { client.ping(); return Health.up().withDetail("api", "available").build(); } catch (Exception ex) { return Health.down().withDetail("api", "unavailable").withException(ex).build(); } }}Q10. How do you externalize configuration in Spring Boot?
app: payment: provider: stripe timeout-ms: 5000 retry: max-attempts: 3 backoff-ms: 1000 feature-flags: new-checkout: false
// Typed configuration class@ConfigurationProperties(prefix = "app.payment")@Validatedpublic record PaymentConfig( @NotBlank String provider, @Positive int timeoutMs, RetryConfig retry) { public record RetryConfig(@Positive int maxAttempts, @Positive int backoffMs) {}}
// Enable in main app or @Configuration class@SpringBootApplication@EnableConfigurationProperties(PaymentConfig.class)public class App { ... }
// Use it@Service@RequiredArgsConstructorpublic class PaymentService { private final PaymentConfig config;
public void charge(BigDecimal amount) { // config.timeoutMs(), config.retry().maxAttempts() }}Configuration priority (highest to lowest):
- Command-line arguments (
--server.port=9090) - Environment variables (
SERVER_PORT=9090) application-{profile}.ymlapplication.yml- Default values in
@ConfigurationProperties
Testing
Q11. How do you write integration tests in Spring Boot?
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)@Testcontainersclass OrderControllerIntegrationTest {
@Container static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16") .withDatabaseName("testdb");
@DynamicPropertySource static void configureProperties(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", postgres::getJdbcUrl); registry.add("spring.datasource.username", postgres::getUsername); registry.add("spring.datasource.password", postgres::getPassword); }
@Autowired private TestRestTemplate restTemplate;
@Autowired private OrderRepository orderRepository;
@Test void createOrder_returnsCreatedWithLocation() { var request = new CreateOrderRequest("PROD-123", 2, "customer@example.com");
var response = restTemplate.postForEntity("/api/v1/orders", request, OrderDto.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); assertThat(response.getHeaders().getLocation()).isNotNull(); assertThat(response.getBody().getStatus()).isEqualTo("PENDING"); assertThat(orderRepository.count()).isEqualTo(1); }}
// Slice test β only web layer@WebMvcTest(OrderController.class)class OrderControllerTest {
@Autowired private MockMvc mockMvc;
@MockBean private OrderService orderService;
@Test void getOrder_notFound_returns404() throws Exception { when(orderService.findById(99L)).thenReturn(Optional.empty());
mockMvc.perform(get("/api/v1/orders/99")) .andExpect(status().isNotFound()); }}Q12. What are the key features introduced in Spring Boot 3.x?
Spring Boot 3.0 (2022):
- Requires Java 17 minimum (Java 21 recommended)
- Jakarta EE 10 namespace migration (
javax.*βjakarta.*) - Native image support via GraalVM AOT compilation
- Observability improvements: Micrometer Tracing, auto-configured OpenTelemetry
Spring Boot 3.2 (2023):
- Virtual threads (Project Loom) support with
spring.threads.virtual.enabled=true - Initial support for Spring Framework 6.1 features
Spring Boot 3.3 / 3.4 (2024β2025):
- CDS (Class Data Sharing) for faster startup
- Docker Compose integration improvements
- RestClient (new fluent HTTP client, replaces RestTemplate)
@Fallbackfor bean fallbacks- Improved GraalVM native hints
// RestClient (Spring Boot 3.2+ β replaces RestTemplate)@BeanRestClient orderClient(RestClient.Builder builder) { return builder.baseUrl("https://api.example.com").build();}
// UsageOrderDto order = restClient.get() .uri("/orders/{id}", orderId) .retrieve() .body(OrderDto.class);
// Virtual threads (Spring Boot 3.2+)// application.properties:# spring.threads.virtual.enabled=true# All web requests now handled by virtual threads automatically