docs
reference
tessl install tessl/maven-io-quarkus--quarkus-rest@3.15.0A Jakarta REST implementation utilizing build time processing and Vert.x for high-performance REST endpoints with reactive programming support, security integration, and cloud-native features.
Quarkus REST provides declarative annotations for setting HTTP response status codes and headers directly on resource methods, eliminating the need to return Response or RestResponse objects for simple cases.
Declaratively set HTTP response status codes on resource methods.
package org.jboss.resteasy.reactive;
/**
* Sets the HTTP status code for successful resource method execution.
* Only applies when the method completes without exception and
* does not return Response or RestResponse.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface ResponseStatus {
/** The HTTP status code to return */
int value();
}Usage Examples:
import org.jboss.resteasy.reactive.ResponseStatus;
import jakarta.ws.rs.*;
@Path("/users")
public class UserResource {
// Return 201 Created instead of default 200 OK
@POST
@ResponseStatus(201)
public User createUser(User user) {
return userService.create(user);
}
// Return 204 No Content for delete
@DELETE
@Path("/{id}")
@ResponseStatus(204)
public void deleteUser(@PathParam("id") Long id) {
userService.delete(id);
}
// Return 202 Accepted for async processing
@POST
@Path("/{id}/process")
@ResponseStatus(202)
public String processUser(@PathParam("id") Long id) {
asyncProcessor.submit(id);
return "Processing started";
}
// Return 200 OK (default, annotation not needed)
@GET
@Path("/{id}")
public User getUser(@PathParam("id") Long id) {
return userService.find(id);
}
}Declaratively add HTTP response headers to resource methods.
package org.jboss.resteasy.reactive;
/**
* Adds specified HTTP response headers when method completes successfully.
* Only applies when the method completes without exception and
* does not return Response or RestResponse.
*
* Do not use for Content-Type or Content-Length headers,
* as those are set automatically by Quarkus REST.
*
* Repeatable annotation - use multiple @ResponseHeader annotations
* or @ResponseHeader.List for multiple headers.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(ResponseHeader.List.class)
@interface ResponseHeader {
/** The header name */
String name();
/** The header value(s) */
String[] value();
/** Container annotation for multiple @ResponseHeader annotations */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface List {
ResponseHeader[] value();
}
}Usage Examples:
import org.jboss.resteasy.reactive.ResponseHeader;
import jakarta.ws.rs.*;
@Path("/api")
public class ApiResource {
// Single header
@GET
@Path("/data")
@ResponseHeader(name = "X-API-Version", value = "1.0")
public Data getData() {
return dataService.load();
}
// Multiple values for same header
@GET
@Path("/info")
@ResponseHeader(name = "X-Custom-Header", value = {"value1", "value2"})
public Info getInfo() {
return infoService.load();
}
// Multiple different headers (repeatable annotation)
@GET
@Path("/report")
@ResponseHeader(name = "X-Report-Version", value = "2.0")
@ResponseHeader(name = "X-Generated-By", value = "Quarkus REST")
@ResponseHeader(name = "X-Cache-Status", value = "MISS")
public Report getReport() {
return reportService.generate();
}
// Cache control headers
@GET
@Path("/static-content")
@ResponseHeader(name = "Cache-Control", value = "max-age=3600, public")
@ResponseHeader(name = "X-Content-Source", value = "database")
public Content getStaticContent() {
return contentService.getStatic();
}
// CORS headers
@GET
@Path("/public-data")
@ResponseHeader(name = "Access-Control-Allow-Origin", value = "*")
@ResponseHeader(name = "Access-Control-Allow-Methods", value = "GET, OPTIONS")
public PublicData getPublicData() {
return publicDataService.load();
}
// Security headers
@GET
@Path("/secure")
@ResponseHeader(name = "X-Frame-Options", value = "DENY")
@ResponseHeader(name = "X-Content-Type-Options", value = "nosniff")
@ResponseHeader(name = "X-XSS-Protection", value = "1; mode=block")
public SecureData getSecureData() {
return secureDataService.load();
}
}Use both annotations together for complete response configuration.
import org.jboss.resteasy.reactive.ResponseStatus;
import org.jboss.resteasy.reactive.ResponseHeader;
@Path("/resources")
public class ResourceEndpoint {
// Created (201) with Location header
@POST
@ResponseStatus(201)
@ResponseHeader(name = "Location", value = "/resources/{id}")
public Resource createResource(Resource resource) {
Resource created = resourceService.create(resource);
// Location header will use placeholder {id} from path
return created;
}
// Accepted (202) with custom tracking header
@POST
@Path("/batch")
@ResponseStatus(202)
@ResponseHeader(name = "X-Batch-ID", value = "${batch.id}")
@ResponseHeader(name = "X-Processing-Status", value = "queued")
public BatchRequest submitBatch(BatchRequest request) {
return batchService.submit(request);
}
// No Content (204) with operation result header
@PUT
@Path("/{id}")
@ResponseStatus(204)
@ResponseHeader(name = "X-Updated-At", value = "${timestamp}")
public void updateResource(@PathParam("id") Long id, Resource resource) {
resourceService.update(id, resource);
}
}The @ResponseStatus and @ResponseHeader annotations are ignored in these cases:
Response: Full control via Response.ResponseBuilderRestResponse: Full control via RestResponse.ResponseBuilder@GET
@Path("/conditional")
@ResponseStatus(201) // IGNORED - method returns Response
@ResponseHeader(name = "X-Custom", value = "value") // IGNORED
public Response conditionalResponse() {
// Annotations ignored because returning Response
return Response.ok()
.status(200) // This takes precedence
.header("X-Custom", "different-value") // This takes precedence
.build();
}
@GET
@Path("/error-case")
@ResponseStatus(200) // IGNORED if exception thrown
@ResponseHeader(name = "X-Success", value = "true") // IGNORED if exception thrown
public Data getData() {
if (someError) {
// Annotations ignored, exception mapper handles response
throw new ServiceException("Data not available");
}
return data; // Annotations apply here
}Do not use @ResponseHeader for:
@Produces annotation instead// INCORRECT - Don't do this
@GET
@ResponseHeader(name = "Content-Type", value = "application/json")
public Data getData() { ... }
// CORRECT - Use @Produces instead
@GET
@Produces(MediaType.APPLICATION_JSON)
public Data getData() { ... }Declarative annotations vs programmatic Response building:
Declarative Approach (Simple, concise):
@POST
@ResponseStatus(201)
@ResponseHeader(name = "X-Request-ID", value = "${request.id}")
public User createUser(User user) {
return userService.create(user);
}Programmatic Approach (More control):
@POST
public Response createUser(User user) {
User created = userService.create(user);
return Response.status(201)
.entity(created)
.header("X-Request-ID", UUID.randomUUID().toString())
.build();
}When to use declarative annotations:
When to use Response/RestResponse:
Declarative response annotations work seamlessly with:
@Cache and @NoCache annotations (see HTTP Caching)@Produces determines Content-Type, not @ResponseHeader@GET
@Path("/cached")
@ResponseStatus(200)
@ResponseHeader(name = "X-Data-Source", value = "database")
@Cache(maxAge = 3600) // Works together with @ResponseHeader
@RolesAllowed("user") // Security checked first
@Produces(MediaType.APPLICATION_JSON) // Content-Type set automatically
public Data getCachedData() {
return dataService.load();
}