Spring BootRequest Parameters
Spring Boot

Request Parameters

Request parameters are the key-value pairs appended to a URL after the ? character. In Spring Boot, @RequestParam binds them to handler method parameters with automatic type conversion, default values, and optional binding. They are the standard mechanism for filtering, sorting, pagination, and search — anything that configures a collection view rather than identifying a specific resource.

@RequestParam Basics

@RequestParam binds a query string parameter to a method parameter. Spring performs automatic type conversion from the string value to the declared type. By default the parameter is required — a missing parameter produces a 400 Bad Request. The annotation's name attribute is optional when the parameter name matches the query string key.
Java
@RestController
@RequestMapping("/users")
public class UserController {

    // ── Basic binding — parameter name matches query key ───────────────
    @GetMapping
    public List<UserResponse> findAll(@RequestParam String role) {
        // GET /users?role=ADMIN
        return userService.findByRole(role);
    }

    // ── Explicit name — when parameter name differs from query key ─────
    @GetMapping
    public List<UserResponse> findAll(
            @RequestParam("role") String userRole) {
        return userService.findByRole(userRole);
    }

    // ── Type conversion — Spring converts string to declared type ──────
    @GetMapping("/search")
    public List<UserResponse> search(
            @RequestParam int minAge,       // "21"21
            @RequestParam boolean active,   // "true"true
            @RequestParam Long departmentId // "5"5L
    ) { ... }

    // ── Enum binding ───────────────────────────────────────────────────
    @GetMapping("/by-role")
    public List<UserResponse> findByRole(
            @RequestParam User.Role role) {
        // GET /users/by-role?role=ADMIN → User.Role.ADMIN
        return userService.findByRole(role);
    }
}

Optional Parameters and Default Values

Mark a parameter optional with required = false, or supply a defaultValue string. Both approaches prevent the 400 error when the query key is absent. defaultValue implicitly sets required = false.
Java
@RestController
@RequestMapping("/products")
public class ProductController {

    // ── required = false — parameter is null when absent ───────────────
    @GetMapping
    public List<ProductResponse> findAll(
            @RequestParam(required = false) String category,
            @RequestParam(required = false) User.Role role) {
        // GET /products           → category = null, role = null
        // GET /products?category=electronics → category = "electronics"
        return productService.findAll(category, role);
    }

    // ── defaultValue — used when parameter is absent ───────────────────
    @GetMapping("/search")
    public Page<ProductResponse> search(
            @RequestParam(defaultValue = "0")    int page,
            @RequestParam(defaultValue = "20")   int size,
            @RequestParam(defaultValue = "name") String sort,
            @RequestParam(defaultValue = "asc")  String direction,
            @RequestParam(required = false)      String query) {
        // GET /products/search → page=0, size=20, sort="name", direction="asc"
        // GET /products/search?page=2&size=10 → page=2, size=10, rest default
        return productService.search(query, page, size, sort, direction);
    }

    // ── Optional<T> — explicit absence vs null ─────────────────────────
    @GetMapping("/filter")
    public List<ProductResponse> filter(
            @RequestParam Optional<String> category,
            @RequestParam Optional<BigDecimal> minPrice) {
        return productService.filter(
            category.orElse(null),
            minPrice.orElse(BigDecimal.ZERO)
        );
    }

    // ── Wrapper types — null when absent (no defaultValue needed) ──────
    @GetMapping("/page")
    public Page<ProductResponse> page(
            @RequestParam(required = false) Integer page,
            @RequestParam(required = false) Integer size) {
        int p = page != null ? page : 0;
        int s = size != null ? size : 20;
        return productService.findPage(p, s);
    }
}

List and Array Parameters

A single query key repeated multiple times binds to a List or array parameter. Spring collects all values for that key into the collection automatically. Both ?ids=1&ids=2&ids=3 and comma-separated ?ids=1,2,3 are supported.
Java
@RestController
@RequestMapping("/users")
public class UserController {

    // ── List<String> — repeated key: ?tags=java&tags=spring&tags=boot ──
    @GetMapping("/by-tags")
    public List<UserResponse> findByTags(
            @RequestParam List<String> tags) {
        // GET /users/by-tags?tags=java&tags=spring
        return userService.findByTags(tags);
    }

    // ── List<Long> — type conversion applied to each value ─────────────
    @GetMapping("/batch")
    public List<UserResponse> findByIds(
            @RequestParam List<Long> ids) {
        // GET /users/batch?ids=1&ids=2&ids=42
        return userService.findByIds(ids);
    }

    // ── Array ──────────────────────────────────────────────────────────
    @GetMapping("/roles")
    public List<UserResponse> findByRoles(
            @RequestParam User.Role[] roles) {
        // GET /users/roles?roles=ADMIN&roles=USER
        return userService.findByRoles(Arrays.asList(roles));
    }

    // ── Comma-separated string → List (manual split) ───────────────────
    // Some clients send: ?ids=1,2,42 (single value, comma-separated)
    // @RequestParam List<Long> does NOT split on commas automatically.
    // Use @RequestParam String and split manually, or use a custom converter:
    @GetMapping("/csv-ids")
    public List<UserResponse> findByCsvIds(
            @RequestParam String ids) {
        List<Long> idList = Arrays.stream(ids.split(","))
            .map(Long::parseLong)
            .toList();
        return userService.findByIds(idList);
    }

    // ── Optional list — empty list when parameter is absent ───────────
    @GetMapping("/filter")
    public List<UserResponse> filter(
            @RequestParam(required = false) List<String> tags) {
        List<String> effectiveTags = tags != null ? tags : List.of();
        return userService.findByTags(effectiveTags);
    }
}

Capturing All Parameters into a Map

Inject Map<String, String> or MultiValueMap<String, String> with @RequestParam to receive all query parameters at once. MultiValueMap preserves multiple values for the same key.
Java
@RestController
@RequestMapping("/search")
public class SearchController {

    // ── Map<String, String> — one value per key (last value wins) ──────
    @GetMapping
    public List<SearchResult> search(
            @RequestParam Map<String, String> params) {
        // GET /search?q=alice&role=ADMIN&active=true
        // params = { "q": "alice", "role": "ADMIN", "active": "true" }
        String query  = params.get("q");
        String role   = params.get("role");
        String active = params.getOrDefault("active", "true");
        return searchService.search(query, role, Boolean.parseBoolean(active));
    }

    // ── MultiValueMap — preserves multiple values for the same key ──────
    @GetMapping("/multi")
    public List<SearchResult> searchMulti(
            @RequestParam MultiValueMap<String, String> params) {
        // GET /search/multi?tag=java&tag=spring&sort=name&sort=date
        // params = { "tag": ["java", "spring"], "sort": ["name", "date"] }
        List<String> tags  = params.get("tag");
        List<String> sorts = params.get("sort");
        return searchService.searchMulti(tags, sorts);
    }
}

Request Parameters with Pageable

Spring Data's Pageable integrates with @RequestParam transparently. @PageableDefault sets fallback values when pagination parameters are absent. This is the recommended pattern for paginated collection endpoints.
Java
@RestController
@RequestMapping("/products")
public class ProductController {

    // ── Pageable absorbs page, size, and sort parameters ──────────────
    @GetMapping
    public Page<ProductResponse> findAll(
            @PageableDefault(size = 20, sort = "name",
                             direction = Sort.Direction.ASC)
            Pageable pageable,
            @RequestParam(required = false) String category,
            @RequestParam(required = false) BigDecimal minPrice,
            @RequestParam(required = false) BigDecimal maxPrice) {

        // GET /products
        // GET /products?page=2&size=10
        // GET /products?sort=price,desc
        // GET /products?category=electronics&page=0&size=5&sort=price,asc
        return productService.search(category, minPrice, maxPrice, pageable);
    }
}

# Pageable query parameters:
# page      — zero-based page index (default: 0)
# size      — page size (default: 20, max configurable)
# sort      — sort expression: field,direction (e.g. sort=name,asc)
#             multiple sort params allowed: sort=category,asc&sort=price,desc

# Response (Spring Data Page):
{
  "content": [ ... ],
  "pageable": { "pageNumber": 0, "pageSize": 20 },
  "totalElements": 148,
  "totalPages": 8,
  "first": true,
  "last": false
}

# ── Limit max page size globally in application.yml: ──────────────────
spring:
  data:
    web:
      pageable:
        max-page-size: 100
        default-page-size: 20

Validation on Request Parameters

Add @Validated to the controller class to enable Bean Validation on @RequestParam values. Constraint annotations go directly on the parameter — no wrapper class needed.
Java
@RestController
@RequestMapping("/users")
@Validated   // required to activate parameter-level Bean Validation
public class UserController {

    @GetMapping
    public Page<UserResponse> findAll(
            @RequestParam(defaultValue = "0")
            @Min(value = 0, message = "page must be >= 0")
            int page,

            @RequestParam(defaultValue = "20")
            @Min(value = 1, message = "size must be >= 1")
            @Max(value = 100, message = "size must be <= 100")
            int size,

            @RequestParam(required = false)
            @Size(max = 100, message = "query must be 100 characters or fewer")
            String query) {
        return userService.search(query, page, size);
    }

    @GetMapping("/by-email")
    public UserResponse findByEmail(
            @RequestParam
            @Email(message = "Must be a valid email address")
            @NotBlank(message = "email is required")
            String email) {
        return userService.findByEmail(email);
    }
}

// Validation failure throws ConstraintViolationException (not
// MethodArgumentNotValidException — that is for @RequestBody).
// Handle it in @RestControllerAdvice:
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ErrorResponse> handleConstraintViolation(
        ConstraintViolationException ex) {
    Map<String, String> errors = ex.getConstraintViolations().stream()
        .collect(Collectors.toMap(
            cv -> cv.getPropertyPath().toString(),
            ConstraintViolation::getMessage
        ));
    return ResponseEntity.badRequest()
        .body(new ErrorResponse(400, "Validation Failed",
            "One or more parameters failed validation",
            Instant.now(), errors));
}