Jakarta EE compatible OpenAPI 3.x annotations for defining REST API specifications
—
Define relationships between operations and enable workflow documentation with parameter passing between linked operations. This system provides comprehensive support for documenting API workflows, operation chains, and parameter flow between related endpoints.
Defines links between operations to describe workflows and parameter passing patterns.
/**
* Defines link to related operation
* Applied to: within response definitions
*/
@Link(
name = "getUserById", // Link name (required)
operationRef = "#/paths/~1users~1{userId}/get", // Reference to operation path
operationId = "getUserById", // Operation ID reference (preferred)
parameters = { // Parameters to pass to linked operation
@LinkParameter(name = "userId", expression = "$response.body#/id"),
@LinkParameter(name = "include", expression = "$request.query.include")
},
requestBody = "$response.body#/userDetails", // Request body for linked operation
description = "Get the created user details", // Link description
server = @Server( // Alternative server for link
url = "https://users-api.example.com",
description = "User management server"
),
extensions = {@Extension(...)}, // Custom extensions
ref = "#/components/links/GetUserById" // Reference to component link
)Usage Examples:
// User CRUD workflow links
@POST
@Path("/users")
@Operation(operationId = "createUser", summary = "Create new user")
@ApiResponse(
responseCode = "201",
description = "User created successfully",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = User.class)
),
headers = @Header(
name = "Location",
description = "URL of created user",
schema = @Schema(type = "string", format = "uri")
),
links = {
@Link(
name = "GetUser",
description = "Retrieve the created user",
operationId = "getUserById",
parameters = @LinkParameter(name = "id", expression = "$response.body#/id")
),
@Link(
name = "UpdateUser",
description = "Update the created user",
operationId = "updateUser",
parameters = @LinkParameter(name = "id", expression = "$response.body#/id")
),
@Link(
name = "DeleteUser",
description = "Delete the created user",
operationId = "deleteUser",
parameters = @LinkParameter(name = "id", expression = "$response.body#/id")
),
@Link(
name = "GetUserProfile",
description = "Get user profile",
operationId = "getUserProfile",
parameters = @LinkParameter(name = "userId", expression = "$response.body#/id")
)
}
)
public Response createUser(@RequestBody CreateUserRequest request) {}
@GET
@Path("/users/{id}")
@Operation(operationId = "getUserById", summary = "Get user by ID")
@ApiResponse(
responseCode = "200",
description = "User retrieved successfully",
content = @Content(schema = @Schema(implementation = User.class)),
links = {
@Link(
name = "GetUserPosts",
description = "Get posts authored by this user",
operationId = "getPostsByAuthor",
parameters = @LinkParameter(name = "authorId", expression = "$response.body#/id")
),
@Link(
name = "GetUserComments",
description = "Get comments by this user",
operationId = "getCommentsByUser",
parameters = @LinkParameter(name = "userId", expression = "$response.body#/id")
),
@Link(
name = "GetUserFollowers",
description = "Get user's followers",
operationId = "getUserFollowers",
parameters = @LinkParameter(name = "userId", expression = "$response.body#/id")
)
}
)
public Response getUserById(@PathParam("id") Long id) {}
// E-commerce order workflow
@POST
@Path("/orders")
@Operation(operationId = "createOrder", summary = "Create new order")
@ApiResponse(
responseCode = "201",
description = "Order created successfully",
content = @Content(schema = @Schema(implementation = Order.class)),
links = {
@Link(
name = "GetOrder",
description = "Retrieve the created order",
operationId = "getOrderById",
parameters = @LinkParameter(name = "orderId", expression = "$response.body#/id")
),
@Link(
name = "PayOrder",
description = "Process payment for the order",
operationId = "processPayment",
parameters = @LinkParameter(name = "orderId", expression = "$response.body#/id"),
requestBody = "$response.body#/paymentDetails"
),
@Link(
name = "CancelOrder",
description = "Cancel the order if still pending",
operationId = "cancelOrder",
parameters = @LinkParameter(name = "orderId", expression = "$response.body#/id")
),
@Link(
name = "TrackOrder",
description = "Track order shipment status",
operationId = "trackOrder",
parameters = @LinkParameter(name = "orderId", expression = "$response.body#/id")
)
}
)
public Response createOrder(@RequestBody CreateOrderRequest request) {}
// Payment processing workflow
@POST
@Path("/payments")
@Operation(operationId = "processPayment", summary = "Process payment")
@ApiResponse(
responseCode = "200",
description = "Payment processed",
content = @Content(schema = @Schema(implementation = PaymentResult.class)),
links = {
@Link(
name = "GetPaymentStatus",
description = "Check payment status",
operationId = "getPaymentStatus",
parameters = @LinkParameter(name = "paymentId", expression = "$response.body#/paymentId")
),
@Link(
name = "RefundPayment",
description = "Refund this payment",
operationId = "refundPayment",
parameters = @LinkParameter(name = "paymentId", expression = "$response.body#/paymentId")
),
@Link(
name = "GetPaymentReceipt",
description = "Download payment receipt",
operationId = "downloadReceipt",
parameters = @LinkParameter(name = "paymentId", expression = "$response.body#/paymentId")
)
}
)
public Response processPayment(@RequestBody PaymentRequest request) {}Defines parameters passed between linked operations using expression language.
/**
* Defines parameter for linked operation
* Applied to: within Link annotations
*/
@LinkParameter(
name = "userId", // Parameter name (required)
expression = "$response.body#/id" // Expression to get parameter value (required)
)Expression Types and Examples:
// Response body field access
@LinkParameter(name = "userId", expression = "$response.body#/id")
@LinkParameter(name = "email", expression = "$response.body#/user/email")
@LinkParameter(name = "status", expression = "$response.body#/status")
// Response header values
@LinkParameter(name = "etag", expression = "$response.header.ETag")
@LinkParameter(name = "location", expression = "$response.header.Location")
@LinkParameter(name = "contentType", expression = "$response.header.Content-Type")
// Request parameter forwarding
@LinkParameter(name = "include", expression = "$request.query.include")
@LinkParameter(name = "version", expression = "$request.path.version")
@LinkParameter(name = "clientId", expression = "$request.header.X-Client-ID")
// Static/constant values
@LinkParameter(name = "format", expression = "json")
@LinkParameter(name = "version", expression = "v2")
@LinkParameter(name = "scope", expression = "read")
// Complex JSON path expressions
@LinkParameter(name = "addressId", expression = "$response.body#/customer/defaultAddress/id")
@LinkParameter(name = "productIds", expression = "$response.body#/items[*]/productId")
@LinkParameter(name = "totalAmount", expression = "$response.body#/order/totals/grandTotal")Advanced Parameter Examples:
// Pagination workflow
@GET
@Path("/users")
@Operation(operationId = "listUsers", summary = "List users with pagination")
@ApiResponse(
responseCode = "200",
description = "User list retrieved",
content = @Content(schema = @Schema(implementation = UserList.class)),
headers = {
@Header(name = "X-Total-Count", schema = @Schema(type = "integer")),
@Header(name = "X-Page-Number", schema = @Schema(type = "integer")),
@Header(name = "Link", schema = @Schema(type = "string"))
},
links = {
@Link(
name = "NextPage",
description = "Get next page of users",
operationId = "listUsers",
parameters = {
@LinkParameter(name = "page", expression = "$response.header.X-Page-Number + 1"),
@LinkParameter(name = "size", expression = "$request.query.size"),
@LinkParameter(name = "sort", expression = "$request.query.sort")
}
),
@Link(
name = "PreviousPage",
description = "Get previous page of users",
operationId = "listUsers",
parameters = {
@LinkParameter(name = "page", expression = "$response.header.X-Page-Number - 1"),
@LinkParameter(name = "size", expression = "$request.query.size")
}
),
@Link(
name = "FirstPage",
description = "Get first page",
operationId = "listUsers",
parameters = {
@LinkParameter(name = "page", expression = "1"),
@LinkParameter(name = "size", expression = "$request.query.size")
}
)
}
)
public Response listUsers(
@QueryParam("page") Integer page,
@QueryParam("size") Integer size,
@QueryParam("sort") String sort
) {}
// File upload and processing workflow
@POST
@Path("/files/upload")
@Operation(operationId = "uploadFile", summary = "Upload file")
@ApiResponse(
responseCode = "201",
description = "File uploaded successfully",
content = @Content(schema = @Schema(implementation = FileUploadResult.class)),
links = {
@Link(
name = "ProcessFile",
description = "Start processing the uploaded file",
operationId = "processFile",
parameters = @LinkParameter(name = "fileId", expression = "$response.body#/fileId"),
requestBody = "$response.body#/processingOptions"
),
@Link(
name = "DownloadFile",
description = "Download the uploaded file",
operationId = "downloadFile",
parameters = @LinkParameter(name = "fileId", expression = "$response.body#/fileId")
),
@Link(
name = "GetFileMetadata",
description = "Get file metadata and status",
operationId = "getFileMetadata",
parameters = @LinkParameter(name = "fileId", expression = "$response.body#/fileId")
),
@Link(
name = "DeleteFile",
description = "Delete the uploaded file",
operationId = "deleteFile",
parameters = @LinkParameter(name = "fileId", expression = "$response.body#/fileId")
)
}
)
public Response uploadFile(
@FormParam("file") InputStream file,
@FormParam("metadata") FileMetadata metadata
) {}Container for multiple link definitions in responses.
/**
* Container for multiple Link annotations
*/
@Links({
@Link(name = "self", operationId = "getUserById", parameters = @LinkParameter(name = "id", expression = "$response.body#/id")),
@Link(name = "edit", operationId = "updateUser", parameters = @LinkParameter(name = "id", expression = "$response.body#/id")),
@Link(name = "delete", operationId = "deleteUser", parameters = @LinkParameter(name = "id", expression = "$response.body#/id"))
})// Customer onboarding workflow
@POST
@Path("/customers/onboard")
@Operation(operationId = "startOnboarding", summary = "Start customer onboarding")
@ApiResponse(
responseCode = "201",
description = "Onboarding process started",
content = @Content(schema = @Schema(implementation = OnboardingSession.class)),
links = {
@Link(
name = "VerifyIdentity",
description = "Verify customer identity",
operationId = "verifyIdentity",
parameters = @LinkParameter(name = "sessionId", expression = "$response.body#/sessionId")
),
@Link(
name = "UploadDocuments",
description = "Upload verification documents",
operationId = "uploadDocuments",
parameters = @LinkParameter(name = "sessionId", expression = "$response.body#/sessionId")
),
@Link(
name = "CheckOnboardingStatus",
description = "Check onboarding progress",
operationId = "getOnboardingStatus",
parameters = @LinkParameter(name = "sessionId", expression = "$response.body#/sessionId")
)
}
)
public Response startOnboarding(@RequestBody OnboardingRequest request) {}
@PUT
@Path("/customers/onboard/{sessionId}/verify")
@Operation(operationId = "verifyIdentity", summary = "Verify customer identity")
@ApiResponse(
responseCode = "200",
description = "Identity verification completed",
content = @Content(schema = @Schema(implementation = VerificationResult.class)),
links = {
@Link(
name = "SetupAccount",
description = "Set up customer account after verification",
operationId = "setupAccount",
parameters = @LinkParameter(name = "customerId", expression = "$response.body#/customerId"),
requestBody = "$response.body#/accountDetails"
),
@Link(
name = "ActivateServices",
description = "Activate customer services",
operationId = "activateServices",
parameters = @LinkParameter(name = "customerId", expression = "$response.body#/customerId")
)
}
)
public Response verifyIdentity(@PathParam("sessionId") String sessionId, @RequestBody IdentityData data) {}@GET
@Path("/orders/{id}")
@Operation(operationId = "getOrder", summary = "Get order details")
@ApiResponse(
responseCode = "200",
description = "Order details",
content = @Content(schema = @Schema(implementation = Order.class)),
links = {
@Link(
name = "PayOrder",
description = "Pay for order (only available for unpaid orders)",
operationId = "processPayment",
parameters = @LinkParameter(name = "orderId", expression = "$response.body#/id"),
extensions = @Extension(
name = "x-link-condition",
properties = @ExtensionProperty(name = "when", value = "$response.body#/status == 'pending'")
)
),
@Link(
name = "CancelOrder",
description = "Cancel order (only available for pending orders)",
operationId = "cancelOrder",
parameters = @LinkParameter(name = "orderId", expression = "$response.body#/id"),
extensions = @Extension(
name = "x-link-condition",
properties = @ExtensionProperty(name = "when", value = "$response.body#/status in ['pending', 'processing']")
)
),
@Link(
name = "TrackShipment",
description = "Track shipment (only available for shipped orders)",
operationId = "trackShipment",
parameters = @LinkParameter(name = "trackingNumber", expression = "$response.body#/tracking/number"),
extensions = @Extension(
name = "x-link-condition",
properties = @ExtensionProperty(name = "when", value = "$response.body#/status == 'shipped'")
)
),
@Link(
name = "RequestRefund",
description = "Request refund (only available for completed orders)",
operationId = "requestRefund",
parameters = @LinkParameter(name = "orderId", expression = "$response.body#/id"),
extensions = @Extension(
name = "x-link-condition",
properties = @ExtensionProperty(name = "when", value = "$response.body#/status == 'completed'")
)
)
}
)
public Response getOrder(@PathParam("id") Long orderId) {}@POST
@Path("/data/import")
@Operation(operationId = "importData", summary = "Import data")
@ApiResponses({
@ApiResponse(
responseCode = "200",
description = "Import successful",
content = @Content(schema = @Schema(implementation = ImportResult.class)),
links = {
@Link(
name = "GetImportStatus",
description = "Get detailed import status",
operationId = "getImportStatus",
parameters = @LinkParameter(name = "importId", expression = "$response.body#/importId")
),
@Link(
name = "DownloadReport",
description = "Download import report",
operationId = "downloadImportReport",
parameters = @LinkParameter(name = "importId", expression = "$response.body#/importId")
)
}
),
@ApiResponse(
responseCode = "400",
description = "Import failed with validation errors",
content = @Content(schema = @Schema(implementation = ImportError.class)),
links = {
@Link(
name = "GetErrorDetails",
description = "Get detailed error information",
operationId = "getImportErrors",
parameters = @LinkParameter(name = "importId", expression = "$response.body#/importId")
),
@Link(
name = "RetryImport",
description = "Retry import with corrected data",
operationId = "retryImport",
parameters = @LinkParameter(name = "importId", expression = "$response.body#/importId")
),
@Link(
name = "DownloadErrorReport",
description = "Download error report with line numbers",
operationId = "downloadErrorReport",
parameters = @LinkParameter(name = "importId", expression = "$response.body#/importId")
)
}
)
})
public Response importData(@RequestBody ImportRequest request) {}@POST
@Path("/users")
@Operation(operationId = "createUser", summary = "Create user")
@ApiResponse(
responseCode = "201",
description = "User created",
content = @Content(schema = @Schema(implementation = User.class)),
links = {
@Link(
name = "CreateUserProfile",
description = "Create user profile in profile service",
operationId = "createProfile",
parameters = @LinkParameter(name = "userId", expression = "$response.body#/id"),
server = @Server(
url = "https://profiles-api.example.com",
description = "Profile management service"
)
),
@Link(
name = "SetupNotifications",
description = "Set up user notifications",
operationId = "setupNotifications",
parameters = @LinkParameter(name = "userId", expression = "$response.body#/id"),
server = @Server(
url = "https://notifications-api.example.com",
description = "Notification service"
)
),
@Link(
name = "InitializeWallet",
description = "Initialize user wallet",
operationId = "initializeWallet",
parameters = @LinkParameter(name = "userId", expression = "$response.body#/id"),
server = @Server(
url = "https://wallet-api.example.com",
description = "Wallet service"
)
)
}
)
public Response createUser(@RequestBody CreateUserRequest request) {}@Link(
name = "complexWorkflow",
description = "Initiate complex multi-step workflow",
operationId = "startWorkflow",
parameters = @LinkParameter(name = "entityId", expression = "$response.body#/id"),
extensions = {
@Extension(
name = "x-link-metadata",
properties = {
@ExtensionProperty(name = "category", value = "workflow"),
@ExtensionProperty(name = "complexity", value = "high"),
@ExtensionProperty(name = "estimated-duration", value = "5-10 minutes"),
@ExtensionProperty(name = "requires-approval", value = "true")
}
),
@Extension(
name = "x-link-permissions",
properties = {
@ExtensionProperty(name = "required-roles", value = "[\"admin\", \"workflow-manager\"]"),
@ExtensionProperty(name = "required-scopes", value = "[\"workflow:execute\"]")
}
)
}
)@Link(
name = "optimizedDataAccess",
description = "Access data with caching optimization",
operationId = "getCachedData",
parameters = {
@LinkParameter(name = "id", expression = "$response.body#/id"),
@LinkParameter(name = "cacheKey", expression = "$response.body#/cacheKey")
},
extensions = {
@Extension(
name = "x-caching",
properties = {
@ExtensionProperty(name = "ttl", value = "3600"),
@ExtensionProperty(name = "cache-strategy", value = "read-through"),
@ExtensionProperty(name = "cache-tags", value = "[\"user-data\", \"profile\"]")
}
)
}
)Install with Tessl CLI
npx tessl i tessl/maven-io-swagger-core-v3--swagger-annotations-jakarta