Spring Framework Interview Questions (Free Preview)
Free sample of 15 from 66 questions available
Spring Framework Fundamentals
What is the Spring Framework and what were the primary motivations behind its creation?
The Spring Framework is a comprehensive programming and configuration model for modern Java-based enterprise applications. Created by Rod Johnson in 2003, Spring is an open-source application framework that provides infrastructure support for developing Java applications.
Primary Motivations Behind Spring's Creation:
- Simplify Enterprise Java Development: Traditional Java EE (Enterprise Java) was complex, heavyweight, and required extensive boilerplate code
- Reduce Coupling: Promote loose coupling between components through dependency injection
- Improve Testability: Make applications more testable by reducing dependencies on specific frameworks
- Non-invasive Framework: Allow developers to write Plain Old Java Objects (POJOs) without extending framework-specific classes
- Address EJB Complexity: Provide a simpler alternative to Enterprise JavaBeans (EJB) for enterprise applications
Core Philosophy:
- Convention over Configuration: Sensible defaults to reduce configuration overhead
- Don't Repeat Yourself (DRY): Eliminate boilerplate code through abstractions
- Testability First: Design applications that are easy to unit test and integrate test
References:
↑ Back to topExplain the concept of Inversion of Control (IoC) and its implementation in Spring.
Inversion of Control (IoC) is a design principle where the control of object creation and dependency management is transferred from the application code to an external container or framework.
Traditional Approach vs. IoC:
Traditional Approach (Without IoC):
public class OrderService {
private PaymentService paymentService;
public OrderService() {
// Direct dependency creation - tight coupling
this.paymentService = new CreditCardPaymentService();
}
public void processOrder(Order order) {
paymentService.processPayment(order.getAmount());
}
}
In this approach, OrderService directly creates its dependency, leading to tight coupling.
IoC Approach (With Spring):
@Service
public class OrderService {
private final PaymentService paymentService;
// Constructor injection - dependency provided by container
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void processOrder(Order order) {
paymentService.processPayment(order.getAmount());
}
}
Here, Spring container manages the dependency injection, promoting loose coupling.
Spring's IoC Implementation:
- Container Management: Spring IoC container manages object lifecycle
- Configuration: Beans and dependencies defined through metadata
- Dependency Resolution: Container resolves and injects dependencies
- Lifecycle Management: Container handles initialization and destruction
Benefits of IoC in Spring:
- Loose Coupling: Components depend on abstractions, not concrete implementations
- Testability: Easy to mock dependencies for unit testing
- Flexibility: Easy to swap implementations without code changes
- Maintainability: Centralized configuration and dependency management
References:
↑ Back to topCompare and contrast the @Component, @Repository, @Service, and @Controller annotations.
These annotations are part of Spring's stereotype annotations hierarchy, all derived from @Component but serving different architectural layers:
Hierarchy and Relationships:
@Component (Base stereotype)
├── @Repository (Data Access Layer)
├── @Service (Business Logic Layer)
└── @Controller (Presentation Layer)
Detailed Comparison:
| Annotation | Layer | Purpose | Special Features | Example Use Case |
|---|---|---|---|---|
| @Component | Generic | Generic Spring-managed component | Base functionality only | Utility classes, helpers |
| @Repository | Data Access | Data access and persistence | Exception translation | JPA repositories, DAOs |
| @Service | Business | Business logic and services | Transaction boundaries | Business operations |
| @Controller | Presentation | Web request handling | Request mapping support | MVC controllers |
1. @Component (Base Annotation)
@Component
public class FileUploadHelper {
public void uploadFile(MultipartFile file, String destination) {
// Generic utility functionality
try {
Files.copy(file.getInputStream(),
Paths.get(destination + "/" + file.getOriginalFilename()));
} catch (IOException e) {
throw new RuntimeException("File upload failed", e);
}
}
}
@Component is the most generic stereotype annotation for any Spring-managed component that doesn't fit specific layer categories.
2. @Repository (Data Access Layer)
@Repository
public class UserRepositoryImpl implements UserRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public User findByEmail(String email) {
try {
return entityManager.createQuery(
"SELECT u FROM User u WHERE u.email = :email", User.class)
.setParameter("email", email)
.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
@Override
public void save(User user) {
if (user.getId() == null) {
entityManager.persist(user);
} else {
entityManager.merge(user);
}
}
}
@Repository provides automatic exception translation from persistence-specific exceptions to Spring's DataAccessException hierarchy.
3. @Service (Business Logic Layer)
@Service
@Transactional
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
private final PasswordEncoder passwordEncoder;
public UserService(UserRepository userRepository,
EmailService emailService,
PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.emailService = emailService;
this.passwordEncoder = passwordEncoder;
}
public User createUser(CreateUserRequest request) {
// Business logic validation
if (userRepository.findByEmail(request.getEmail()) != null) {
throw new UserAlreadyExistsException("User with email already exists");
}
// Business operations
User user = new User();
user.setEmail(request.getEmail());
user.setPassword(passwordEncoder.encode(request.getPassword()));
User savedUser = userRepository.save(user);
// Send welcome email
emailService.sendWelcomeEmail(savedUser);
return savedUser;
}
}
@Service typically contains business logic and coordinates multiple repositories or external services.
4. @Controller (Presentation Layer)
@Controller
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
@ResponseBody
public ResponseEntity<UserDto> createUser(@Valid @RequestBody CreateUserRequest request) {
try {
User user = userService.createUser(request);
UserDto userDto = convertToDto(user);
return ResponseEntity.status(HttpStatus.CREATED).body(userDto);
} catch (UserAlreadyExistsException e) {
return ResponseEntity.status(HttpStatus.CONFLICT).build();
}
}
@GetMapping("/{id}")
@ResponseBody
public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return user != null ?
ResponseEntity.ok(convertToDto(user)) :
ResponseEntity.notFound().build();
}
}
@Controller handles HTTP requests and responses, delegating business logic to service layer.
Advanced Usage with @RestController:
@RestController // Combines @Controller + @ResponseBody
@RequestMapping("/api/products")
@Validated
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping
public Page<ProductDto> getProducts(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String category) {
Pageable pageable = PageRequest.of(page, size);
return productService.findProducts(category, pageable);
}
}
Key Architectural Benefits:
- Clear Layer Separation: Each annotation indicates the component's role in the application architecture
- AOP Integration: Different aspects can be applied based on stereotype (e.g., transaction management on @Service)
- Exception Handling: @Repository provides automatic exception translation
- Testing Support: Each layer can be tested independently using Spring's testing support
- Component Scanning: Enables selective scanning based on layer requirements
Best Practices:
- Use @Repository for data access objects that interact with databases
- Use @Service for business logic components that orchestrate multiple operations
- Use @Controller for web request handlers in MVC architecture
- Use @Component for general-purpose Spring beans that don't fit other categories
- Prefer @RestController over @Controller for REST APIs
References:
I'll provide comprehensive answers to these Spring Framework interview questions, covering beans, configuration, lifecycle management, scopes, autowiring, and profiles.
↑ Back to topSpring Beans and Configuration
Explain bean scopes (singleton, prototype, session, request) and their use cases.
Bean scopes define the lifecycle and visibility of Spring beans within the application context. Each scope serves specific use cases and has different memory and performance implications.
Comparison of Bean Scopes:
| Scope | Instances | Lifecycle | Use Cases |
|---|---|---|---|
| Singleton | One per container | Container lifetime | Services, DAOs, utilities |
| Prototype | New per request | Until garbage collected | Stateful objects, commands |
| Request | One per HTTP request | HTTP request duration | Request-specific data |
| Session | One per HTTP session | HTTP session duration | User-specific data |
1. Singleton Scope (Default):
@Component
@Scope("singleton") // Default scope
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User findById(Long id) {
return userRepository.findById(id);
}
}
Use Cases:
- Service layer components
- Data access objects (DAOs)
- Utility classes
- Configuration beans
Characteristics:
- Thread-safe implementation required
- Shared across entire application
- Memory efficient
2. Prototype Scope:
@Component
@Scope("prototype")
public class OrderProcessor {
private String orderId;
private LocalDateTime processedAt;
public void processOrder(String orderId) {
this.orderId = orderId;
this.processedAt = LocalDateTime.now();
// Process order logic
}
// Getters and setters
}
@Service
public class OrderService {
@Autowired
private ApplicationContext applicationContext;
public void handleOrder(String orderId) {
// Get new instance each time
OrderProcessor processor = applicationContext.getBean(OrderProcessor.class);
processor.processOrder(orderId);
}
}
Use Cases:
- Stateful objects that need isolation
- Command objects
- Objects with expensive initialization
- When thread safety is achieved through isolation
3. Request Scope:
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
private String userId;
private String requestId;
private Map<String, Object> attributes = new HashMap<>();
@PostConstruct
public void initialize() {
this.requestId = UUID.randomUUID().toString();
}
// Getters and setters
}
@RestController
public class UserController {
@Autowired
private RequestContext requestContext;
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable String id) {
requestContext.setUserId(id);
// Same RequestContext instance used throughout this request
return ResponseEntity.ok(userService.getUser(id));
}
}
4. Session Scope:
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserSession {
private String sessionId;
private User currentUser;
private List<String> visitedPages = new ArrayList<>();
@PostConstruct
public void initialize() {
this.sessionId = UUID.randomUUID().toString();
}
public void addVisitedPage(String page) {
visitedPages.add(page);
}
// Getters and setters
}
Proxy Mode Explanation:
ScopedProxyMode.TARGET_CLASScreates CGLIB proxyScopedProxyMode.INTERFACEScreates JDK proxy- Required for injecting shorter-lived scoped beans into longer-lived beans
Spring Boot
What is Spring Boot and how does it simplify development compared to traditional Spring?
Spring Boot is an opinionated framework built on top of the Spring Framework that aims to simplify the bootstrapping and development of Spring applications. It provides a convention-over-configuration approach that dramatically reduces the amount of boilerplate code and configuration required to create production-ready applications.
Key Simplifications Compared to Traditional Spring:
| Traditional Spring | Spring Boot |
|---|---|
| Manual XML configuration or extensive Java configuration | Auto-configuration based on classpath dependencies |
| Manual dependency management | Starter dependencies with pre-configured versions |
| Manual server setup and deployment | Embedded application servers (Tomcat, Jetty, Undertow) |
| Complex project structure setup | Simplified project structure with sensible defaults |
| Manual health checks and monitoring setup | Built-in production-ready features via Actuator |
Example Comparison:
Traditional Spring Application Setup:
// Multiple configuration files needed
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.example")
public class WebConfig implements WebMvcConfigurer {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
// Additional bean configurations...
}
// Separate web.xml configuration
// Manual dependency management in pom.xml
// Server deployment configuration
Spring Boot Equivalent:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// That's it! Auto-configuration handles the rest
The Spring Boot approach eliminates hundreds of lines of configuration code and provides a self-contained, executable JAR that can run anywhere with Java installed.
References:
↑ Back to topWhat are Spring Boot starters and how do they work under the hood?
Spring Boot Starters are dependency descriptors that provide a convenient way to include a cohesive set of dependencies for specific functionality. They follow a naming convention of spring-boot-starter-* and eliminate the need for developers to research and manually configure compatible versions of multiple libraries.
How Starters Work Under the Hood:
- Dependency Aggregation: Starters are essentially Maven/Gradle POMs that aggregate related dependencies
- Version Management: They inherit from
spring-boot-dependencieswhich provides version management - Auto-Configuration Triggering: Dependencies on the classpath trigger corresponding auto-configuration classes
- Conditional Configuration: Auto-configuration uses
@ConditionalOn*annotations to conditionally apply configurations
Popular Spring Boot Starters:
| Starter | Purpose | Key Dependencies |
|---|---|---|
spring-boot-starter-web |
Web applications with Spring MVC | spring-webmvc, tomcat-embed-core, jackson |
spring-boot-starter-data-jpa |
JPA with Hibernate | spring-data-jpa, hibernate-core |
spring-boot-starter-security |
Spring Security | spring-security-web, spring-security-config |
spring-boot-starter-test |
Testing support | junit, mockito, spring-test |
spring-boot-starter-actuator |
Production monitoring | micrometer, actuator endpoints |
Example: Web Starter Analysis
When you add spring-boot-starter-web:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
This starter internally includes:
<!-- Simplified view of what spring-boot-starter-web includes -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
Auto-Configuration Mechanism:
The presence of these dependencies triggers auto-configuration classes:
@Configuration
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
@Configuration
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
// Auto-configuration logic for Spring MVC
}
}
This auto-configuration class:
- Checks if required classes are on the classpath (
@ConditionalOnClass) - Only applies if no custom configuration exists (
@ConditionalOnMissingBean) - Automatically configures DispatcherServlet, view resolvers, message converters, etc.
Creating Custom Starters:
You can create custom starters for reusable functionality:
// Custom auto-configuration class
@Configuration
@ConditionalOnClass(MyCustomService.class)
@EnableConfigurationProperties(MyCustomProperties.class)
public class MyCustomAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyCustomService myCustomService(MyCustomProperties properties) {
return new MyCustomService(properties);
}
}
META-INF/spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfigure.MyCustomAutoConfiguration
This mechanism allows Spring Boot to automatically discover and apply configurations based on classpath contents, making application setup incredibly simple while maintaining flexibility for customization.
References:
↑ Back to topHow do you implement application monitoring using Spring Boot Actuator and health checks?
Spring Boot Actuator provides production-ready features for monitoring and managing Spring Boot applications. It offers a comprehensive set of built-in endpoints that provide insights into application metrics, health status, configuration, and runtime behavior.
Setting Up Actuator:
Maven Dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Basic Configuration:
management:
endpoints:
web:
exposure:
include: health, info, metrics, prometheus
base-path: /actuator
endpoint:
health:
show-details: always
show-components: always
server:
port: 9090 # Separate port for management endpoints
Built-in Actuator Endpoints:
| Endpoint | Purpose | URL Example |
|---|---|---|
/health |
Application health status | GET /actuator/health |
/info |
Application information | GET /actuator/info |
/metrics |
Application metrics | GET /actuator/metrics |
/env |
Environment properties | GET /actuator/env |
/loggers |
Logging configuration | GET /actuator/loggers |
/httptrace |
HTTP request traces | GET /actuator/httptrace |
/prometheus |
Prometheus metrics | GET /actuator/prometheus |
Implementing Custom Health Indicators:
Basic Health Indicator:
@Component
public class DatabaseHealthIndicator implements HealthIndicator {
@Autowired
private DataSource dataSource;
@Override
public Health health() {
try (Connection connection = dataSource.getConnection()) {
if (connection.isValid(1)) {
return Health.up()
.withDetail("database", "Available")
.withDetail("validationQuery", "SELECT 1")
.build();
}
} catch (SQLException e) {
return Health.down()
.withDetail("database", "Unavailable")
.withException(e)
.build();
}
return Health.down().withDetail("database", "Connection validation failed").build();
}
}
Reactive Health Indicator:
@Component
public class ExternalServiceHealthIndicator implements ReactiveHealthIndicator {
@Autowired
private WebClient webClient;
@Override
public Mono<Health> health() {
return webClient.get()
.uri("https://api.external-service.com/health")
.retrieve()
.toBodilessEntity()
.map(response -> Health.up()
.withDetail("externalService", "Available")
.withDetail("status", response.getStatusCode())
.build())
.onErrorReturn(Health.down()
.withDetail("externalService", "Unavailable")
.build());
}
}
Advanced Health Check Configuration:
Composite Health Indicators:
@Configuration
public class HealthConfiguration {
@Bean
public CompositeHealthContributor customHealthContributor() {
Map<String, HealthContributor> contributors = new LinkedHashMap<>();
contributors.put("database", new DatabaseHealthIndicator());
contributors.put("cache", new CacheHealthIndicator());
contributors.put("externalApi", new ExternalApiHealthIndicator());
return CompositeHealthContributor.fromMap(contributors);
}
}
Health Groups:
management:
endpoint:
health:
group:
readiness:
include: readinessState, database, externalApi
liveness:
include: livenessState, diskSpace
Custom Metrics Implementation:
Counter Metrics:
@Service
public class OrderService {
private final Counter orderCounter;
private final Timer orderProcessingTimer;
public OrderService(MeterRegistry meterRegistry) {
this.orderCounter = Counter.builder("orders.created")
.description("Number of orders created")
.tag("type", "online")
.register(meterRegistry);
this.orderProcessingTimer = Timer.builder("orders.processing.time")
.description("Order processing time")
.register(meterRegistry);
}
public Order createOrder(OrderRequest request) {
return orderProcessingTimer.recordCallable(() -> {
Order order = processOrder(request);
orderCounter.increment();
return order;
});
}
}
Gauge Metrics:
@Component
public class SystemMetrics {
private final List<String> activeUsers = new ArrayList<>();
@EventListener
public void handleUserLogin(UserLoginEvent event) {
activeUsers.add(event.getUserId());
}
@Bean
public Gauge activeUsersGauge(MeterRegistry meterRegistry) {
return Gauge.builder("system.active.users")
.description("Number of active users")
.register(meterRegistry, this, SystemMetrics::getActiveUserCount);
}
private Number getActiveUserCount(SystemMetrics systemMetrics) {
return systemMetrics.activeUsers.size();
}
}
Application Information Configuration:
Static Information:
info:
app:
name: My Spring Boot Application
description: Production monitoring example
version: 1.0.0
java:
version: @java.version@
maven:
version: @project.version@
Dynamic Information:
@Component
public class CustomInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("customInfo",
Map.of(
"activeProfiles", getActiveProfiles(),
"buildTime", getBuildTime(),
"lastDeployment", getLastDeploymentTime()
));
}
private List<String> getActiveProfiles() {
return Arrays.asList(environment.getActiveProfiles());
}
}
Production Monitoring Setup:
Prometheus Integration:
management:
endpoints:
web:
exposure:
include: prometheus
metrics:
export:
prometheus:
enabled: true
step: 10s
distribution:
percentiles-histogram:
http.server.requests: true
percentiles:
http.server.requests: 0.50, 0.90, 0.95, 0.99
Micrometer Custom Registry:
@Configuration
public class MetricsConfiguration {
@Bean
public PrometheusMeterRegistry prometheusMeterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
Method-Level Monitoring:
@RestController
public class OrderController {
@GetMapping("/orders")
@Timed(name = "orders.list", description = "Time taken to list orders")
@Counted(name = "orders.list.requests", description = "Number of requests to list orders")
public List<Order> listOrders() {
return orderService.findAll();
}
}
Kubernetes Integration:
Health Check Configuration for K8s:
management:
endpoint:
health:
probes:
enabled: true
health:
livenessstate:
enabled: true
readinessstate:
enabled: true
Kubernetes Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-boot-app
spec:
template:
spec:
containers:
- name: app
image: my-spring-boot-app:latest
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
This comprehensive monitoring setup provides real-time insights into application performance, health, and behavior, enabling proactive maintenance and quick issue resolution in production environments.
References:
↑ Back to topSpring MVC and Web Layer
What is the difference between @Controller and @RestController annotations?
The primary difference between @Controller and @RestController lies in their intended use cases and default response handling behavior.
| Aspect | @Controller | @RestController |
|---|---|---|
| Purpose | Traditional MVC with views | RESTful web services |
| Response Type | View names (String) | Direct object serialization |
| Content Type | HTML (typically) | JSON/XML |
| @ResponseBody | Required for JSON responses | Implicit on all methods |
| Composition | @Component |
@Controller + @ResponseBody |
@Controller Example
@Controller
public class WebController {
// Returns view name for rendering HTML page
@GetMapping("/users")
public String listUsers(Model model) {
model.addAttribute("users", userService.getAllUsers());
return "users-list"; // View name
}
// Explicitly requires @ResponseBody for JSON response
@GetMapping("/api/users")
@ResponseBody
public List<User> getUsersAsJson() {
return userService.getAllUsers(); // Serialized to JSON
}
}
@RestController Example
@RestController
@RequestMapping("/api")
public class UserRestController {
// Automatically serializes return value to JSON
@GetMapping("/users")
public List<User> getAllUsers() {
return userService.getAllUsers();
}
// Response is automatically converted to JSON
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
User savedUser = userService.save(user);
return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
}
}
The @RestController annotation is essentially a convenience annotation that combines @Controller and @ResponseBody, eliminating the need to annotate every request handling method with @ResponseBody.
When to Use Each
- @Controller: Use for traditional web applications that render HTML views
- @RestController: Use for RESTful APIs that return data in JSON or XML format
References:
↑ Back to topHow does request mapping work using @RequestMapping and its variants (@GetMapping, @PostMapping, etc.)?
Request mapping in Spring MVC allows you to bind HTTP requests to specific controller methods based on various criteria including URL patterns, HTTP methods, request parameters, and headers.
@RequestMapping - The Base Annotation
@RequestMapping is the most flexible annotation that can handle multiple HTTP methods and various mapping conditions:
@Controller
@RequestMapping("/api/users") // Class-level mapping
public class UserController {
// Method-level mapping with multiple HTTP methods
@RequestMapping(value = "/{id}", method = {RequestMethod.GET, RequestMethod.HEAD})
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return ResponseEntity.ok(user);
}
// Mapping with specific content type and headers
@RequestMapping(
value = "/search",
method = RequestMethod.POST,
consumes = "application/json",
produces = "application/json",
headers = "X-API-Version=1.0"
)
public List<User> searchUsers(@RequestBody SearchCriteria criteria) {
return userService.search(criteria);
}
}
HTTP Method-Specific Variants
Spring provides specialized annotations for common HTTP methods:
@RestController
@RequestMapping("/api/products")
public class ProductController {
@GetMapping // Equivalent to @RequestMapping(method = RequestMethod.GET)
public List<Product> getAllProducts() {
return productService.findAll();
}
@GetMapping("/{id}") // Path variable mapping
public Product getProduct(@PathVariable Long id) {
return productService.findById(id);
}
@PostMapping // Create new resource
public ResponseEntity<Product> createProduct(@RequestBody Product product) {
Product saved = productService.save(product);
return ResponseEntity.status(HttpStatus.CREATED).body(saved);
}
@PutMapping("/{id}") // Update existing resource
public Product updateProduct(@PathVariable Long id, @RequestBody Product product) {
return productService.update(id, product);
}
@DeleteMapping("/{id}") // Delete resource
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
productService.delete(id);
return ResponseEntity.noContent().build();
}
@PatchMapping("/{id}") // Partial update
public Product patchProduct(@PathVariable Long id, @RequestBody Map<String, Object> updates) {
return productService.partialUpdate(id, updates);
}
}
Advanced Mapping Features
@RestController
public class AdvancedMappingController {
// Request parameter mapping
@GetMapping("/users")
public List<User> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String filter) {
return userService.findUsers(page, size, filter);
}
// Multiple path variables
@GetMapping("/users/{userId}/orders/{orderId}")
public Order getUserOrder(@PathVariable Long userId, @PathVariable Long orderId) {
return orderService.findByUserAndId(userId, orderId);
}
// Regular expression in path variables
@GetMapping("/products/{code:[A-Z]{2}\\d{4}}")
public Product getProductByCode(@PathVariable String code) {
return productService.findByCode(code);
}
// Conditional mapping based on request parameters
@GetMapping(value = "/reports", params = "type=summary")
public SummaryReport getSummaryReport() {
return reportService.generateSummary();
}
// Header-based mapping
@GetMapping(value = "/data", headers = "Accept=application/json")
public ResponseEntity<String> getJsonData() {
return ResponseEntity.ok("{\"message\":\"JSON data\"}");
}
}
Mapping Precedence and Conflicts
When multiple mappings could match a request, Spring uses the following precedence rules:
- More specific path patterns take precedence over less specific ones
- Mappings with fewer wildcards are preferred
- Explicit HTTP method mappings beat generic ones
| Annotation | HTTP Method | Use Case |
|---|---|---|
@GetMapping |
GET | Retrieve resources |
@PostMapping |
POST | Create new resources |
@PutMapping |
PUT | Update entire resources |
@PatchMapping |
PATCH | Partial resource updates |
@DeleteMapping |
DELETE | Remove resources |
References:
↑ Back to topSpring Data
What is Spring Data and what are its main modules (Spring Data JPA, MongoDB, etc.)?
Spring Data is a comprehensive umbrella project within the Spring Framework ecosystem designed to simplify data access across various data storage technologies. Its primary objective is to provide a consistent, Spring-based programming model for data access while retaining the specific characteristics of each underlying data store.
Core Philosophy
Spring Data eliminates much of the boilerplate code traditionally required for data access operations by providing a repository abstraction layer. This abstraction allows developers to focus on business logic rather than infrastructure concerns.
Main Spring Data Modules
| Module | Purpose | Supported Technologies |
|---|---|---|
| Spring Data JPA | Relational database access | MySQL, PostgreSQL, Oracle, SQL Server |
| Spring Data MongoDB | Document database operations | MongoDB |
| Spring Data Redis | Key-value store operations | Redis |
| Spring Data Elasticsearch | Search engine integration | Elasticsearch |
| Spring Data Cassandra | Column-family database | Apache Cassandra |
| Spring Data Neo4j | Graph database operations | Neo4j |
| Spring Data JDBC | Lightweight JDBC abstraction | All JDBC-compatible databases |
| Spring Data R2DBC | Reactive database access | PostgreSQL, MySQL, H2 |
Key Features
- Repository Pattern Implementation: Provides standardized CRUD operations
- Query Method Generation: Automatically generates queries from method names
- Custom Query Support: JPQL, native SQL, and NoSQL query support
- Auditing Capabilities: Automatic tracking of entity creation and modification
- Pagination and Sorting: Built-in support for large dataset handling
- Transaction Management: Seamless integration with Spring's transaction framework
Example Implementation:
// Entity definition
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
// constructors, getters, setters
}
// Repository interface - no implementation required
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByUsername(String username);
List<User> findByEmailContaining(String emailPart);
}
This example demonstrates how Spring Data JPA eliminates the need for manual repository implementations while providing powerful query capabilities through method naming conventions.
References:
↑ Back to topTransactions and Spring AOP
How does the @Transactional annotation work internally and what are its key attributes?
The @Transactional annotation is Spring's declarative approach to transaction management, implemented through AOP proxies. When Spring detects this annotation, it creates a proxy around the target object that intercepts method calls to manage transactions.
Internal Working Mechanism
- Proxy Creation: Spring creates a proxy (JDK dynamic proxy or CGLIB) around beans with
@Transactionalmethods - Transaction Interceptor: The proxy uses
TransactionInterceptorto handle transaction logic - PlatformTransactionManager: Delegates actual transaction operations to the configured transaction manager
- Transaction Synchronization: Manages resources like database connections within the transaction scope
Here's an example showing the internal flow:
@Service
@Transactional
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private EmailService emailService;
// Method-level transaction configuration
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED,
timeout = 30,
rollbackFor = {BusinessException.class},
noRollbackFor = {ValidationException.class}
)
public User createUserWithNotification(User user) {
// This entire method executes within a single transaction
User savedUser = userRepository.save(user);
// If this throws BusinessException, transaction rolls back
emailService.sendWelcomeEmail(savedUser.getEmail());
return savedUser;
}
// Read-only transaction for performance optimization
@Transactional(readOnly = true)
public List<User> findActiveUsers() {
return userRepository.findByActiveTrue();
}
}
Key Attributes of @Transactional
| Attribute | Description | Default Value |
|---|---|---|
| propagation | Defines transaction propagation behavior | REQUIRED |
| isolation | Sets transaction isolation level | DEFAULT |
| timeout | Transaction timeout in seconds | -1 (no timeout) |
| readOnly | Optimization hint for read-only operations | false |
| rollbackFor | Exceptions that trigger rollback | RuntimeException and Error |
| noRollbackFor | Exceptions that don't trigger rollback | None |
| value/transactionManager | Specifies transaction manager bean | Default manager |
Advanced Configuration Example
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager manager = new DataSourceTransactionManager();
manager.setDataSource(dataSource);
manager.setDefaultTimeout(60); // Global timeout
return manager;
}
// Custom transaction manager for specific use cases
@Bean("asyncTransactionManager")
public PlatformTransactionManager asyncTransactionManager(DataSource dataSource) {
DataSourceTransactionManager manager = new DataSourceTransactionManager();
manager.setDataSource(dataSource);
manager.setValidateExistingTransaction(true);
return manager;
}
}
Important Considerations:
@Transactionalonly works when methods are called from external classes (proxy limitation)- Self-invocation within the same class bypasses the proxy
- Only public methods can be transactional by default
Reference: Spring Transaction Management
↑ Back to topExplain different transaction propagation types and their use cases.
Transaction propagation defines how transactions relate to each other when transactional methods call other transactional methods. Spring provides seven propagation types, each serving specific use cases.
Propagation Types Overview
| Propagation | Behavior | Use Case |
|---|---|---|
| REQUIRED | Join existing or create new transaction | Standard business operations |
| REQUIRES_NEW | Always create new transaction | Independent audit logging |
| SUPPORTS | Use existing transaction if present | Utility methods |
| NOT_SUPPORTED | Execute without transaction | File system operations |
| MANDATORY | Must have existing transaction | Nested business operations |
| NEVER | Must not have transaction | External service calls |
| NESTED | Create savepoint in existing transaction | Partial rollback scenarios |
Detailed Examples with Use Cases
1. REQUIRED (Default)
Most common propagation type for standard business operations:
@Service
public class OrderService {
@Autowired
private PaymentService paymentService;
@Transactional(propagation = Propagation.REQUIRED)
public void processOrder(Order order) {
// Transaction T1 starts here
orderRepository.save(order);
// This joins the existing transaction T1
paymentService.processPayment(order.getPayment());
// Both operations commit or rollback together
}
}
@Service
public class PaymentService {
@Transactional(propagation = Propagation.REQUIRED)
public void processPayment(Payment payment) {
// Joins existing transaction from processOrder()
paymentRepository.save(payment);
// If this fails, entire order processing rolls back
}
}
2. REQUIRES_NEW
Creates independent transactions for audit logging or separate concerns:
@Service
public class AuditService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logUserAction(String action, String userId) {
// This always creates a new transaction T2
// Even if called from within another transaction T1
AuditLog log = new AuditLog(action, userId, Instant.now());
auditRepository.save(log);
// T2 commits independently of T1
}
}
@Service
public class UserService {
@Autowired
private AuditService auditService;
@Transactional
public void updateUser(User user) {
// Transaction T1
userRepository.save(user);
// This creates independent transaction T2
auditService.logUserAction("USER_UPDATE", user.getId());
// If user update fails, audit log still persists
// If audit fails, user update still succeeds
}
}
3. NESTED
Useful for partial rollback scenarios using savepoints:
@Service
public class BatchProcessingService {
@Transactional
public void processUserBatch(List<User> users) {
// Main transaction T1
for (User user : users) {
try {
// Creates savepoint within T1
processIndividualUser(user);
} catch (ValidationException e) {
// Only this user's changes are rolled back
// Other users in the batch continue processing
log.warn("Failed to process user: {}", user.getId());
}
}
// T1 commits with successfully processed users
}
@Transactional(propagation = Propagation.NESTED)
public void processIndividualUser(User user) {
// Creates savepoint in existing transaction
validateUser(user);
userRepository.save(user);
sendNotification(user);
}
}
4. SUPPORTS
For utility methods that can work with or without transactions:
@Service
public class CacheService {
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public Optional<User> getCachedUser(String userId) {
// If called within a transaction, participates in it
// If called without transaction, executes without one
return Optional.ofNullable(userCache.get(userId));
}
}
Practical Implementation Strategy
@Configuration
public class TransactionConfig {
// Configure different transaction managers for different propagation needs
@Bean
public PlatformTransactionManager primaryTransactionManager(
@Qualifier("primaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public PlatformTransactionManager auditTransactionManager(
@Qualifier("auditDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
Best Practices
- Use REQUIRED for most business operations
- Use REQUIRES_NEW for independent operations like auditing
- Use NESTED for batch processing with partial failure tolerance
- Consider performance implications of different propagation types
- Test transaction boundaries thoroughly, especially with complex propagation scenarios
Reference: Spring Transaction Propagation
↑ Back to topSpring Security
What is Spring Security and what are its core security features?
Spring Security is a comprehensive security framework for Java applications, particularly those built with the Spring Framework. It provides a robust and customizable authentication and access control framework that can be easily integrated into Spring-based applications.
Core Security Features
| Feature | Description |
|---|---|
| Authentication | Verifies user identity through various mechanisms (form-based, HTTP Basic, OAuth2, SAML) |
| Authorization | Controls access to resources based on user roles and permissions |
| Session Management | Manages user sessions, including session fixation protection and concurrent session control |
| CSRF Protection | Prevents Cross-Site Request Forgery attacks by default |
| Password Encoding | Provides secure password storage using BCrypt, SCrypt, and other algorithms |
| Method Security | Enables annotation-based security at the method level |
| Remember-Me Authentication | Allows persistent login functionality |
| Security Headers | Automatically adds security headers (X-Frame-Options, X-Content-Type-Options) |
Spring Security operates on a filter-based architecture and integrates seamlessly with Spring's dependency injection and AOP capabilities.
References:
↑ Back to topHow do you implement OAuth2 and JWT integration with Spring Security?
OAuth2 and JWT integration in Spring Security enables modern authentication patterns, particularly useful for microservices and API-based applications.
OAuth2 Resource Server Configuration
@Configuration
@EnableWebSecurity
public class OAuth2ResourceServerConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.decoder(jwtDecoder())
.jwtAuthenticationConverter(jwtAuthenticationConverter())
)
)
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasAuthority("SCOPE_admin")
.requestMatchers("/api/user/**").hasAuthority("SCOPE_user")
.anyRequest().authenticated()
);
return http.build();
}
@Bean
public JwtDecoder jwtDecoder() {
// For symmetric key (shared secret)
String secretKey = "your-256-bit-secret-key-here";
return NimbusJwtDecoder.withSecretKey(
new SecretKeySpec(secretKey.getBytes(), "HmacSHA256")
).build();
// For asymmetric key (public key)
// return NimbusJwtDecoder.withJwkSetUri("https://issuer/.well-known/jwks.json").build();
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter authoritiesConverter =
new JwtGrantedAuthoritiesConverter();
authoritiesConverter.setAuthorityPrefix("SCOPE_");
authoritiesConverter.setAuthoritiesClaimName("scope");
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
return converter;
}
}
This configuration sets up Spring Security as an OAuth2 Resource Server that validates JWT tokens and extracts authorities from the token's scope claim.
JWT Token Generation Service
@Service
public class JwtTokenService {
private final JwtEncoder jwtEncoder;
private final JwtDecoder jwtDecoder;
public JwtTokenService(JwtEncoder jwtEncoder, JwtDecoder jwtDecoder) {
this.jwtEncoder = jwtEncoder;
this.jwtDecoder = jwtDecoder;
}
public String generateToken(Authentication authentication) {
Instant now = Instant.now();
long expiry = 3600L; // 1 hour
String scope = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(" "));
JwtClaimsSet claims = JwtClaimsSet.builder()
.issuer("your-app")
.issuedAt(now)
.expiresAt(now.plusSeconds(expiry))
.subject(authentication.getName())
.claim("scope", scope)
.claim("userId", getUserId(authentication))
.build();
return this.jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
}
public boolean validateToken(String token) {
try {
Jwt jwt = jwtDecoder.decode(token);
return jwt.getExpiresAt().isAfter(Instant.now());
} catch (JwtException e) {
return false;
}
}
private String getUserId(Authentication authentication) {
if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
// Extract user ID from UserDetails implementation
return userDetails.getUsername(); // Simplified example
}
return authentication.getName();
}
}
This service handles JWT token generation and validation, including setting custom claims and expiration times.
OAuth2 Client Configuration
@Configuration
public class OAuth2ClientConfig {
@Bean
public SecurityFilterChain clientFilterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.loginPage("/oauth2/authorization/google")
.defaultSuccessUrl("/dashboard")
.failureUrl("/login?error=oauth2")
.userInfoEndpoint(userInfo -> userInfo
.userService(customOAuth2UserService())
)
)
.oauth2Client(oauth2 -> oauth2
.clientRegistrationRepository(clientRegistrationRepository())
.authorizedClientService(authorizedClientService())
);
return http.build();
}
@Bean
public OAuth2UserService<OAuth2UserRequest, OAuth2User> customOAuth2UserService() {
return new CustomOAuth2UserService();
}
}
This configuration enables OAuth2 login functionality, allowing users to authenticate through external providers like Google, GitHub, or custom OAuth2 servers.
References:
↑ Back to topTesting in Spring
How do you set up unit and integration tests in Spring applications?
Setting up comprehensive testing in Spring applications involves configuring both unit and integration tests with proper test dependencies and structure.
Dependencies Setup: First, ensure your project includes the necessary testing dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
This starter includes JUnit 5, Mockito, AssertJ, Hamcrest, and Spring Test & Spring Boot Test modules.
Unit Tests Structure: Unit tests focus on testing individual components in isolation, typically using mocks for dependencies:
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void shouldCreateUser() {
// Given
User user = new User("john@example.com", "John Doe");
when(userRepository.save(any(User.class))).thenReturn(user);
// When
User result = userService.createUser("john@example.com", "John Doe");
// Then
assertThat(result.getEmail()).isEqualTo("john@example.com");
verify(userRepository).save(any(User.class));
}
}
This example demonstrates a pure unit test that isolates the UserService by mocking its UserRepository dependency using Mockito.
Integration Tests Structure: Integration tests verify that multiple components work together correctly:
@SpringBootTest
@TestPropertySource(locations = "classpath:application-test.properties")
class UserIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private UserRepository userRepository;
@Test
void shouldCreateAndRetrieveUser() {
// Given
CreateUserRequest request = new CreateUserRequest("jane@example.com", "Jane Doe");
// When
ResponseEntity<User> response = restTemplate.postForEntity("/api/users", request, User.class);
// Then
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertThat(userRepository.findByEmail("jane@example.com")).isPresent();
}
}
This integration test verifies the entire flow from HTTP request to database persistence.
Test Configuration: Create separate configuration files for testing:
# application-test.properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.jpa.hibernate.ddl-auto=create-drop
logging.level.org.springframework.web=DEBUG
↑ Back to top