C
Spring Boot/Web MVC/Lesson 05

Web MVC — *From Request to Response*

45 min·theory
This chapter
1/2

Web MVC — *From Request to Response*

🎯 After reading this lesson

After reading this lesson, you will be able to confidently do the following 3 things.

  • ✅ Implement 4 types of REST API (GET/POST/PUT/DELETE) using @RestController + @RequestMapping
  • ✅ Understand and use the differences between @RequestBody / @PathVariable / @RequestParam
  • ✅ Set up global exception handling patterns with @RestControllerAdvice

Keep these learning goals as a checklist — once you can answer all of them, close the lesson.

*How* Requests Are Processed — The DispatcherServlet Flow

Key Takeaway

When a browser sends a GET /users/42 request, Spring routes it through DispatcherServlet — a traffic controller — and delivers it to the right controller.

6-Step Flow

1. HTTP request arrives — received by the embedded Tomcat
2. DispatcherServlet — Spring's front door. All requests pass through here
3. HandlerMapping — "Which method on which controller handles this URL?"
4. Controller executes — business logic + Service call
5. Response object — DTO returned → Jackson serializes it to JSON
6. HTTP response — sent back to the browser

code
Request: GET /api/users/42
       │
       ▼
┌──────────────────────┐
│   Tomcat (embedded)  │
└────────┬─────────────┘
         │
         ▼
┌──────────────────────┐
│ DispatcherServlet    │  ← entry point for all requests
└────────┬─────────────┘
         │
         ▼
┌──────────────────────┐
│   HandlerMapping     │  ← "which method?"
└────────┬─────────────┘
         │
         ▼
┌──────────────────────┐
│  UserController      │
│   .get(42)           │  ← actual logic
└────────┬─────────────┘
         │
         ▼  Jackson → JSON
{"id":42, "name":"Hong Gil-dong"}

Each step is handled automatically by Spring. As a developer, you only need to write a @GetMapping("/users/{id}") method inside a @RestController class.

Filter vs Interceptor vs AOP — Hooking into the Middle

When you want to inject common logic (authentication, logging, compression) into the request processing pipeline, you have 3 options.

Filter — Servlet Standard

Runs before DispatcherServlet. Handles the entire HTTP response.

java
@Component
public class LoggingFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest req,
                                     HttpServletResponse res,
                                     FilterChain chain) {
        log.info("Request: {} {}", req.getMethod(), req.getRequestURI());
        chain.doFilter(req, res);     // To the next filter/servlet
        log.info("Response: {}", res.getStatus());
    }
}

Use for: common to all requests — logging, compression, CORS, auth token extraction

Interceptor — Spring MVC Layer

Runs after DispatcherServlet, hooks in before/after the controller is called.

java
@Component
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
        // *Before* controller call
        String token = req.getHeader("Authorization");
        if (!isValid(token)) {
            res.setStatus(401);
            return false;       // Abort
        }
        return true;
    }
}

Use for: targeting specific controller patterns. URL-based authorization and logging.

AOP — Method Level

Before/after specific method calls. The most granular option.

java
@Aspect
@Component
public class TimingAspect {
    @Around("@annotation(Timed)")
    public Object measure(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();
        log.info("{} executed {}ms", pjp.getSignature(), System.currentTimeMillis() - start);
        return result;
    }
}

@Service
public class OrderService {
    @Timed
    public void process() { ... }
}

@Transactional, @Cacheable, and similar annotations are all AOP-based.

When to use what?

  • Common to all requests → Filter
  • URL pattern-based → Interceptor
  • Specific methods → AOP

Summary

DispatcherServlet receives all requests and routes them to the appropriate controller. To inject common logic in between, choose from Filter, Interceptor, or AOP. The more granular the need, the further back you go.

@RestController + @GetMapping — *Building a REST API*

@Controller vs @RestController

The older @Controller in Spring returns a View (HTML). It worked with template engines like JSP and Thymeleaf for server-side rendering (SSR).

@RestController returns data (JSON). The standard for REST APIs communicating with SPAs and mobile apps. Essentially a combination of @Controller + @ResponseBody.

java
@RestController         // All method responses are JSON
@RequestMapping("/api/users")
public class UserController {

    @GetMapping("/{id}")
    public UserDto get(@PathVariable Long id) {
        return userService.findById(id);
    }
}

The returned UserDto object is automatically converted to JSON by Jackson and sent to the browser. Clean and concise.

HTTP Method Mapping

MethodAnnotationPurpose
GET@GetMappingRead
POST@PostMappingCreate
PUT@PutMappingFull replace
PATCH@PatchMappingPartial update
DELETE@DeleteMappingDelete

All are shorthand for @RequestMapping(method = RequestMethod.GET). Always use these shorthand forms for explicit semantic meaning.

4 Ways to Receive Parameters

1. @PathVariable — variable in the URL path:

java
@GetMapping("/users/{id}")
public User get(@PathVariable Long id) { ... }
// GET /users/42 → id = 42

2. @RequestParam — query string:

java
@GetMapping("/users")
public List<User> list(@RequestParam(defaultValue = "0") int page,
                        @RequestParam(required = false) String name) { ... }
// GET /users?page=2&name=Hong → page=2, name="Hong"

3. @RequestBody — JSON body (POST/PUT):

java
@PostMapping("/users")
public UserDto create(@RequestBody @Valid CreateUserDto dto) { ... }
// POST /users { "name":"Hong" } → dto.name = "Hong"

4. @RequestHeader / @CookieValue — HTTP headers and cookies:

java
@GetMapping("/me")
public UserDto me(@RequestHeader("Authorization") String token) { ... }

Response — Specifying HTTP Status Codes

Returning a value alone defaults to 200 OK. When you need a different status:

java
@PostMapping("/users")
@ResponseStatus(HttpStatus.CREATED)        // 201
public UserDto create(@RequestBody CreateUserDto dto) { ... }

// Or more precisely:
@GetMapping("/{id}")
public ResponseEntity<UserDto> get(@PathVariable Long id) {
    return userService.findById(id)
        .map(u -> ResponseEntity.ok(u))                 // 200
        .orElse(ResponseEntity.notFound().build());     // 404
}

Exception Handling — @RestControllerAdvice

Instead of scattering try-catch blocks across every controller, configure global exception handling:

java
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(EntityNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ErrorResponse notFound(EntityNotFoundException e) {
        return new ErrorResponse("NOT_FOUND", e.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResponse validation(MethodArgumentNotValidException e) {
        return new ErrorResponse("VALIDATION_FAILED",
            e.getBindingResult().getAllErrors().toString());
    }
}

Now, throwing EntityNotFoundException from any controller → automatically produces a 404 response.

Input Validation — @Valid

java
public record CreateUserDto(
    @NotBlank @Email String email,
    @NotBlank @Size(min=8, max=100) String password,
    @NotBlank String name
) { }

@PostMapping("/users")
public UserDto create(@RequestBody @Valid CreateUserDto dto) { ... }

Validation failure → MethodArgumentNotValidException is thrown automatically → the GlobalExceptionHandler above returns a clean 400 response.

Summary

  • @RestController + @GetMapping for instant REST API setup
  • @PathVariable, @RequestParam, @RequestBody for input
  • @ResponseStatus, ResponseEntity for status codes
  • @RestControllerAdvice for global exception handling
  • @Valid + Bean Validation for input validation

🤖 Try asking AI like this

Knowing the concepts from this lesson lets you give AI specific, precise instructions. Instead of a vague 'fix this,' you make vocabulary-driven requests — and that's where token savings start.

  • 'Add @ControllerAdvice-based global exception handling to this controller'
  • 'Create a User CRUD API with 4 endpoints (GET/POST/PUT/DELETE)'
  • 'Wrap this response in a ResponseEntity and return exactly 201 / 204 status codes'

Why This Reduces Tokens

Without the concepts, you have to ask 'what does that mean?' after every AI response. Those follow-up questions eat your tokens. Master the concepts once, and the conversation ends in a single exchange.

Web MVC — From Request to Response - Spring Boot