FREE PREVIEW

You're viewing a free preview

This is a sample of 15 questions from our full collection of 66 interview questions.

Unlock all 66 questions with detailed explanations and code examples

Get Full Access

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:

  1. Simplify Enterprise Java Development: Traditional Java EE (Enterprise Java) was complex, heavyweight, and required extensive boilerplate code
  2. Reduce Coupling: Promote loose coupling between components through dependency injection
  3. Improve Testability: Make applications more testable by reducing dependencies on specific frameworks
  4. Non-invasive Framework: Allow developers to write Plain Old Java Objects (POJOs) without extending framework-specific classes
  5. 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 top

Explain 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:

  1. Container Management: Spring IoC container manages object lifecycle
  2. Configuration: Beans and dependencies defined through metadata
  3. Dependency Resolution: Container resolves and injects dependencies
  4. 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 top

Compare 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:

  1. Clear Layer Separation: Each annotation indicates the component's role in the application architecture
  2. AOP Integration: Different aspects can be applied based on stereotype (e.g., transaction management on @Service)
  3. Exception Handling: @Repository provides automatic exception translation
  4. Testing Support: Each layer can be tested independently using Spring's testing support
  5. 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 top

Spring 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_CLASS creates CGLIB proxy
  • ScopedProxyMode.INTERFACES creates JDK proxy
  • Required for injecting shorter-lived scoped beans into longer-lived beans
↑ Back to top

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 top

What 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:

  1. Dependency Aggregation: Starters are essentially Maven/Gradle POMs that aggregate related dependencies
  2. Version Management: They inherit from spring-boot-dependencies which provides version management
  3. Auto-Configuration Triggering: Dependencies on the classpath trigger corresponding auto-configuration classes
  4. 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 top

How 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 top

Spring 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 top

How 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:

  1. More specific path patterns take precedence over less specific ones
  2. Mappings with fewer wildcards are preferred
  3. 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 top

Spring 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 top

Transactions 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

  1. Proxy Creation: Spring creates a proxy (JDK dynamic proxy or CGLIB) around beans with @Transactional methods
  2. Transaction Interceptor: The proxy uses TransactionInterceptor to handle transaction logic
  3. PlatformTransactionManager: Delegates actual transaction operations to the configured transaction manager
  4. 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:

  • @Transactional only 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 top

Explain 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

  1. Use REQUIRED for most business operations
  2. Use REQUIRES_NEW for independent operations like auditing
  3. Use NESTED for batch processing with partial failure tolerance
  4. Consider performance implications of different propagation types
  5. Test transaction boundaries thoroughly, especially with complex propagation scenarios

Reference: Spring Transaction Propagation

↑ Back to top

Spring 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 top

How 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 top

Testing 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

Want more questions?

You've seen 15 sample questions. Unlock all 66 En interview questions with detailed explanations, code examples, and expert insights.

66+ questions
Code examples
Expert explanations
Instant access
Unlock Full Access