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: 20Validation 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));
}