Java annotations for defining Swagger API documentation and OpenAPI specification metadata
—
Annotations for documenting API responses, status codes, and response headers. These annotations provide detailed metadata about what clients can expect when calling API endpoints, including success and error scenarios.
Documents a single response from an operation, including the response code, message, and return type.
/**
* Documents a single response from an operation
* Target: METHOD, TYPE
* Retention: RUNTIME
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface ApiResponse {
/**
* Response code (HTTP status code)
* REQUIRED ATTRIBUTE
* Should use formal HTTP Status Code definitions
*/
int code();
/**
* Human-readable response message
* REQUIRED ATTRIBUTE
* Brief description of what this response means
*/
String message();
/**
* Optional response class
* Describes the type of response body
* Default Void.class means no response body
*/
Class<?> response() default Void.class;
/**
* Specifies a reference to response type definition
* Can be local or remote reference
* Overrides response() class if specified
*/
String reference() default "";
/**
* List of possible headers for this response
*/
ResponseHeader[] responseHeaders() default @ResponseHeader(name = "", response = Void.class);
/**
* Declares container wrapping the response
* Valid values: "List", "Set", "Map"
* Other values are ignored
*/
String responseContainer() default "";
}Usage Examples:
// Single response documentation
@ApiResponse(code = 200, message = "User found", response = User.class)
@GET
@Path("/{id}")
public User getUser(@PathParam("id") Long id) {
// implementation
}
// Response with headers
@ApiResponse(
code = 201,
message = "User created successfully",
response = User.class,
responseHeaders = {
@ResponseHeader(
name = "Location",
description = "URL of the created user",
response = String.class
)
}
)
@POST
public Response createUser(User user) {
// implementation
}
// Collection response
@ApiResponse(
code = 200,
message = "Users retrieved successfully",
response = User.class,
responseContainer = "List"
)
@GET
public List<User> getAllUsers() {
// implementation
}Container annotation for documenting multiple possible responses from a single operation.
/**
* Container for multiple @ApiResponse annotations
* Target: ANNOTATION_TYPE, METHOD, TYPE
* Retention: RUNTIME
*/
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface ApiResponses {
/** Array of response definitions */
ApiResponse[] value();
}Usage Examples:
// Multiple response scenarios
@ApiResponses({
@ApiResponse(code = 200, message = "User found", response = User.class),
@ApiResponse(code = 404, message = "User not found"),
@ApiResponse(code = 500, message = "Internal server error")
})
@GET
@Path("/{id}")
public Response getUser(@PathParam("id") Long id) {
// implementation
}
// CRUD operation with full response documentation
@ApiResponses({
@ApiResponse(
code = 201,
message = "User created successfully",
response = User.class,
responseHeaders = @ResponseHeader(name = "Location", response = String.class)
),
@ApiResponse(code = 400, message = "Invalid user data", response = ErrorResponse.class),
@ApiResponse(code = 409, message = "User already exists", response = ErrorResponse.class),
@ApiResponse(code = 500, message = "Internal server error", response = ErrorResponse.class)
})
@POST
public Response createUser(User user) {
// implementation
}
// Search operation with various outcomes
@ApiResponses({
@ApiResponse(
code = 200,
message = "Search completed successfully",
response = User.class,
responseContainer = "List"
),
@ApiResponse(code = 204, message = "No users found matching criteria"),
@ApiResponse(code = 400, message = "Invalid search parameters", response = ErrorResponse.class),
@ApiResponse(code = 429, message = "Rate limit exceeded",
responseHeaders = @ResponseHeader(name = "Retry-After", response = Integer.class))
})
@GET
@Path("/search")
public Response searchUsers(
@QueryParam("q") String query,
@QueryParam("limit") Integer limit
) {
// implementation
}Documents headers that may be returned with a response.
/**
* Documents a response header
* Target: ANNOTATION_TYPE (used within other annotations)
* Retention: RUNTIME
*/
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface ResponseHeader {
/**
* Name of the response header
* REQUIRED ATTRIBUTE
*/
String name();
/** Description of the header */
String description() default "";
/**
* Response type/class for the header value
* REQUIRED ATTRIBUTE
*/
Class<?> response();
/**
* Container type for the header if it contains multiple values
* Valid values: "List", "Set", "Map"
*/
String responseContainer() default "";
}Usage Examples:
// Authentication response with token in header
@ApiResponse(
code = 200,
message = "Login successful",
response = User.class,
responseHeaders = {
@ResponseHeader(
name = "Authorization",
description = "JWT token for subsequent requests",
response = String.class
),
@ResponseHeader(
name = "X-Token-Expires",
description = "Token expiration timestamp",
response = Long.class
)
}
)
@POST
@Path("/login")
public Response login(LoginRequest request) {
// implementation
}
// File download with metadata headers
@ApiResponse(
code = 200,
message = "File downloaded successfully",
responseHeaders = {
@ResponseHeader(
name = "Content-Type",
description = "MIME type of the file",
response = String.class
),
@ResponseHeader(
name = "Content-Length",
description = "Size of file in bytes",
response = Long.class
),
@ResponseHeader(
name = "Content-Disposition",
description = "Attachment filename",
response = String.class
),
@ResponseHeader(
name = "Last-Modified",
description = "File modification timestamp",
response = String.class
)
}
)
@GET
@Path("/files/{id}/download")
public Response downloadFile(@PathParam("id") String fileId) {
// implementation
}
// Pagination headers
@ApiResponse(
code = 200,
message = "Page retrieved successfully",
response = User.class,
responseContainer = "List",
responseHeaders = {
@ResponseHeader(
name = "X-Total-Count",
description = "Total number of items available",
response = Integer.class
),
@ResponseHeader(
name = "X-Page-Count",
description = "Total number of pages",
response = Integer.class
),
@ResponseHeader(
name = "Link",
description = "Links to first, last, next, and previous pages",
response = String.class
)
}
)
@GET
public Response getUsers(
@QueryParam("page") @DefaultValue("1") Integer page,
@QueryParam("size") @DefaultValue("20") Integer size
) {
// implementation
}@Path("/users")
public class UserController {
// GET collection
@ApiResponses({
@ApiResponse(
code = 200,
message = "Users retrieved successfully",
response = User.class,
responseContainer = "List",
responseHeaders = @ResponseHeader(name = "X-Total-Count", response = Integer.class)
),
@ApiResponse(code = 204, message = "No users found")
})
@GET
public Response getAllUsers() {
// implementation
}
// GET single resource
@ApiResponses({
@ApiResponse(code = 200, message = "User found", response = User.class),
@ApiResponse(code = 404, message = "User not found", response = ErrorResponse.class)
})
@GET
@Path("/{id}")
public Response getUser(@PathParam("id") Long id) {
// implementation
}
// POST create
@ApiResponses({
@ApiResponse(
code = 201,
message = "User created successfully",
response = User.class,
responseHeaders = @ResponseHeader(name = "Location", response = String.class)
),
@ApiResponse(code = 400, message = "Invalid user data", response = ErrorResponse.class),
@ApiResponse(code = 409, message = "User already exists", response = ErrorResponse.class)
})
@POST
public Response createUser(User user) {
// implementation
}
// PUT update
@ApiResponses({
@ApiResponse(code = 200, message = "User updated successfully", response = User.class),
@ApiResponse(code = 404, message = "User not found", response = ErrorResponse.class),
@ApiResponse(code = 400, message = "Invalid user data", response = ErrorResponse.class)
})
@PUT
@Path("/{id}")
public Response updateUser(@PathParam("id") Long id, User user) {
// implementation
}
// DELETE
@ApiResponses({
@ApiResponse(code = 204, message = "User deleted successfully"),
@ApiResponse(code = 404, message = "User not found", response = ErrorResponse.class),
@ApiResponse(code = 409, message = "Cannot delete user with active dependencies", response = ErrorResponse.class)
})
@DELETE
@Path("/{id}")
public Response deleteUser(@PathParam("id") Long id) {
// implementation
}
}@ApiModel(description = "Standard error response")
public class ErrorResponse {
@ApiModelProperty(value = "Error code", required = true, example = "VALIDATION_ERROR")
private String code;
@ApiModelProperty(value = "Human-readable error message", required = true, example = "Invalid input data")
private String message;
@ApiModelProperty(value = "Request timestamp", example = "2023-01-15T10:30:00Z")
private LocalDateTime timestamp;
@ApiModelProperty(value = "Request path", example = "/api/users")
private String path;
@ApiModelProperty(value = "Validation errors")
private List<FieldError> fieldErrors;
}
@ApiModel(description = "Field validation error")
public class FieldError {
@ApiModelProperty(value = "Field name", example = "email")
private String field;
@ApiModelProperty(value = "Rejected value", example = "invalid-email")
private Object rejectedValue;
@ApiModelProperty(value = "Error message", example = "Must be a valid email address")
private String message;
}// Async operation initiation
@ApiResponses({
@ApiResponse(
code = 202,
message = "Processing started",
response = JobResponse.class,
responseHeaders = @ResponseHeader(name = "Location", description = "Job status URL", response = String.class)
),
@ApiResponse(code = 400, message = "Invalid request", response = ErrorResponse.class)
})
@POST
@Path("/bulk-import")
public Response startBulkImport(BulkImportRequest request) {
// implementation
}
// Job status checking
@ApiResponses({
@ApiResponse(code = 200, message = "Job status retrieved", response = JobStatus.class),
@ApiResponse(code = 404, message = "Job not found", response = ErrorResponse.class)
})
@GET
@Path("/jobs/{jobId}")
public Response getJobStatus(@PathParam("jobId") String jobId) {
// implementation
}
@ApiModel(description = "Background job response")
public class JobResponse {
@ApiModelProperty(value = "Job identifier", example = "job-12345")
private String jobId;
@ApiModelProperty(value = "Job status URL", example = "/api/jobs/job-12345")
private String statusUrl;
@ApiModelProperty(value = "Estimated completion time", example = "2023-01-15T11:00:00Z")
private LocalDateTime estimatedCompletion;
}// File upload
@ApiResponses({
@ApiResponse(
code = 201,
message = "File uploaded successfully",
response = FileInfo.class,
responseHeaders = {
@ResponseHeader(name = "Location", description = "File URL", response = String.class),
@ResponseHeader(name = "ETag", description = "File hash", response = String.class)
}
),
@ApiResponse(code = 400, message = "Invalid file", response = ErrorResponse.class),
@ApiResponse(code = 413, message = "File too large", response = ErrorResponse.class),
@ApiResponse(code = 415, message = "Unsupported file type", response = ErrorResponse.class)
})
@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(
@FormDataParam("file") InputStream fileInputStream,
@FormDataParam("file") FormDataContentDisposition fileDetail
) {
// implementation
}
// File download
@ApiResponses({
@ApiResponse(
code = 200,
message = "File content",
responseHeaders = {
@ResponseHeader(name = "Content-Type", response = String.class),
@ResponseHeader(name = "Content-Length", response = Long.class),
@ResponseHeader(name = "Content-Disposition", response = String.class)
}
),
@ApiResponse(code = 404, message = "File not found", response = ErrorResponse.class),
@ApiResponse(code = 403, message = "Access denied", response = ErrorResponse.class)
})
@GET
@Path("/files/{id}")
public Response downloadFile(@PathParam("id") String fileId) {
// implementation
}Install with Tessl CLI
npx tessl i tessl/maven-io-swagger--swagger-annotations