Spring Boot Developer Interview Questions
Core Overview
Prepare for enterprise Java roles with questions on Spring Boot configuration, DI, Spring Web, Data JPA, Security, and Cloud integration.
Ready to test your knowledge?
Launch a focused practice session to review questions without distraction.
Describe the lifecycle of the Spring IoC Container and how beans are instantiated.
Direct Answer
The Spring IoC Container lifecycle involves loading BeanDefinitions, executing BeanFactoryPostProcessors, instantiating beans, injecting dependencies, executing BeanPostProcessors, and performing destruction on shutdown.
Detailed Explanation
The lifecycle of the Spring IoC container can be split into startup and shutdown phases:
1. Metadata Loading & Parsing: Spring reads configuration (Java Config, XML, or Annotations) and creates a registry of BeanDefinition metadata.
2. BeanFactoryPostProcessing: Classes implementing BeanFactoryPostProcessor (like configuration processors that resolve externalized properties) are executed to modify configuration metadata before beans are created.
3. Bean Instantiation: Spring instantiates beans (typically via constructor reflection).
4. Dependency Injection: Spring populates properties and resolves references between beans.
5. Aware Interfaces: Beans implementing BeanNameAware, BeanFactoryAware, etc., receive their corresponding context references.
6. BeanPostProcessing (Before Init): postProcessBeforeInitialization of any registered BeanPostProcessor is run.
7. Initialization: Custom initialization callbacks (like @PostConstruct or InitializingBean.afterPropertiesSet()) execute.
8. BeanPostProcessing (After Init): postProcessAfterInitialization runs (this is where AOP proxies are created).
9. Active State: The beans are ready for client use in the container.
10. Destruction: On container shutdown, @PreDestroy methods and DisposableBean.destroy() are called.
Code Example
import org.springframework.beans.factory.InitializingBean;
import javax.annotation.PostConstruct;
public class CustomBean implements InitializingBean {
@PostConstruct
public void postConstruct() {
System.out.println("1. PostConstruct annotation callback called");
}
@Override
public void afterPropertiesSet() {
System.out.println("2. InitializingBean interface callback called");
}
}
Common Interview Pitfalls
- Assuming BeanPostProcessors operate on individual beans before the entire bean factory metadata is loaded.
- Relying on `@PostConstruct` in classes loaded outside Spring context management.
Explain the different bean scopes in Spring and how proxyMode resolves scope mismatches.
Direct Answer
Spring supports singleton, prototype, request, session, application, and websocket scopes. Injecting a short-lived scoped bean into a singleton requires a scoped proxy to intercept dynamic access.
Detailed Explanation
Spring provides six standard bean scopes:
Scope Mismatch: If you inject a short-lived bean (e.g. request) into a long-lived bean (e.g. singleton), the singleton only receives the reference once at initialization. Subsequent HTTP requests will fetch stale data. To resolve this, Spring uses Scoped Proxies (@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)). The container injects a proxy instead of the real bean. The proxy intercepts method calls and dynamically delegates them to the active instance matching the current request/session thread.
Code Example
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserSessionToken {
private String token;
public String getToken() { return token; }
public void setToken(String token) { this.token = token; }
}
Common Interview Pitfalls
- Injecting prototype beans directly into singleton beans without using a Provider, ObjectFactory, or Scoped Proxy, expecting prototype instances to refresh.
- Expecting prototype beans to execute destruction lifecycle callbacks (Spring does not manage prototype bean destruction).
What is the difference between BeanFactory and ApplicationContext in Spring?
Direct Answer
BeanFactory provides basic configuration and lazy instantiation of beans. ApplicationContext is a subclass of BeanFactory that adds advanced features like eager loading, AOP integration, events, and i18n.
Detailed Explanation
Both BeanFactory and ApplicationContext represent the Spring container, but they serve different needs:
getBean() is called) to save memory, making it suitable for resource-constrained environments.BeanFactory and adds advanced features including:ApplicationEventPublisher).WebApplicationContext).Code Example
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ContextDemo {
public static void main(String[] args) {
// Eagerly instantiates and validates configuration
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyService service = context.getBean(MyService.class);
}
}
Common Interview Pitfalls
- Using BeanFactory manually in modern Boot enterprise projects unless under severe memory constraints.
- Assuming ApplicationContext lazy-loads all beans by default (it eagerly validates singletons).
How does Spring AOP implement proxy patterns, and what is the difference between JDK Dynamic Proxies and CGLIB?
Direct Answer
Spring AOP uses JDK Dynamic Proxies for interface-based class proxying, and CGLIB for subclassing-based concrete class proxying. Self-invocation bypasses AOP intercepts.
Detailed Explanation
Spring AOP is proxy-based. When you annotate a bean with AOP interceptors (e.g. @Transactional or @Aspect), Spring wraps the target class inside a proxy.
java.lang.reflect.Proxy) to dynamically construct a proxy class implementing the target interfaces.Self-Invocation Limitation: Because AOP depends on external proxy invocation, if a method inside a class calls another annotated method in the same class directly (e.g., this.someTransactionalMethod()), the call bypasses the proxy container wrapper, and the aspect/transaction logic will fail to trigger.
Code Example
@Service
public class OrderService {
@Transactional
public void processOrder() {
// Active transaction
}
public void checkout() {
// Self-invocation: transaction aspect bypassed
processOrder();
}
}
Common Interview Pitfalls
- Calling an internal method annotated with `@Transactional` from a non-transactional method within the same class, expecting a transaction to start.
- Making target methods or classes `final` when using CGLIB proxying (CGLIB cannot subclass final classes or override final methods).
Compare constructor injection, setter injection, and field injection in Spring.
Direct Answer
Constructor injection is preferred because it guarantees immutability, facilitates unit testing with plain mock injections, and prevents circular dependency risks.
Detailed Explanation
Spring supports three primary styles of Dependency Injection:
@Autowired private MyService service;): Simple and clean syntax. However, it hides dependencies, makes unit testing difficult (requires reflection/MockitoRunner to inject), and violates immutability rules.final), guarantees that the object is fully initialized with valid dependencies at instantiation time, and makes testing easy because dependencies can be passed via simple class constructors.Code Example
@Service
public class PaymentProcessor {
private final GatewayService gateway;
// Constructor injection: final fields guaranteed, @Autowired is optional in single constructor classes
public PaymentProcessor(GatewayService gateway) {
this.gateway = gateway;
}
}
Common Interview Pitfalls
- Using field injection and getting NullPointerExceptions in unit tests because Spring container context was not bootstrapped.
- Declaring fields injected by constructors without the `final` keyword, allowing unwanted mutability.
How does Spring resolve dependencies with `@Autowired`, and what happens under type conflict?
Direct Answer
Spring resolves `@Autowired` dependencies first by type, then by bean name. If multiple matching beans exist without qualifiers, a NoUniqueBeanDefinitionException is thrown.
Detailed Explanation
Dependency resolution for @Autowired follows this priority logic:
1. By Type: Spring searches the ApplicationContext for a bean of the requested type.
2. By Qualifier: If multiple beans of the same type exist, Spring checks for @Qualifier("name") annotations at the injection point.
3. By Name Fallback: If no explicit qualifier is provided, Spring attempts to resolve the conflict by looking for a bean whose name matches the variable name at the injection point.
4. Failure: If resolution is still ambiguous, Spring throws a NoUniqueBeanDefinitionException during application bootstrap, preventing startup.
Code Example
@Component
public class ClientService {
@Autowired
@Qualifier("smtpService") // Disambiguates between multiple EmailService beans
private EmailService emailService;
}
Common Interview Pitfalls
- Naming a bean variable arbitrarily expecting naming fallback to magically match a different configured bean name.
- Setting `@Autowired(required = false)` and failing to perform null checks before invoking methods on the bean.
Compare `@Primary` and `@Qualifier` annotations for resolving DI ambiguity.
Direct Answer
`@Primary` designates a default bean to use when multiple instances match a type. `@Qualifier` provides a specific target name to override default selection.
Detailed Explanation
Both @Primary and @Qualifier resolve injection ambiguity for multiple beans of the same type, but they are used in different context modes:
PaymentService exist, the one with @Primary is selected unless the client explicitly requests otherwise. It is a broad, declarative fallback.@Qualifier has higher precedence than @Primary.Code Example
@Configuration
public class ServiceConfig {
@Bean
@Primary // Default choice
public MessageSender smsSender() { return new SmsSender(); }
@Bean
public MessageSender emailSender() { return new EmailSender(); }
}
Common Interview Pitfalls
- Marking multiple bean definitions of the same type with `@Primary`, returning the same NoUniqueBeanDefinitionException conflict.
- Typing the qualifier string argument incorrectly (qualifiers are evaluated as string keys and are not validated at compile time).
How do `@PostConstruct` and `@PreDestroy` work in Spring, and when should you use them?
Direct Answer
`@PostConstruct` runs initialization logic once dependencies are injected. `@PreDestroy` runs cleanups before the container destroys the bean.
Detailed Explanation
@PostConstruct and @PreDestroy are lifecycle annotations that execute custom initialization and destruction logic:
@PostConstruct exactly once, *after* the constructor has completed and all dependencies are fully injected (properties populated). Use this for loading caches, warming connections, or validating properties.@PreDestroy methods before the container destroys the bean instance (typically during ApplicationContext shutdown). Use this to close threads, release resources, or release database connections.Note: These are standard annotations (jakarta.annotation) integrated directly into the Spring lifecycle.
Code Example
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component
public class CacheWarmer {
@PostConstruct
public void init() {
System.out.println("Loading reference tables into memory cache...");
}
@PreDestroy
public void cleanup() {
System.out.println("Releasing socket connections...");
}
}
Common Interview Pitfalls
- Trying to access dependency-injected beans inside the constructor instead of a `@PostConstruct` method (dependencies are null in the constructor).
- Declaring a `@PostConstruct` or `@PreDestroy` method with arguments (they must be void and take no parameters).
What annotations compose `@SpringBootApplication`, and what does each do?
Direct Answer
`@SpringBootApplication` is a meta-annotation composed of `@SpringBootConfiguration`, `@EnableAutoConfiguration`, and `@ComponentScan`.
Detailed Explanation
@SpringBootApplication is a configuration shortcut that combines three essential annotations:
1. `@SpringBootConfiguration`: A specialized form of @Configuration. It designates the class as a source of bean definitions for the application context.
2. `@EnableAutoConfiguration`: Tells Spring Boot to automatically configure beans based on dependencies present in the project classpath (pom.xml/build.gradle).
3. `@ComponentScan`: Instructs Spring to scan the current package and its sub-packages for stereotypes (@Component, @Service, @Repository, @Controller), registering them as beans.
Code Example
@SpringBootApplication // Combines Configuration, AutoConfiguration, and Scan
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
Common Interview Pitfalls
- Placing the `@SpringBootApplication` class in a nested sub-package, causing ComponentScan to bypass parent packages and miss classes.
- Manually adding `@ComponentScan` pointing to the exact same packages, causing duplicate scan configurations.
How do `@Conditional` annotations drive Spring Boot auto-configuration, and what are common conditionals?
Direct Answer
`@Conditional` annotations evaluate system properties, classes on the classpath, or existence of other beans before creating a bean.
Detailed Explanation
Auto-configuration in Spring Boot is non-invasive and relies heavily on @Conditional annotations. These annotations allow beans to be registered dynamically based on condition evaluations at startup:
app.feature.enabled) is set to a specific value in properties/YAML files.Code Example
@Configuration
public class DatabaseConfig {
@Bean
@ConditionalOnProperty(name = "datasource.mock", havingValue = "false", matchIfMissing = true)
public DataSource realDataSource() {
return new HikariDataSource();
}
}
Common Interview Pitfalls
- Evaluating `@ConditionalOnBean` or `@ConditionalOnMissingBean` annotations in incorrect class order (conditionals should be run in auto-configurations after user configurations are processed).
- Configuring `@ConditionalOnProperty` but failing to specify a default value or setting `matchIfMissing` appropriately.
Describe the process of creating a custom Spring Boot Starter and configuring its auto-configuration imports.
Direct Answer
Create an autoconfigure module, write a `@Configuration` class with conditionals, register it in `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`, and package it.
Detailed Explanation
A custom Spring Boot Starter bundles a library alongside its auto-configuration:
1. Starters vs. Auto-configure: Create two modules: my-library-spring-boot-starter (empty pom that pulls in dependencies) and my-library-spring-boot-autoconfigure (containing configuration classes).
2. Configuration Class: Write a @AutoConfiguration class that instantiates your library's core beans using conditional checks (@ConditionalOnClass, @ConditionalOnMissingBean).
3. Registration (Spring Boot 2.7+): Create a file at src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. Add the fully qualified name of your auto-configuration class to this file.
4. Properties Configuration: Optionally expose properties using @ConfigurationProperties to allow custom starter configurations from the client's application.properties.
Code Example
// File: META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.autoconfigure.CustomLibraryAutoConfiguration
Common Interview Pitfalls
- Using `spring.factories` in Spring Boot 3.x+ (it has been removed in favor of the `.imports` file).
- Failing to make auto-configured beans `@ConditionalOnMissingBean`, which prevents clients from overriding those beans with their own configurations.
What is the order of precedence for configuration properties in a Spring Boot application?
Direct Answer
Spring Boot loads configuration properties in a strict order: Command line arguments override OS environment variables, which override application.properties/yaml files.
Detailed Explanation
Spring Boot uses a flexible property loading hierarchy. In case of duplicate keys, values from higher-priority sources override lower ones:
1. Command line arguments (e.g., --server.port=9090).
2. ServletConfig init parameters.
3. ServletContext init parameters.
4. JNDI attributes from java:comp/env.
5. Java System properties (System.getProperties()).
6. OS Environment variables (e.g. PORT=9090).
7. RandomValuePropertySource (e.g. random.*).
8. Profile-specific application properties outside packaged jar (application-{profile}.properties).
9. Profile-specific properties inside jar.
10. Application properties outside jar (application.properties).
11. Application properties inside jar.
Code Example
// Override database URL at execution time via OS Environment variable
// Spring Boot relaxes name rules: DB_PASSWORD maps to datasource.password
DATABASE_URL=jdbc:postgresql://prod-db:5432/db java -jar app.jar
Common Interview Pitfalls
- Assuming packaged properties inside the jar can override values set via environment variables in Kubernetes/Docker environments.
- Failing to utilize profile-specific configuration overrides for testing and production environments.
What is the N+1 Query Problem in JPA/Hibernate, and how do you resolve it?
Direct Answer
The N+1 query problem occurs when fetching an entity triggers a query for the parent, followed by N separate queries for its lazy-loaded children. Resolve using Join Fetch or `@EntityGraph`.
Detailed Explanation
The N+1 query problem occurs when JPA loads an entity with a collection relationship (e.g., Author with Books). Fetching N authors will trigger 1 query for the authors, and then N subsequent queries to fetch the books for each author individually, leading to database execution overhead.
Resolutions:
JOIN FETCH. This instructs Hibernate to perform an SQL INNER JOIN or LEFT JOIN and load the associated child collections in a single round-trip query.@BatchSize on the collection to load children in batches (e.g. groups of 20) instead of one by one.Code Example
@Repository
public interface AuthorRepository extends JpaRepository<Author, Long> {
@Query("SELECT a FROM Author a JOIN FETCH a.books")
List<Author> findAllWithBooks(); // Resolved via JPQL join fetch
@EntityGraph(attributePaths = {"books"})
List<Author> findAll(); // Resolved via EntityGraph
}
Common Interview Pitfalls
- Assuming that setting `FetchType.LAZY` on relationships prevents the N+1 problem (LAZY only defers queries; iterating over the lazy collection still triggers N+1).
- Using multiple independent JOIN FETCH statements for different collection relationships in a single query (triggers a Cartesian product validation error).
Describe the entity lifecycle states in JPA/Hibernate.
Direct Answer
JPA entities cycle through four states: Transient (new, unmanaged), Managed (associated with active PersistenceContext), Detached (session closed), and Removed (marked for deletion).
Detailed Explanation
An entity instance in JPA belongs to one of four states relative to the PersistenceContext (EntityManager):
1. Transient: The object is created using the new operator. It has no database identity (primary key) and is not associated with an active EntityManager session. Changes are not tracked.
2. Managed (Persistent): The entity is associated with the database and managed by the active PersistenceContext. Changes made to its fields are tracked, and Hibernate will flush updates to the database automatically during transaction commit.
3. Detached: The entity has a database identity but its associated PersistenceContext has been closed, cleared, or the entity was manually detached (via evict() or detach()). Changes are not tracked.
4. Removed: The entity is associated with an active PersistenceContext but has been marked for deletion (using EntityManager.remove()). It will be deleted from the database during the next flush.
Code Example
EntityManager em = ...;
em.getTransaction().begin();
User user = new User("John"); // Transient state
em.persist(user); // Managed state
em.getTransaction().commit(); // Database insert, user is still Managed
em.clear(); // Detached state (session cleared)
Common Interview Pitfalls
- Expecting database updates to trigger on modifications made to detached objects without calling `EntityManager.merge()`.
- Calling `persist` on an object that already has a primary key set, causing potential IdentifierGenerationExceptions.
Explain `@Transactional` propagation types in Spring and when to use each.
Direct Answer
`REQUIRED` (default) joins an active transaction or creates a new one. `REQUIRES_NEW` suspends active transactions to run in a separate physical connection.
Detailed Explanation
Spring transactions support seven propagation types to control boundaries across transactional boundaries:
Code Example
@Service
public class AuditService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logAction(String msg) {
// Will commit even if the calling method's transaction fails and rolls back
auditRepo.save(new AuditLog(msg));
}
}
Common Interview Pitfalls
- Using `Propagation.REQUIRES_NEW` inside the same class via self-invocation (aspect bypassed, runs in the parent transaction instead).
- Catching an exception inside a `REQUIRED` transaction boundary and hoping the transaction won't roll back (once a transaction is marked rollback-only, it cannot commit).
What causes LazyInitializationException in Hibernate, and how do you prevent it?
Direct Answer
`LazyInitializationException` occurs when attempting to access a lazy-loaded collection/proxy after its associated EntityManager session has been closed. Prevent using Join Fetch or EntityGraph.
Detailed Explanation
In JPA/Hibernate, FetchType.LAZY creates uninitialized proxy objects for children collections. If you close the transaction (and thus the EntityManager session) and then try to read the lazy collection (e.g. in the controller or serialization layer), Hibernate cannot query the database anymore and throws a LazyInitializationException.
Prevention Techniques:
1. Fetch Joining: Load dependencies eagerly within the active transaction scope using JOIN FETCH or @EntityGraph in the repository query.
2. DTO Projection: Project data directly into a non-managed Data Transfer Object (DTO) in the repository query, removing Hibernate proxies entirely.
3. Transactional Service Layer: Ensure all collection mappings are accessed while still within the @Transactional boundary before returning entities to controllers.
Code Example
@Service
public class BookService {
@Transactional
public BookDetails getBookDetails(Long id) {
Book book = bookRepo.findById(id).orElseThrow();
// Trigger lazy load inside active transaction context
book.getReviews().size();
return new BookDetails(book);
}
}
Common Interview Pitfalls
- Enabling `spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true` (anti-pattern: opens and closes a new database connection for *every* lazy property fetch, causing connection pool exhaustion).
- Accessing lazy relationships directly in JSON serialization classes without DTO mapping.
How do you implement pagination and sorting in Spring Data JPA?
Direct Answer
Pass a `Pageable` parameter (created via `PageRequest.of(page, size, Sort)`) to the repository method. Return a `Page<T>` or `Slice<T>` object.
Detailed Explanation
Spring Data JPA supports pagination and sorting dynamically through the PagingAndSortingRepository interface:
1. Pageable Object: Create a configuration using PageRequest.of(pageNumber, pageSize, Sort). This defines the offset and sorting properties.
2. Method Signature: Define your query with a Pageable argument:
Page<Product> findByCategory(String cat, Pageable p);LIMIT size + 1 query to check if a next page exists without running the expensive count query (ideal for infinite scroll).Code Example
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
Page<Product> findByNameContaining(String name, Pageable pageable);
}
// Controller call:
Pageable pageable = PageRequest.of(0, 10, Sort.by("price").descending());
Page<Product> page = productRepo.findByNameContaining("phone", pageable);
Common Interview Pitfalls
- Returning `Page<T>` for massive datasets when a `Slice<T>` or `List<T>` would avoid expensive SQL count queries.
- Performing pagination in-memory (e.g. fetching all rows via `.findAll()` and slicing them in Java list methods).
Explain the architecture of the Spring Security Filter Chain.
Direct Answer
Spring Security uses a series of servlet filters (managed by DelegatingFilterProxy and FilterChainProxy) to intercept HTTP requests for authentication, authorization, and CSRF protection.
Detailed Explanation
Spring Security's web infrastructure is built on standard Servlet Filters:
1. `DelegatingFilterProxy`: A standard Servlet filter registered with the servlet container. It delegates the processing of requests to a Spring-managed bean implementing Filter (FilterChainProxy).
2. `FilterChainProxy`: The entry point for Spring Security. It manages a list of SecurityFilterChain beans, selecting the appropriate chain that matches the incoming request path.
3. `SecurityFilterChain` Filters: The actual filters configured in sequence (e.g. UsernamePasswordAuthenticationFilter, BasicAuthenticationFilter, CsrfFilter, FilterSecurityInterceptor). Each filter inspects the request, handles authentication, writes security context variables, or redirects to login/error handlers.
Code Example
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
}
Common Interview Pitfalls
- Mixing up Servlet Filters and Spring MVC Interceptors (filters run before Spring MVC DispatcherServlet is reached).
- Configuring multiple SecurityFilterChains without defining clear path matching priority rules (`@Order`).
Detail the authentication flow in Spring Security using AuthenticationManager.
Direct Answer
An AuthenticationFilter extracts credentials into an unauthenticated Authentication token and delegates it to the AuthenticationManager, which queries AuthenticationProviders to return a fully populated, authenticated token.
Detailed Explanation
The core authentication flow follows these steps:
1. Credential Extraction: A filter (e.g. UsernamePasswordAuthenticationFilter) extracts username and password from the HTTP request and constructs an unauthenticated token (UsernamePasswordAuthenticationToken).
2. AuthenticationManager Delegation: The filter calls AuthenticationManager.authenticate(token).
3. Provider Loop: The manager (typically ProviderManager) delegates validation to one or more AuthenticationProvider instances (e.g., DaoAuthenticationProvider).
4. Credential Verification: The provider retrieves the user record (e.g., via UserDetailsService.loadUserByUsername()) and checks credentials (e.g., using PasswordEncoder).
5. Success Token: If valid, the provider constructs a fully authenticated Authentication token containing roles/authorities.
6. Context Persistence: The filter receives the authenticated token and saves it in the SecurityContextHolder.
Code Example
@Component
public class CustomAuthProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
String username = auth.getName();
String password = auth.getCredentials().toString();
// Implement custom validation logic
return new UsernamePasswordAuthenticationToken(username, password, List.of(new SimpleGrantedAuthority("ROLE_USER")));
}
@Override
public boolean supports(Class<?> authType) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authType);
}
}
Common Interview Pitfalls
- Failing to save the authenticated user in the SecurityContextHolder manually in custom filter implementations.
- Storing passwords in plain text instead of using BCrypt/Argon2 hashing encoders.
What are the pros and cons of JWT versus Session-based authentication in microservice architectures?
Direct Answer
Session-based authentication uses stateful sessions stored on the server. JWT is stateless, self-contained, and scales horizontally, but revocation is complex.
Detailed Explanation
Comparing the authentication models:
Authorization: Bearer header.Code Example
// Configure Spring Security for stateless JWT execution
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // Safe to disable CSRF when not using session cookies
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
Common Interview Pitfalls
- Disabling CSRF protection while still using stateful, cookie-based session authentication in your endpoints.
- Storing sensitive credentials (like passwords) inside unencrypted JWT claims.
How do `@PreAuthorize` and `@Secured` enforce method-level security in Spring?
Direct Answer
Enable method security via `@EnableMethodSecurity`. `@PreAuthorize` uses Spring Expression Language (SpEL) for dynamic condition evaluation before method execution.
Detailed Explanation
Spring method security enforces authorization checks directly on service-layer methods using AOP proxies:
@EnableMethodSecurity to a configuration class.ROLE_ADMIN) but does not support SpEL expressions.Code Example
@Service
public class DocumentService {
@PreAuthorize("hasRole('ADMIN') or #owner == authentication.name")
public Document getDocument(Long id, String owner) {
return docRepo.findById(id).orElseThrow();
}
}
Common Interview Pitfalls
- Adding `@PreAuthorize` to methods inside class definitions without enabling `@EnableMethodSecurity` (silently ignores the security checks).
- Using role name strings inside `hasRole(...)` expressions with the `ROLE_` prefix (Spring adds it automatically; use `hasRole("ADMIN")` instead of `hasRole("ROLE_ADMIN")`).
What is the difference between `@SpringBootTest` and `@WebMvcTest` in Spring Boot?
Direct Answer
`@SpringBootTest` bootstraps the complete ApplicationContext for integration testing. `@WebMvcTest` is a test slice that loads only the web layer and mocks dependencies.
Detailed Explanation
Spring Boot provides annotations to balance test coverage and boot speed:
webEnvironment = WebEnvironment.RANDOM_PORT.@MockBean.Code Example
@WebMvcTest(UserController.class)
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService; // Mocked dependency
@Test
public void testGetUsers() throws Exception {
mockMvc.perform(get("/users")).andExpect(status().isOk());
}
}
Common Interview Pitfalls
- Using `@SpringBootTest` for simple controller endpoint testing, adding unnecessary database configuration loading to the test runner.
- Forgetting that `@WebMvcTest` does not scan regular `@Component` or `@Service` beans, requiring them to be manually mocked or declared.
How does `@MockBean` simplify dependency mocking in Spring Boot tests?
Direct Answer
`@MockBean` creates a Mockito mock and registers it in the Spring ApplicationContext, replacing any existing bean of the same type.
Detailed Explanation
When writing integration tests in Spring Boot, you often need to mock external systems (such as third-party APIs or notification services) without loading real resources:
@MockBean is a Spring Boot-specific testing annotation. It tells the test context loader to create a Mockito mock of the declared class type.when(mock.method()).thenReturn(...)). After the test, the mock is reset automatically.Code Example
@SpringBootTest
public class OrderProcessorTest {
@Autowired
private OrderProcessor processor;
@MockBean
private PaymentGateway mockGateway; // Replaces the real gateway bean
@Test
public void testSuccess() {
Mockito.when(mockGateway.charge(100.0)).thenReturn(true);
boolean result = processor.process(100.0);
Assertions.assertTrue(result);
}
}
Common Interview Pitfalls
- Using Mockito's `@Mock` instead of `@MockBean` in Spring Boot tests, resulting in Spring injecting the real implementation instead of the mock.
- Failing to realize that using `@MockBean` in different tests alters the ApplicationContext, which disables Spring's test context caching and slows down the test suite.
How do you customize and secure Spring Boot Actuator endpoints?
Direct Answer
Configure exposure in `application.properties`, write classes annotated with `@Endpoint`, and secure the endpoint path `/actuator/**` in Spring Security.
Detailed Explanation
Spring Boot Actuator exposes production-ready metrics about your application:
1. Exposure Configuration: Actuator endpoints are disabled or hidden by default except /health. Expose them using:
management.endpoints.web.exposure.include=health,info,metrics
2. Custom Endpoints: Create custom endpoints using @Component and @Endpoint(id = "custom"). Annotate methods inside with @ReadOperation (HTTP GET), @WriteOperation (HTTP POST), or @DeleteOperation (HTTP DELETE).
3. Security Integration: Actuator paths contain sensitive operational metrics. You must configure Spring Security filter chains to restrict access to the /actuator/** endpoint prefix (e.g. requiring ROLE_ADMIN).
Code Example
@Component
@Endpoint(id = "systemStatus")
public class SystemStatusEndpoint {
@ReadOperation
public Map<String, String> getStatus() {
return Map.of("database", "UP", "threads", "OK");
}
}
Common Interview Pitfalls
- Exposing sensitive endpoints like `/env` or `/heapdump` to the public web without adding authentication constraints.
- Configuring endpoints without proper path mappings under `/actuator` in security filter chains.
How do you collect custom application metrics in Spring Boot using Micrometer?
Direct Answer
Inject a `MeterRegistry` bean, construct a metric type (Counter, Gauge, or Timer), and record application events to expose them at `/actuator/prometheus`.
Detailed Explanation
Micrometer is the metrics collection engine behind Spring Boot Actuator. It maps metrics data to monitoring backends (like Prometheus, Datadog, or InfluxDB):
1. MeterRegistry Injection: Inject MeterRegistry into your service bean.
2. Meter Selection:
3. Registration: Initialize meters using builders. Actuator exposes registered metrics on the /actuator/prometheus scrape endpoint dynamically.
Code Example
@Service
public class OrderService {
private final Counter orderCounter;
public OrderService(MeterRegistry registry) {
// Register custom Counter metric
this.orderCounter = Counter.builder("shop.orders.completed")
.description("Total number of completed orders")
.register(registry);
}
public void completeOrder() {
// Business logic
orderCounter.increment();
}
}
Common Interview Pitfalls
- Re-creating a Counter or Timer instance on every method execution instead of caching it as a single class field (creates duplicate registration overhead).
- Using Gauges to track running counts (Gauges should only monitor current states; use Counters for cumulative calculations).
Official Documentation & Specifications
Spring Framework Fundamentals
Dependency Injection & Context
Spring Boot Auto-configuration
Spring Data JPA & Hibernate
Spring Security & OAuth2
Want to tailer your resume for Spring Boot Developer roles?
Import your resume, scan it for critical Spring Boot Developer keywords, and compare it against ATS standards instantly.