Skip to main content

Spring MVC & REST

Spring MVC is the web framework built on the Servlet API. In modern microservices, we mostly use it to build REST APIs.

1. REST Controller Annotations

AnnotationPurpose
@RestControllerCombines @Controller and @ResponseBody.
@RequestMappingBase path for the controller (e.g., /api/v1/users).
@GetMapping, @PostMappingShortcuts for specific HTTP methods.
@PutMapping, @DeleteMappingUpdate and Delete mappings.
@PathVariableExtract values from the URI path (e.g., /users/{id}).
@RequestParamExtract query parameters (e.g., /users?role=admin).
@RequestBodyMap the JSON body to a Java Object (POJO).
@ResponseStatusSet the HTTP status code (e.g., 201 CREATED).

2. Building a User API

Let’s build a CRUD API for a User resource. The Domain Model (DTO)
public record UserDto(Long id, String name, String email) {}
The Controller
@RestController
@RequestMapping("/api/v1/users")
public class UserController {

    // Simulating a DB
    private final List<UserDto> users = new ArrayList<>();

    @GetMapping
    public List<UserDto> getAllUsers() {
        return users;
    }

    @GetMapping("/{id}")
    public UserDto getUserById(@PathVariable Long id) {
        return users.stream()
                .filter(u -> u.id().equals(id))
                .findFirst()
                .orElseThrow(() -> new RuntimeException("User not found"));
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public UserDto createUser(@RequestBody UserDto user) {
        users.add(user);
        return user;
    }
}

3. Exception Handling with @ControllerAdvice

Don’t let raw stack traces leak to the client. Use global exception handling.
@RestControllerAdvice // 1. Global Interceptor
public class GlobalExceptionHandler {

    @ExceptionHandler(RuntimeException.class) // 2. Catch this exception
    @ResponseStatus(HttpStatus.NOT_FOUND) // 3. Return 404
    public ErrorResponse handleNotFound(RuntimeException e) {
        return new ErrorResponse(404, e.getMessage());
    }
}

record ErrorResponse(int status, String message) {}

4. Bean Validation

Never trust client input. Use Hibernate Validator (implementation of Jakarta Bean Validation). Add dependency spring-boot-starter-validation. Add Constraints to DTO
public record UserCreateRequest(
    @NotBlank(message = "Name is required")
    String name,

    @Email(message = "Invalid email format")
    @NotBlank
    String email
) {}
Validate in Controller
@PostMapping
public UserDto createUser(@Valid @RequestBody UserCreateRequest request) { 
    // If validation fails, Spring throws MethodArgumentNotValidException
    // ... logic
}
You can then catch MethodArgumentNotValidException in your @RestControllerAdvice to return a nice list of validation errors.

5. Content Negotiation

Spring Boot uses Jackson by default to serialize/deserialize Java Objects to JSON.
  • If you want XML, add jackson-dataformat-xml dependency.
  • Spring will check the Accept header of the request to decide whether to return JSON or XML.

6. Internal Request Lifecycle (DispatcherServlet)

Spring MVC is designed around the Front Controller pattern. The DispatcherServlet handles all incoming requests.

7. Filters vs Interceptors vs AOP

Interviewers love this question.
FeatureFilterInterceptorAOP
LayerServlet Container (Tomcat)Spring MVC FrameworkSpring Bean (Method Level)
ScopeRuns for ALL requests (even non-Spring)Runs only for valid DispatcherServlet requestsRuns for method calls
AccessRaw ServletRequest / ServletResponseHandlerMethod (Knows which controller is mapped)Method Arguments & Return Value
Use CaseSecurity, GZip Compression, CORSAuth Checks, Logging execution timeTransaction mgmt, Audit Logging

Implementing an Interceptor

public class LoggingInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        System.out.println("Incoming request: " + request.getRequestURI());
        return true; // Continue processing
    }
}

// Register it
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoggingInterceptor());
    }
}

8. Asynchronous Requests

If an API takes 10 seconds, you don’t want to block a Tomcat thread (default ~200 threads) for 10s. CompletableFuture
@GetMapping("/async")
public CompletableFuture<String> asyncEndpoint() {
    return CompletableFuture.supplyAsync(() -> {
        slowService.process(); // Runs in a separate ForkJoinPool
        return "Done";
    });
}
Tomcat thread is released immediately. The response is sent when the future completes.

9. Spring Security Integration

Spring Security is simply a chain of standard Servlet Filters.
If AuthenticationFilter fails (e.g., bad token), it throws an exception and the request never reaches the Controller.

10. Deep Dive: Spring Security Architecture

Spring Security is a lot more than just a few annotations.

The Big Picture

  1. DelegatingFilterProxy: A standard Servlet Filter (registered with Tomcat) that delegates to a Spring Bean.
  2. FilterChainProxy: The Spring Bean that holds all security logic. It contains a list of SecurityFilterChains.
  3. SecurityFilterChain: A chain of filters matching a specific URL pattern.

The Authentication Flow

Key Components

  • AuthenticationManager: The API that defines how Spring Security’s Filters perform authentication.
  • ProviderManager: The standard implementation of AuthenticationManager. It delegates to a list of AuthenticationProviders.
  • AuthenticationProvider: Doing the actual work (e.g., DaoAuthenticationProvider talks to DB, LdapAuthenticationProvider talks to LDAP).
  • UserDetailsService: Interface to load user-specific data using a username.