Jakarta EE compatible OpenAPI 3.x annotations for defining REST API specifications
—
Advanced callback operations for asynchronous API interactions and webhook definitions supporting OpenAPI 3.1 features. This system enables documentation of event-driven API interactions, asynchronous callbacks, and webhook-based communication patterns.
Defines callback operations that describe requests initiated by the server based on specific events or conditions.
/**
* Defines callback operation for asynchronous interactions
* Applied to: METHOD, TYPE
* Repeatable: Yes
*/
@Callback(
name = "statusUpdateCallback", // Callback name (required)
callbackUrlExpression = "{$request.body#/callbackUrl}", // URL expression (required)
operation = @Operation( // Callback operation definition
method = "POST",
summary = "Status update notification",
description = "Called when status changes",
requestBody = @RequestBody(
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = StatusUpdateEvent.class)
)
),
responses = @ApiResponse(responseCode = "200", description = "Acknowledged")
),
extensions = {@Extension(...)} // Custom extensions
)Callback URL Expressions:
// Request body field
callbackUrlExpression = "{$request.body#/callbackUrl}"
// Request header
callbackUrlExpression = "{$request.header.X-Callback-URL}"
// Query parameter
callbackUrlExpression = "{$request.query.callback}"
// Static URL with parameters
callbackUrlExpression = "https://api.example.com/callbacks/{$request.body#/id}"
// Complex expression
callbackUrlExpression = "{$request.body#/baseUrl}/webhooks/{$request.body#/eventType}"Usage Examples:
// Payment processing callback
@POST
@Path("/payments")
@Operation(summary = "Process payment")
@Callback(
name = "paymentStatusCallback",
callbackUrlExpression = "{$request.body#/statusWebhookUrl}",
operation = @Operation(
method = "POST",
summary = "Payment status update",
description = "Called when payment status changes",
requestBody = @RequestBody(
description = "Payment status event",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = PaymentStatusEvent.class),
examples = @ExampleObject(
name = "paymentCompleted",
value = """
{
"paymentId": "pay_123456",
"status": "completed",
"amount": 99.99,
"timestamp": "2023-12-01T10:30:00Z"
}
"""
)
)
),
responses = {
@ApiResponse(responseCode = "200", description = "Callback acknowledged"),
@ApiResponse(responseCode = "404", description = "Callback endpoint not found"),
@ApiResponse(responseCode = "500", description = "Callback processing failed")
}
)
)
public Response processPayment(@RequestBody PaymentRequest request) {}
// Order fulfillment callbacks
@PUT
@Path("/orders/{id}/fulfill")
@Operation(summary = "Fulfill order")
@Callbacks({
@Callback(
name = "orderShipped",
callbackUrlExpression = "{$request.body#/shippingWebhook}",
operation = @Operation(
method = "POST",
summary = "Order shipped notification",
requestBody = @RequestBody(
content = @Content(schema = @Schema(implementation = ShippingEvent.class))
)
)
),
@Callback(
name = "orderDelivered",
callbackUrlExpression = "{$request.body#/deliveryWebhook}",
operation = @Operation(
method = "POST",
summary = "Order delivered notification",
requestBody = @RequestBody(
content = @Content(schema = @Schema(implementation = DeliveryEvent.class))
)
)
)
})
public Response fulfillOrder(@PathParam("id") Long orderId, @RequestBody FulfillmentRequest request) {}
// User registration callback with verification
@POST
@Path("/users/register")
@Operation(summary = "Register new user")
@Callback(
name = "emailVerificationCallback",
callbackUrlExpression = "{$request.body#/verificationCallbackUrl}",
operation = @Operation(
method = "POST",
summary = "Email verification result",
description = "Called after user clicks verification link",
requestBody = @RequestBody(
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = VerificationEvent.class)
)
),
responses = @ApiResponse(responseCode = "200", description = "Verification processed"),
security = @SecurityRequirement(name = "callbackToken")
)
)
public Response registerUser(@RequestBody UserRegistrationRequest request) {}
// File processing callback with progress updates
@POST
@Path("/files/process")
@Operation(summary = "Process uploaded file")
@Callback(
name = "processingProgressCallback",
callbackUrlExpression = "{$request.body#/progressUrl}",
operation = @Operation(
method = "POST",
summary = "File processing progress update",
requestBody = @RequestBody(
content = @Content(
schema = @Schema(implementation = ProcessingProgressEvent.class)
)
),
responses = @ApiResponse(responseCode = "200", description = "Progress update received")
)
)
public Response processFile(@RequestBody FileProcessingRequest request) {}Container for multiple callback definitions on a single operation.
/**
* Container for multiple Callback annotations
*/
@Callbacks({
@Callback(name = "success", callbackUrlExpression = "{$request.body#/successUrl}", operation = @Operation(...)),
@Callback(name = "failure", callbackUrlExpression = "{$request.body#/failureUrl}", operation = @Operation(...)),
@Callback(name = "progress", callbackUrlExpression = "{$request.body#/progressUrl}", operation = @Operation(...))
})Defines webhook operations for event-driven API interactions using OpenAPI 3.1 webhook specification.
/**
* Defines webhook operation (OpenAPI 3.1)
* Applied to: TYPE, METHOD
* Repeatable: Yes
*/
@Webhook(
name = "userCreated", // Webhook name (required)
operation = @Operation( // Webhook operation definition
method = "POST",
summary = "User created notification",
description = "Sent when a new user is created",
requestBody = @RequestBody(
description = "User creation event data",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = UserCreatedEvent.class)
)
),
responses = {
@ApiResponse(responseCode = "200", description = "Webhook received successfully"),
@ApiResponse(responseCode = "410", description = "Webhook endpoint no longer available")
},
security = @SecurityRequirement(name = "webhookSignature")
),
extensions = {@Extension(...)} // Custom extensions
)Usage Examples:
// Application-level webhooks
@OpenAPIDefinition(
info = @Info(title = "E-commerce API", version = "1.0"),
webhooks = {
@Webhook(
name = "orderCreated",
operation = @Operation(
method = "POST",
summary = "Order created webhook",
requestBody = @RequestBody(
content = @Content(schema = @Schema(implementation = OrderCreatedEvent.class))
)
)
),
@Webhook(
name = "paymentProcessed",
operation = @Operation(
method = "POST",
summary = "Payment processed webhook",
requestBody = @RequestBody(
content = @Content(schema = @Schema(implementation = PaymentProcessedEvent.class))
)
)
)
}
)
public class ECommerceApplication {}
// Resource-specific webhooks
@Path("/subscriptions")
@Webhooks({
@Webhook(
name = "subscriptionActivated",
operation = @Operation(
method = "POST",
summary = "Subscription activated",
requestBody = @RequestBody(
content = @Content(schema = @Schema(implementation = SubscriptionEvent.class))
),
security = @SecurityRequirement(name = "webhookSignature")
)
),
@Webhook(
name = "subscriptionCancelled",
operation = @Operation(
method = "POST",
summary = "Subscription cancelled",
requestBody = @RequestBody(
content = @Content(schema = @Schema(implementation = SubscriptionEvent.class))
)
)
)
})
public class SubscriptionResource {}
// User lifecycle webhooks
@Webhook(
name = "userStatusChanged",
operation = @Operation(
method = "POST",
summary = "User status change notification",
description = "Triggered when user status changes (active, suspended, deleted)",
requestBody = @RequestBody(
description = "User status change event",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = UserStatusChangeEvent.class),
examples = {
@ExampleObject(
name = "userSuspended",
summary = "User account suspended",
value = """
{
"eventType": "user.status.changed",
"userId": 12345,
"previousStatus": "active",
"newStatus": "suspended",
"reason": "Terms of service violation",
"timestamp": "2023-12-01T15:30:00Z"
}
"""
),
@ExampleObject(
name = "userDeleted",
summary = "User account deleted",
value = """
{
"eventType": "user.status.changed",
"userId": 12345,
"previousStatus": "active",
"newStatus": "deleted",
"reason": "User requested account deletion",
"timestamp": "2023-12-01T16:45:00Z"
}
"""
)
}
)
),
responses = {
@ApiResponse(
responseCode = "200",
description = "Webhook processed successfully"
),
@ApiResponse(
responseCode = "401",
description = "Invalid webhook signature"
),
@ApiResponse(
responseCode = "410",
description = "Webhook endpoint no longer exists"
)
},
security = @SecurityRequirement(name = "webhookSignature"),
extensions = {
@Extension(
name = "x-webhook-metadata",
properties = {
@ExtensionProperty(name = "retry-policy", value = "exponential-backoff"),
@ExtensionProperty(name = "max-retries", value = "5"),
@ExtensionProperty(name = "timeout", value = "30s")
}
)
}
)
)Container for multiple webhook definitions.
/**
* Container for multiple Webhook annotations
*/
@Webhooks({
@Webhook(name = "created", operation = @Operation(...)),
@Webhook(name = "updated", operation = @Operation(...)),
@Webhook(name = "deleted", operation = @Operation(...))
})@POST
@Path("/orders")
@Operation(summary = "Create order with conditional callbacks")
@Callbacks({
@Callback(
name = "fulfillmentCallback",
callbackUrlExpression = "{$request.body#/fulfillmentWebhook}",
operation = @Operation(
method = "POST",
summary = "Order fulfillment update",
description = "Called only for physical products requiring fulfillment"
)
),
@Callback(
name = "digitalDeliveryCallback",
callbackUrlExpression = "{$request.body#/deliveryWebhook}",
operation = @Operation(
method = "POST",
summary = "Digital product delivery",
description = "Called only for digital products"
)
)
})
public Response createOrder(@RequestBody OrderRequest request) {}@Webhook(
name = "secureWebhook",
operation = @Operation(
method = "POST",
summary = "Secure webhook with signature verification",
security = {
@SecurityRequirement(name = "webhookSignature"),
@SecurityRequirement(name = "bearerToken")
},
extensions = {
@Extension(
name = "x-webhook-security",
properties = {
@ExtensionProperty(name = "signature-header", value = "X-Webhook-Signature"),
@ExtensionProperty(name = "signature-algorithm", value = "HMAC-SHA256"),
@ExtensionProperty(name = "timestamp-header", value = "X-Webhook-Timestamp"),
@ExtensionProperty(name = "timestamp-tolerance", value = "300")
}
)
}
)
)@Callback(
name = "robustCallback",
callbackUrlExpression = "{$request.body#/callbackUrl}",
operation = @Operation(
method = "POST",
summary = "Callback with comprehensive error handling",
responses = {
@ApiResponse(responseCode = "200", description = "Success"),
@ApiResponse(responseCode = "400", description = "Invalid callback data"),
@ApiResponse(responseCode = "401", description = "Unauthorized callback"),
@ApiResponse(responseCode = "404", description = "Callback endpoint not found"),
@ApiResponse(responseCode = "408", description = "Callback timeout"),
@ApiResponse(responseCode = "410", description = "Callback endpoint permanently unavailable"),
@ApiResponse(responseCode = "429", description = "Too many callback attempts"),
@ApiResponse(responseCode = "500", description = "Callback endpoint error")
},
extensions = {
@Extension(
name = "x-callback-retry",
properties = {
@ExtensionProperty(name = "max-attempts", value = "5"),
@ExtensionProperty(name = "backoff-strategy", value = "exponential"),
@ExtensionProperty(name = "initial-delay", value = "1s"),
@ExtensionProperty(name = "max-delay", value = "300s")
}
)
}
)
)@Schema(description = "Payment status update event")
public class PaymentStatusEvent {
@Schema(description = "Payment identifier", example = "pay_123456")
private String paymentId;
@Schema(description = "Payment status", allowableValues = {"pending", "completed", "failed", "cancelled"})
private String status;
@Schema(description = "Payment amount", example = "99.99")
private BigDecimal amount;
@Schema(description = "Status change timestamp", format = "date-time")
private Instant timestamp;
@Schema(description = "Optional failure reason")
private String failureReason;
}
@Schema(description = "User status change event")
public class UserStatusChangeEvent {
@Schema(description = "Event type", example = "user.status.changed")
private String eventType;
@Schema(description = "User ID", example = "12345")
private Long userId;
@Schema(description = "Previous status", example = "active")
private String previousStatus;
@Schema(description = "New status", example = "suspended")
private String newStatus;
@Schema(description = "Reason for status change")
private String reason;
@Schema(description = "Change timestamp", format = "date-time")
private Instant timestamp;
}Install with Tessl CLI
npx tessl i tessl/maven-io-swagger-core-v3--swagger-annotations-jakarta