Request Input Handling
Request Input Handling Params Headers Forms
Teknik menerima input HTTP secara eksplisit di JAX-RS: path params, query params, headers, cookies, forms, optionality, default values, conversion, canonicalization, validation, and failure modes
Part 026 — Request Input Handling: Params, Headers, Forms
Fokus part ini: memahami bagaimana input HTTP masuk ke resource method JAX-RS, bagaimana binding terjadi, apa failure mode-nya, dan bagaimana mendesain input contract yang eksplisit, aman, dan mudah direview.
Di JAX-RS, input bisa datang dari banyak tempat:
- path parameter
- query parameter
- header
- cookie
- form field
- request body JSON/XML
- multipart part
- context object
Part ini fokus pada params, headers, cookies, dan forms. Request body JSON/XML akan tersambung ke part validation, serialization, dan DTO mapping.
Senior-level principle:
Input handling is contract enforcement at the system boundary.
Jika boundary longgar, ambiguity masuk ke domain/service layer.
1. Mental Model: HTTP Request to Java Arguments
Request HTTP mentah:
GET /quotes/Q-123/items?includePricing=true&limit=50 HTTP/1.1
Host: quote-api.example.internal
Accept: application/json
X-Correlation-ID: c-abc-123
Authorization: Bearer eyJ...
Cookie: sessionId=s-123
JAX-RS resource method:
@GET
@Path("/quotes/{quoteId}/items")
@Produces(MediaType.APPLICATION_JSON)
public QuoteItemsResponse getQuoteItems(
@PathParam("quoteId") String quoteId,
@QueryParam("includePricing") @DefaultValue("false") boolean includePricing,
@QueryParam("limit") @DefaultValue("50") int limit,
@HeaderParam("X-Correlation-ID") String correlationId
) {
return quoteItemService.getItems(quoteId, includePricing, limit, correlationId);
}
Binding flow:
Failure can happen at every step.
2. Path Parameters
Path params identify resource location.
Example:
@GET
@Path("/quotes/{quoteId}")
public QuoteResponse getQuote(@PathParam("quoteId") String quoteId) {
return quoteService.getQuote(quoteId);
}
Path params should usually represent:
- resource identity
- parent-child hierarchy
- stable addressable entity
Examples:
/quotes/{quoteId}
/quotes/{quoteId}/items/{itemId}
/orders/{orderId}
/customers/{customerId}/quotes
Avoid using path params for arbitrary filters:
Bad:
/orders/status/ACTIVE/date/2026-07-10
Better:
/orders?status=ACTIVE&createdDate=2026-07-10
Path parameter is part of resource identity. Query parameter modifies retrieval.
3. Path Param Type Conversion
JAX-RS can convert simple types.
Example:
@GET
@Path("/catalogs/{catalogVersion}")
public CatalogResponse getCatalog(@PathParam("catalogVersion") int catalogVersion) {
return catalogService.getCatalog(catalogVersion);
}
But be careful.
If conversion fails, request usually fails before business logic runs.
Example request:
GET /catalogs/current
If method expects int, conversion fails.
For enterprise APIs, prefer explicit identity types as string unless numeric semantics are truly part of the contract.
@GET
@Path("/catalogs/{catalogVersion}")
public CatalogResponse getCatalog(@PathParam("catalogVersion") String catalogVersion) {
return catalogService.getCatalog(catalogVersion);
}
Then validate intentionally.
4. Path Param Ambiguity
Ambiguous paths create routing surprises.
Example:
@GET
@Path("/quotes/{quoteId}")
public QuoteResponse getQuote(...) { ... }
@GET
@Path("/quotes/search")
public QuoteSearchResponse searchQuotes(...) { ... }
Depending on matching rules and implementation, /quotes/search might be interpreted as quoteId = search if routes are poorly structured.
Better:
GET /quotes/{quoteId}
GET /quotes?filter=...
Or if explicit search resource is needed:
POST /quote-searches
GET /quote-searches/{searchId}
Review path design before coding resource methods.
5. Query Parameters
Query params modify retrieval, filtering, sorting, pagination, or optional response expansion.
Example:
@GET
@Path("/quotes")
public QuoteSearchResponse searchQuotes(
@QueryParam("customerId") String customerId,
@QueryParam("status") List<String> statuses,
@QueryParam("limit") @DefaultValue("50") int limit,
@QueryParam("cursor") String cursor
) {
return quoteQueryService.search(customerId, statuses, limit, cursor);
}
Common query param categories:
- filter: status, customerId, effectiveDate
- pagination: limit, offset, cursor, pageToken
- sorting: sort, orderBy
- expansion: includePricing, includeItems
- projection: fields
Part 023 covers pagination/filtering/sorting in depth. This part focuses on binding mechanics and boundary rules.
6. Repeated Query Parameters
HTTP allows repeated query params:
GET /quotes?status=DRAFT&status=SUBMITTED&status=APPROVED
JAX-RS can bind to list-like types:
@QueryParam("status") List<String> statuses
Alternative comma-separated style:
GET /quotes?status=DRAFT,SUBMITTED,APPROVED
Both are possible, but choose one convention.
Repeated params are more naturally HTTP-native. Comma-separated params are compact but need custom parsing and escaping rules.
Internal verification:
- Does internal API convention prefer repeated params or comma-separated values?
- Are generated clients compatible with the convention?
- Does API gateway preserve repeated query params?
7. Optionality and Defaults
Optionality should be explicit.
Example:
@QueryParam("includePricing") @DefaultValue("false") boolean includePricing
Without @DefaultValue, primitive types can be risky because missing input maps to primitive default in some cases or conversion behavior depends on provider/version.
Safer patterns:
@QueryParam("limit") @DefaultValue("50") int limit
Or:
@QueryParam("cursor") String cursor
Or with optional wrapper if supported by project convention:
@QueryParam("cursor") Optional<String> cursor
But do not assume Optional<T> binding is supported identically across all JAX-RS implementations and versions. Verify internally.
Senior-level rule:
A default value is part of the API contract.
Changing default sort, limit, include flag, or effective date behavior can be breaking.
8. Header Parameters
Headers carry metadata, not primary business identity.
Example:
@GET
@Path("/quotes/{quoteId}")
public QuoteResponse getQuote(
@PathParam("quoteId") String quoteId,
@HeaderParam("X-Correlation-ID") String correlationId,
@HeaderParam("X-Tenant-ID") String tenantId
) {
return quoteService.getQuote(tenantId, quoteId, correlationId);
}
Common enterprise headers:
- Authorization
- X-Correlation-ID
- X-Request-ID
- X-Tenant-ID
- X-Idempotency-Key
- If-Match
- If-None-Match
- Accept-Language
- User-Agent
But direct header injection in every resource method can become noisy.
Often better:
request filter extracts headers
-> validates/normalizes
-> puts into request context/security context/MDC
-> resource method receives business inputs only
Example conceptual filter:
@Provider
public class CorrelationFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) {
String correlationId = requestContext.getHeaderString("X-Correlation-ID");
CorrelationContext.set(normalizeOrGenerate(correlationId));
}
}
Remember to clear ThreadLocal/MDC correctly. This connects to Part 033 and Part 055.
9. Authorization Header
Avoid injecting Authorization header directly into business methods unless implementing low-level auth infrastructure.
Bad pattern:
public QuoteResponse getQuote(
@HeaderParam("Authorization") String authorization,
@PathParam("quoteId") String quoteId
) {
String userId = tokenParser.parse(authorization).userId();
return quoteService.getQuote(userId, quoteId);
}
Better pattern:
Auth filter validates token
-> creates principal/security context
-> resource/service uses authenticated identity abstraction
JAX-RS has SecurityContext:
@GET
@Path("/quotes/{quoteId}")
public QuoteResponse getQuote(
@Context SecurityContext securityContext,
@PathParam("quoteId") String quoteId
) {
Principal principal = securityContext.getUserPrincipal();
return quoteService.getQuote(principal.getName(), quoteId);
}
Actual internal pattern may differ. Verify security framework and gateway responsibilities.
10. Tenant Header Risk
Tenant ID is dangerous if treated as just another untrusted header.
Bad assumption:
X-Tenant-ID header says tenant A, so request belongs to tenant A.
Safer model:
Authenticated identity/service credential
+ allowed tenant claims/permissions
+ requested tenant context
-> validated tenant context
Tenant-aware input flow:
Part 040 and Part 060 cover tenancy and security in more depth.
For this part, the key is simple:
Do not let raw tenant input flow into data access without validation.
11. Cookie Parameters
JAX-RS supports cookie binding:
@GET
@Path("/session")
public SessionResponse getSession(@CookieParam("sessionId") String sessionId) {
return sessionService.getSession(sessionId);
}
Cookies are common for browser-facing APIs, less common for service-to-service APIs.
Risks:
- CSRF exposure if browser credential automatically sent
- SameSite/Secure/HttpOnly misconfiguration
- session fixation
- accidental statefulness
- hidden dependency on browser behavior
For backend service APIs, bearer token/service identity is usually clearer.
Internal verification:
- Are cookies used at all?
- Are APIs browser-facing or service-to-service?
- Is CSRF protection required?
- Are cookie flags enforced by app or gateway?
12. Form Parameters
JAX-RS can bind form fields:
@POST
@Path("/login")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public LoginResponse login(
@FormParam("username") String username,
@FormParam("password") String password
) {
return authService.login(username, password);
}
Form params are encoded in request body with content type:
Content-Type: application/x-www-form-urlencoded
username=alice&password=secret
For modern internal APIs, JSON request bodies are usually clearer for structured commands.
Forms may still appear in:
- OAuth2 token endpoints
- legacy integrations
- browser form submissions
- simple admin endpoints
Avoid mixing JSON body and form params in the same operation unless framework behavior is explicitly understood.
13. BeanParam for Grouped Inputs
JAX-RS supports grouping parameters into a bean using @BeanParam.
Example:
public class QuoteSearchParams {
@QueryParam("customerId")
private String customerId;
@QueryParam("status")
private List<String> statuses;
@QueryParam("limit")
@DefaultValue("50")
private int limit;
@QueryParam("cursor")
private String cursor;
// getters/setters
}
Resource:
@GET
@Path("/quotes")
public QuoteSearchResponse searchQuotes(@BeanParam QuoteSearchParams params) {
return quoteQueryService.search(params);
}
Pros:
- avoids long method signatures
- groups related input
- reusable across endpoints if appropriate
- easier validation composition
Risks:
- hides which params endpoint accepts
- reused bean can accidentally widen API surface
- defaults scattered across fields
- validation becomes less visible
Use @BeanParam for cohesive input groups, not as dumping ground.
14. MatrixParam: Usually Avoid Unless Standard Internally
JAX-RS supports matrix params in URI path segments:
/catalogs;version=2026-01/products;status=active
Example:
@Path("/catalogs")
public class CatalogResource {
@GET
@Path("{catalogId}")
public CatalogResponse getCatalog(
@PathParam("catalogId") String catalogId,
@MatrixParam("version") String version
) { ... }
}
Most enterprise HTTP APIs do not use matrix params because:
- many developers are unfamiliar with them
- proxies/gateways may normalize semicolons
- generated clients may not support them cleanly
- API docs become less intuitive
Recommendation:
Avoid matrix params unless existing internal API standard explicitly uses them.
15. ParamConverter and Custom Conversion
JAX-RS supports custom conversion via ParamConverterProvider.
Use case:
- custom ID type
- domain enum parsing
- date/effective date parsing
- comma-separated value parsing
Example conceptual converter:
@Provider
public class QuoteIdParamConverterProvider implements ParamConverterProvider {
@Override
public <T> ParamConverter<T> getConverter(
Class<T> rawType,
Type genericType,
Annotation[] annotations
) {
if (rawType.equals(QuoteId.class)) {
return (ParamConverter<T>) new QuoteIdConverter();
}
return null;
}
}
Risks:
- conversion failure becomes framework-level error
- error response may be generic unless mapped
- hidden parsing logic makes endpoint behavior less obvious
- converter applies broadly if not scoped carefully
Senior review question:
Is custom conversion improving clarity, or hiding input rules?
16. Enum Binding
Enum query/path params are convenient:
@QueryParam("status") QuoteStatus status
But strict enum parsing can create bad client experience:
GET /quotes?status=draft
might fail if enum requires DRAFT.
Options:
- require exact enum string and document it
- normalize case in custom converter
- accept string and validate manually
For public/enterprise APIs, explicit validation often gives better error messages.
@QueryParam("status") String status
Then:
QuoteStatus parsed = QuoteStatusParser.parse(status)
.orElseThrow(() -> new InvalidRequestException("INVALID_QUOTE_STATUS"));
This allows controlled error response.
17. Date/Time Input
Date/time input must be explicit.
Bad:
?date=07/10/26
Better:
?effectiveDate=2026-07-10
?createdAfter=2026-07-10T00:00:00Z
Use distinct types:
LocalDate for date without time
Instant/OffsetDateTime for timestamp
But direct binding depends on provider and version.
Safer approach for critical APIs:
@QueryParam("effectiveDate") String effectiveDateRaw
Then parse explicitly:
LocalDate effectiveDate = LocalDate.parse(effectiveDateRaw, DateTimeFormatter.ISO_LOCAL_DATE);
Part 051 covers temporal correctness in depth.
18. Canonicalization
Input canonicalization means converting semantically equivalent inputs to one internal representation.
Examples:
trim whitespace
normalize case for case-insensitive IDs if allowed
normalize Unicode if relevant
canonicalize currency code to uppercase ISO form
parse date to LocalDate/Instant
sort/deduplicate repeated filters
Do not over-canonicalize identifiers if case sensitivity matters.
Example risk:
Product code "abc" and "ABC" may be different in legacy catalog.
Internal verification:
- Are IDs case-sensitive?
- Are product/catalog codes normalized?
- Are tenant/customer identifiers canonicalized?
- Who owns normalization rules?
19. Validation Placement
Input validation has layers.
Transport validation:
Is param syntactically valid?
API validation:
Is request shape allowed?
Domain validation:
Is operation valid under business rules?
Authorization validation:
Is caller allowed to perform action?
Example:
quoteId format invalid
-> 400 Bad Request
quoteId valid format but not found
-> 404 Not Found
quote exists but caller cannot access it
-> 403 Forbidden or 404 depending security policy
quote exists but cannot be submitted in current status
-> 409 Conflict or domain-specific error
Do not collapse all failures into 400 or 500.
20. Input DTO vs Parameter List
For simple GET:
public QuoteResponse getQuote(@PathParam("quoteId") String quoteId)
For complex query:
public QuoteSearchResponse searchQuotes(@BeanParam QuoteSearchParams params)
For command POST:
public QuoteResponse createQuote(CreateQuoteRequest request)
Rule of thumb:
Path/query/header params are good for addressing and retrieval modifiers.
Request body DTO is better for complex command payload.
Bad:
POST /quotes?customerId=C-1&productId=P-1&price=100¤cy=USD&effectiveDate=2026-07-10
Better:
POST /quotes
Content-Type: application/json
{
"customerId": "C-1",
"items": [
{ "productId": "P-1", "quantity": 1 }
],
"effectiveDate": "2026-07-10"
}
21. Request Context Objects
JAX-RS supports context injection:
@Context UriInfo uriInfo
@Context HttpHeaders headers
@Context Request request
@Context SecurityContext securityContext
Example:
@GET
@Path("/quotes")
public QuoteSearchResponse searchQuotes(@Context UriInfo uriInfo) {
MultivaluedMap<String, String> query = uriInfo.getQueryParameters();
return quoteSearchService.search(query);
}
This is flexible but can weaken contract visibility.
Prefer explicit parameters for stable API inputs.
Use context objects for:
- generic infrastructure behavior
- dynamic query parsing
- links/base URI construction
- conditional request handling
- security context
Do not pass raw UriInfo deep into domain logic.
22. Header Strategy and Context Propagation
Some headers should be propagated downstream.
Examples:
- traceparent
- tracestate
- X-Correlation-ID
- X-Request-ID
- X-Tenant-ID after validation
- baggage if policy allows
But not all inbound headers should be blindly propagated.
Dangerous headers:
- Authorization from external caller to internal service without token exchange policy
- spoofed identity headers
- user-controlled tenant headers
- hop-by-hop headers
- oversized/custom headers
Senior-level rule:
Normalize and whitelist propagation. Never forward arbitrary inbound headers blindly.
23. Size Limits
Input is not only semantic. It consumes resources.
Check limits for:
- max URL length
- max header size
- max query param count
- max body size
- max form size
- max multipart size
- max array length
- max page size
These limits may exist in:
- browser/client
- API gateway
- load balancer
- servlet container
- Jersey/runtime
- application validation
If limits differ, failure can happen before JAX-RS code sees the request.
24. Failure Modes
24.1 Missing required param
Symptoms:
- null value passed to service
- unexpected 500 NullPointerException
- generic 400 from framework
Prevention:
- explicit validation
- Bean Validation
- clear error mapper
- endpoint tests for missing param
24.2 Invalid type conversion
Symptoms:
- 400 with poor message
- 404 if path no longer matches
- generic server error if converter throws incorrectly
Prevention:
- parse manually for critical inputs
- custom error handling
- documented format
24.3 Header spoofing
Symptoms:
- tenant leak
- incorrect audit identity
- wrong correlation chain
- unauthorized access if trusted header accepted
Prevention:
- trust boundary at gateway/auth layer
- signed tokens/claims
- strip untrusted headers
- validate tenant against identity
24.4 Query explosion
Symptoms:
- slow DB query
- high CPU from parsing/filtering
- large memory usage
- timeout
Prevention:
- max limit
- filter whitelist
- index-aligned filters
- query complexity limits
24.5 Default value change
Symptoms:
- client sees different result without changing request
- paging behavior changes
- pricing/catalog effective date changes
Prevention:
- treat default values as contract
- document defaults in OpenAPI
- compatibility review before changing defaults
25. Debugging Checklist
When input binding behaves unexpectedly:
1. Capture raw HTTP request.
2. Confirm path after gateway/proxy rewrite.
3. Confirm query string exactly.
4. Confirm headers after gateway normalization.
5. Confirm content type.
6. Confirm JAX-RS resource path and method annotations.
7. Confirm `@PathParam`, `@QueryParam`, `@HeaderParam` names match exactly.
8. Confirm default values.
9. Confirm type conversion behavior.
10. Confirm filters/interceptors mutate request or context.
11. Confirm validation layer and exception mapper.
12. Confirm OpenAPI contract matches implementation.
For production debugging, compare:
client raw request
vs gateway access log
vs service access log
vs resource-level debug log
Differences reveal where the request changed.
26. JAX-RS Examples
26.1 Simple resource lookup
@GET
@Path("/quotes/{quoteId}")
@Produces(MediaType.APPLICATION_JSON)
public QuoteResponse getQuote(@PathParam("quoteId") String quoteId) {
QuoteId id = QuoteId.parse(quoteId)
.orElseThrow(() -> InvalidRequestException.of("INVALID_QUOTE_ID"));
return quoteApplicationService.getQuote(id);
}
26.2 Search endpoint with query params
@GET
@Path("/quotes")
@Produces(MediaType.APPLICATION_JSON)
public QuoteSearchResponse searchQuotes(
@QueryParam("customerId") String customerId,
@QueryParam("status") List<String> statuses,
@QueryParam("limit") @DefaultValue("50") int limit,
@QueryParam("cursor") String cursor
) {
QuoteSearchCriteria criteria = QuoteSearchCriteria.builder()
.customerId(customerId)
.statuses(QuoteStatusParser.parseAll(statuses))
.limit(limit)
.cursor(cursor)
.build();
return quoteQueryService.search(criteria);
}
26.3 Header through context/filter instead of method pollution
@Provider
public class TenantContextFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) {
String requestedTenant = requestContext.getHeaderString("X-Tenant-ID");
TenantContext tenantContext = tenantResolver.resolveAndAuthorize(requestedTenant);
requestContext.setProperty("tenantContext", tenantContext);
}
}
Resource:
@GET
@Path("/quotes/{quoteId}")
public QuoteResponse getQuote(
@PathParam("quoteId") String quoteId,
@Context ContainerRequestContext requestContext
) {
TenantContext tenant = (TenantContext) requestContext.getProperty("tenantContext");
return quoteService.getQuote(tenant, quoteId);
}
This is conceptual. Actual internal code may use CDI, HK2, request-scoped context, ThreadLocal, or security context. Verify.
27. Internal Verification Checklist
For CSG/internal codebase, verify:
JAX-RS binding:
- Are params mostly injected directly or grouped with @BeanParam?
- Are custom ParamConverterProvider classes used?
- Are Optional<T> params supported and used?
- Are enums bound directly or parsed manually?
- How are conversion errors mapped?
Headers:
- What correlation/request headers are standard?
- Is tenant ID passed via header, token claim, path, or derived context?
- Which headers are trusted from gateway?
- Which headers are stripped/replaced by gateway?
- Is Authorization handled in app, gateway, or both?
Forms/cookies:
- Are form params used anywhere?
- Are cookies used for browser-facing APIs?
- Is CSRF relevant?
Validation:
- Are missing params validated manually or by Bean Validation?
- Are error codes standardized?
- Are default values documented in OpenAPI?
Limits:
- Max URL length?
- Max header size?
- Max query param count?
- Max body/form size?
- Gateway and container limits aligned?
Tenancy:
- How is tenant context resolved?
- Is tenant context validated against identity?
- Is tenant context propagated to DB/log/Kafka?
28. PR Review Checklist
Before approving request input changes:
Contract:
- Input location is appropriate: path vs query vs header vs body.
- Param names follow standard.
- Required/optional/default behavior is explicit.
- OpenAPI is updated.
- Examples are updated.
Correctness:
- Type conversion is safe.
- Invalid input returns controlled error.
- Defaults are intentional.
- Date/time/currency parsing is explicit if relevant.
- Tenant/security inputs are not trusted blindly.
Performance:
- Query params cannot create unbounded DB queries.
- Limit/page size enforced.
- Large headers/forms are bounded.
Security:
- Authorization header handled by security layer.
- Identity/tenant headers validated.
- Sensitive input is not logged.
- Header propagation is whitelisted.
Testing:
- Missing param test exists.
- Invalid type/enum test exists.
- Boundary limit test exists.
- Default behavior test exists.
- Negative security/tenant test exists where relevant.
29. Anti-Patterns
- Passing raw query map into domain logic.
- Using GET query params for complex command payload.
- Trusting tenant header without identity validation.
- Parsing Authorization inside every resource method.
- Changing default values without compatibility review.
- Binding enum directly and returning generic framework error.
- Allowing unbounded `limit`.
- Accepting arbitrary sort/filter fields.
- Blindly propagating all inbound headers downstream.
- Logging raw headers with secrets/PII.
- Reusing @BeanParam across unrelated endpoints until API surface becomes unclear.
30. Key Takeaways
- Request input handling is a boundary design problem, not just annotation usage.
- Path params identify resources; query params modify retrieval.
- Headers carry metadata and must respect trust boundaries.
- Tenant/security headers are dangerous if accepted raw.
- Defaults are part of the API contract.
- Type conversion failure should produce controlled, documented errors.
- Complex command input usually belongs in request body DTO, not query params.
- Context propagation should be normalized and whitelisted.
- Senior PR review must evaluate correctness, security, performance, and compatibility of input changes.
Next part: response model, including body shape, metadata, pagination response, links, empty response, and backward-compatible response evolution.
You just completed lesson 26 in build core. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.
Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.