Skip to content

Spring Boot Annotations (Controller Layer) | Controller, RestController, RequestMapping

The Reception Desk and Mail Sorting Office

Imagine a corporate headquarters. At the front entrance sits the reception desk. When an envelope arrives from a courier, the receptionist inspects the label: Is it addressed to Human Resources, Billing, or Legal? Is it marked as urgent? Does it contain confidential tax forms, or is it a simple delivery confirmation? The receptionist does not process payroll or draft contracts; they inspect the envelope, sort the mail, and hand it to the appropriate department.

In Spring Boot, the Controller Layer is that corporate reception desk. Annotations are the standardized shipping labels. They declare: Which HTTP path routes here? Is the incoming data inside the URL path, inside the query parameters, or inside the JSON payload body? Should the response be an HTML webpage, or should it be serialized directly as raw JSON data?

This lecture covers all major annotations used in Spring Boot's web layer, the critical distinction between @Controller and @RestController, path mapping shortcuts, and extracting data using @PathVariable, @RequestParam, @RequestBody, and @RequestHeader.


@Controller vs @RestController

Understanding the difference between @Controller and @RestController is one of the most fundamental Spring web concepts.

Feature@Controller@RestController
Target ApplicationTraditional web apps rendering server side HTML (Thymeleaf, JSP)RESTful web APIs returning raw data (JSON, XML)
Return Value InterpretationMethod return value is treated as a view template nameMethod return value is written directly to the HTTP response body
CompositionPlain @ComponentMeta annotation: combines @Controller + @ResponseBody
JSON SerializationRequires manual @ResponseBody on every single methodAutomatic on every method via Jackson
java
// Traditional Controller: returns a view name like "home.html"
@Controller
public class WebPageController {

    @GetMapping("/home")
    public String showHomePage() {
        return "home"; // Resolves to templates/home.html
    }
}

// REST Controller: returns raw domain object serialized directly to JSON
@RestController
public class ApiController {

    @GetMapping("/api/user")
    public User getUser() {
        return new User("Alice", "alice@example.com"); // Serialized to {"name":"Alice","email":"alice@example.com"}
    }
}

Notice that @RestController is simply a shortcut:

java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
}

Path Mapping: @RequestMapping and HTTP Shortcuts

@RequestMapping maps HTTP requests to handler methods. You can declare it at the class level to establish a common base URL path for all endpoints in that controller:

java
@RestController
@RequestMapping("/api/v1/products")
public class ProductController {

    // Inherits base path: GET /api/v1/products
    @GetMapping
    public List<Product> getAllProducts() { /* ... */ }

    // Appends sub-path: GET /api/v1/products/featured
    @GetMapping("/featured")
    public List<Product> getFeaturedProducts() { /* ... */ }
}

The Five HTTP Verb Shortcuts

Spring provides dedicated method level shortcuts for the five standard HTTP verbs:

AnnotationHTTP MethodREST Convention & MeaningIdempotent
@GetMappingGETRead / Retrieve resourcesYes
@PostMappingPOSTCreate new resourcesNo
@PutMappingPUTReplace an existing resource entirelyYes
@PatchMappingPATCHPartially update fields of an existing resourceNo
@DeleteMappingDELETERemove a resourceYes
java
@RestController
@RequestMapping("/api/customers")
public class CustomerController {

    @GetMapping("/{id}")
    public Customer getById(@PathVariable Long id) { /* ... */ }

    @PostMapping
    public Customer create(@RequestBody Customer customer) { /* ... */ }

    @PutMapping("/{id}")
    public Customer replace(@PathVariable Long id, @RequestBody Customer customer) { /* ... */ }

    @PatchMapping("/{id}")
    public Customer updatePartial(@PathVariable Long id, @RequestBody CustomerUpdates updates) { /* ... */ }

    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id) { /* ... */ }
}

Extracting Request Data: Four Core Annotations

Spring Boot provides four primary annotations for extracting data from incoming HTTP requests:

1. @PathVariable

Extracts values embedded directly inside the URI path template:

java
// URL: /api/orders/99
@GetMapping("/api/orders/{orderId}")
public Order getOrder(@PathVariable("orderId") Long orderId) {
    return orderService.findById(orderId);
}

If the method parameter name matches the path placeholder exactly, the annotation argument can be omitted:

java
@GetMapping("/api/orders/{orderId}")
public Order getOrder(@PathVariable Long orderId) { /* ... */ }

2. @RequestParam

Extracts query parameters from the URL query string (after the ? mark) or form data:

java
// URL: /api/products?category=books&page=2
@GetMapping("/api/products")
public List<Product> searchProducts(
        @RequestParam(name = "category", required = false, defaultValue = "all") String category,
        @RequestParam(name = "page", defaultValue = "0") int page) {
    return productService.search(category, page);
}

Key attributes of @RequestParam:

  • required: Defaults to true. If true and the client omits the parameter, Spring returns 400 Bad Request.
  • defaultValue: Automatically marks required as false and supplies a fallback value.

Path Variable vs Request Parameter: When to Use Which?

Dimension@PathVariable@RequestParam
PurposeIdentify a specific resourceFilter, sort, paginate, or modify search criteria
URL Example/api/books/101/api/books?author=tolkien&sort=year
SemanticsPart of the identity of the resourceOptional query modifier

3. @RequestBody

Deserializes the JSON or XML payload from the HTTP request body into a strongly typed Java object using Jackson:

java
@PostMapping("/api/users")
public ResponseEntity<User> createUser(@Valid @RequestBody UserDto dto) {
    User created = userService.save(dto);
    return ResponseEntity.status(HttpStatus.CREATED).body(created);
}

Spring inspects the Content-Type header (e.g. application/json) and selects an appropriate HttpMessageConverter to parse the input into your DTO object.

4. @RequestHeader

Extracts HTTP header values from incoming requests:

java
@GetMapping("/api/account")
public AccountDetails getAccount(
        @RequestHeader("Authorization") String authToken,
        @RequestHeader(value = "X-Device-Type", defaultValue = "web") String deviceType) {
    return accountService.getDetails(authToken, deviceType);
}

You can also inject all headers at once into a HttpHeaders or Map<String, String> map:

java
@GetMapping("/api/debug-headers")
public Map<String, String> debugHeaders(@RequestHeader Map<String, String> headers) {
    return headers;
}

Practical Controller Example

Here is a complete, production ready controller demonstrating all annotations working together:

java
package com.example.ecommerce.controller;

import com.example.ecommerce.dto.CreateProductRequest;
import com.example.ecommerce.dto.ProductResponse;
import com.example.ecommerce.service.ProductService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/v1/catalog")
public class CatalogController {

    @Autowired
    private ProductService productService;

    // 1. Path Variable + Request Header
    @GetMapping("/products/{productId}")
    public ResponseEntity<ProductResponse> getProductById(
            @PathVariable Long productId,
            @RequestHeader(value = "X-User-Locale", defaultValue = "en-US") String locale) {

        ProductResponse product = productService.findById(productId, locale);
        return ResponseEntity.ok(product);
    }

    // 2. Request Parameters for Filtering and Pagination
    @GetMapping("/products")
    public ResponseEntity<List<ProductResponse>> searchProducts(
            @RequestParam(required = false) String brand,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {

        List<ProductResponse> results = productService.search(brand, page, size);
        return ResponseEntity.ok(results);
    }

    // 3. Request Body for Resource Creation
    @PostMapping("/products")
    public ResponseEntity<ProductResponse> addProduct(
            @Valid @RequestBody CreateProductRequest request) {

        ProductResponse created = productService.create(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(created);
    }

    // 4. Delete mapping
    @DeleteMapping("/products/{productId}")
    public ResponseEntity<Void> removeProduct(@PathVariable Long productId) {
        productService.delete(productId);
        return ResponseEntity.noContent().build();
    }
}

Interview Questions & Pitfalls

Q1: What is the fundamental difference between @Controller and @RestController in Spring Boot?

@Controller is designed for traditional web applications where handler methods return a string representing an HTML view template name (such as a Thymeleaf or JSP template). @RestController is a meta annotation combining @Controller and @ResponseBody: its return values are serialized directly into the HTTP response body as raw JSON or XML via Jackson.

Q2: What happens if a client omits a parameter marked with @RequestParam without configuring a default value?

By default, @RequestParam(required = true) is assumed. If the parameter is missing in the request URL query string, Spring Boot automatically aborts execution and returns an HTTP 400 Bad Request error to the client before your method code executes.

Q3: When should you choose @PathVariable over @RequestParam?

Use @PathVariable to identify a specific, unique resource hierarchically (e.g. /orders/101). Use @RequestParam to supply optional modifiers, filters, search terms, sorting flags, or pagination bounds (e.g. /orders?status=PENDING&page=2).

Q4: How does Spring Boot deserialize JSON payloads into @RequestBody objects?

Spring Boot uses the HttpMessageConverter interface. For JSON payloads, it delegates to MappingJackson2HttpMessageConverter, which uses the Jackson ObjectMapper library to inspect the JSON fields and map them to matching setter methods or constructor arguments in your Java DTO class.

Q5: Can @RequestMapping be applied at both class level and method level?

Yes. Placing @RequestMapping("/api/v1/orders") at the class level defines a common root URL path prefix for all endpoints in that class. Method level annotations (@GetMapping("/{id}")) append their path to the root prefix, resulting in /api/v1/orders/{id}.