diff --git a/modules/ROOT/pages/chapter04/chapter04.adoc b/modules/ROOT/pages/chapter04/chapter04.adoc index a25f56a0..356cd74f 100644 --- a/modules/ROOT/pages/chapter04/chapter04.adoc +++ b/modules/ROOT/pages/chapter04/chapter04.adoc @@ -2,45 +2,88 @@ :imagesdir: ../../assets/images -== Introduction +== Introduction -In the previous chapter, we saw how RESTful APIs facilitate language-agnostic access to web services from diverse environments. However, a clear and comprehensive contract is required to ensure seamless integration between clients and services. This need for a well-defined API contract has led to the adoption of the OpenAPI specification. -This chapter will explore the primary features of MicroProfile OpenAPI, demonstrate how to integrate it into your MicroProfile applications, and show you how to annotate your RESTful services to produce rich documentation that adheres to the OpenAPI specification. Furthermore, we will introduce the OpenAPI UI, a visual interface allowing developers and stakeholders to interact with and visualize the documented APIs, enhancing understanding and facilitating integration. +In previous chapters, you built Restful (Representational State Transfer) APIs that various clients can use to access your web services regardless of programming language or platform. However, clients need a clear and comprehensive contract that describes available endpoints, expected parameters, and response formats to integrate seamlessly with these web services. Organizations adopt the OpenAPI Specification to provide this well-defined API contract. +This chapter introduces MicroProfile OpenAPI and shows you how to document your RESTful services using OpenAPI annotations. You will learn to generate comprehensive API documentation that follows the OpenAPI Specification, then visualize and interact with your documented APIs using Swagger UI, a browser-based interface that makes your API contract immediately accessible to developers and stakeholders. -== Topics to be covered: -- Introduction to MicroProfile OpenAPI -- API Specification using MicroProfile Open API -- Generating API Documentation -- Documenting Authentication and Authorization Requirements -- Exploring the APIs using Swagger UI +== What you will learn + +This chapter covers the following topics: + +* Understanding MicroProfile OpenAPI +* Specifying APIs using MicroProfile OpenAPI +* Generating API documentation +* Documenting authentication and authorization +* Exploring APIs using Swagger UI +* Using new features in MicroProfile OpenAPI 4.1 + +=== New features in MicroProfile OpenAPI 4.1 + +MicroProfile OpenAPI 4.1 introduces the following enhancements: + +* OpenAPI v3.1 compatibility +* Java records support +* `@Target` annotation enhancements +* JSON Schema dialect support +* Extensible interface methods +* Asynchronous operations with callbacks +* `Optional` nullable fields +* Comprehensive security schemes == OpenAPI Specification -The Open API Specification (OAS), formerly Swagger specification, is a technical specification that allows REST API providers to describe and publish their APIs using a format that various tools can consume. It defines a standard, language-agnostic interface to RESTful APIs, making it easy for third-party tools to generate documentation, client SDKs, and a range of tools that promote the seamless consumption of RESTful APIs. +The OpenAPI Specification (OAS), formerly known as Swagger, defines a standard format for describing and publishing REST APIs. It provides a language-agnostic interface to RESTful APIs. API providers use OAS to create machine-readable documents that describe their endpoints, request and response schemas, authentication requirements, and other API characteristics. This standardization enables powerful tooling: Integrated Development Environments (IDEs) can automatically generate client code. Testing frameworks can then verify your API's functionality against its specification. Furthermore, Documentation tools can build interactive API explorers, all stemming from a single OpenAPI document. -NOTE: The OpenAPI Initiative, a consortium of industry experts committed to standardizing how to describe REST APIs, maintains the OpenAPI Specification. It is a community-driven initiative, and many large organizations use it, including Google, Microsoft, and Amazon. +[NOTE] +==== +The OpenAPI Initiative (a Linux Foundation collaborative project) maintains the specification. Industry leaders including Google, Microsoft, and Amazon contribute to this community-driven standard, which ensures it meets enterprise requirements while remaining vendor-neutral. +==== -The OpenAPI specification enables creation of a well-defined, clear and comprehensive API contract. It provides a standardized way to describe the API's structure, expected requests and responses, and authentication mechanisms, making it easier to develop, test, and maintain RESTful APIs. +When you adopt OpenAPI, you create a single source of truth that serves as both human-readable documentation and a machine-parsable contract. This approach simplifies API development, testing, and long-term maintenance while making your API more accessible to consumers. == Introduction to MicroProfile OpenAPI -The MicroProfile OpenAPI specification builds upon the widely recognized OpenAPI Specification (OAS) and leverages annotations from the Jakarta Restful Web Services specification. The primary focus of MicroProfile OpenAPI is on defining REST APIs that utilize JSON within the context of HTTP. +The MicroProfile OpenAPI specification integrates the OpenAPI Specification (OAS) with Jakarta RESTful Web Services, previously known as JAX-RS. It provides Java annotations that automatically generate OpenAPI documentation directly from your Java code. This annotation-driven approach offers several advantages: + +* Your documentation stays always synchronized with your codebase +* You do not need to maintain and juggle between separate specification files +* The OpenAPI output is generated automatically when your application is deployed +* The standard Jakarta REST endpoints work without any modifications -The specification aims to provide a uniform way of describing APIs so that they are both human-readable and machine-readable. It facilitates the creation of APIs that are consistent, well-documented, and easily consumable by both humans and machines. +MicroProfile OpenAPI 4.0 supports OpenAPI Specification 3.1. This ensures your APIs are consistent, well-documented, and easily usable across a variety of development tools, testing frameworks, and API consumers. -== Capabilities of MicroProfile OpenAPI Specification +== Capabilities of MicroProfile OpenAPI -MicroProfile OpenAPI provides a suite of Java APIs that allows developers to define and generate API specifications that adhere to OpenAPI v3 standards. As a result, it simplifies the process of designing, documenting, and publishing RESTful APIs for developers. +MicroProfile OpenAPI provides a set of annotations and APIs that allow you to generate OpenAPI 3.1-compliant specifications directly from your Jakarta REST code. Instead of manually writing YAML (YAML Ain't Markup Language) or JSON (JavaScript Object Notation) specification files, you document your API using Java annotations, and MicroProfile OpenAPI generates the complete specification automatically. -Developers can quickly generate documentation for their microservices using MicroProfile OpenAPI. The documentation includes information on what services are provided, how to invoke them, and what data types are used. It generates comprehensive metadata about services, ensuring interoperability across diverse platforms and tools. Also, documentation can generate client code to access the web services. +The specification supports the full range of OpenAPI features: -The OpenAPI Specification fuels a rich ecosystem of tools that automate and support. This specification streamlines the creation of OpenAPI documentation for RESTful services using a unified approach. It generates comprehensive metadata about services, ensuring interoperability across diverse platforms and tools: +* Document endpoints: operations, parameters, request and response schemas, and examples +* Specify API metadata: versioning, contact information, license details, and servers +* Define data models: reusable schemas for your domain objects +* Document authentication: security requirements and schemes +* Describe responses: success and error responses with status codes -* *API Documentation Generation*: Intuitive interactive documentation portals emerge directly from the specification. -* *Client SDK Creation*: Client libraries in various languages can be automatically generated. -* *API Testing*: Testing frameworks can leverage the specification to design robust tests. -* *API Mocking*: Simplifies mocking APIs for testing and development purposes. +Your generated OpenAPI specification enables a rich ecosystem of tools. Documentation portals like Swagger UI create interactive API explorers. Code generators produce client libraries in multiple languages. Testing frameworks validate requests and responses against your specification. Mock servers simulate your API for development and testing. + +MicroProfile OpenAPI ensures your documentation stays synchronized with your implementation, which eliminates the common problem of outdated API specifications. + +== Generating OpenAPI documents + +Add annotations to your Jakarta REST resources to generate documentation with MicroProfile OpenAPI. This annotation-driven approach keeps your documentation synchronized with your code. When you update an endpoint, you update its documentation at the same time. + +=== Annotation-based generation + +You can use OpenAPI annotations to mark up your Jakarta REST classes and methods. MicroProfile OpenAPI then scans your application during the build time, automatically discovering the annotated endpoints, and producing a full OpenAPI specification. + +This approach has several advantages: + +* Documentation lives alongside the code it describes +* IDEs can provide support for autocomplete and validation features. +* Compile-time checking ensures annotations remain valid +* No separate specification files to maintain == Generating OpenAPI documents @@ -48,6 +91,39 @@ There are multiple ways in which you can generate OpenAPI documents. The most co Besides annotations, a predefined OpenAPI document may be provided in either YAML or JSON format. This so-called static model will be merged with the model generated by scanning for Jakarta REST endpoints and the combined result will be made available to clients. However, the annotation-based approach is recommended as it is more maintainable and easier to understand. Finally, you can filter out the resources you do not want to document using configuration. +=== Static OpenAPI files (optional) + +You can also provide a static OpenAPI document in `.yaml` or `.json` format at `META-INF/openapi.yaml` or `META-INF/openapi.json`. MicroProfile OpenAPI will merge this static content with the generated specification, which allows you to: + +* Add API-level metadata (title, version, contact information) +* Define reusable schemas or security schemes +* Override generated documentation for specific endpoints + +However, for most applications, annotations alone provide sufficient documentation. + +=== Selective documentation + +Use the `mp.openapi.scan.exclude.packages` and `mp.openapi.scan.exclude.classes` configuration properties to exclude specific resources from documentation. This approach proves useful for internal or administrative endpoints you do not want to expose in your public API specification. + +== Using MicroProfile OpenAPI in your project + +To use MicroProfile OpenAPI in your project, add the following Maven coordinates: + +[source, xml] +---- + + org.eclipse.microprofile.openapi + microprofile-openapi-api + 4.0 + provided + +---- + +[NOTE] +==== +Use `provided` because your MicroProfile runtime already includes the implementation. +==== + == Using MicroProfile Open API in your project To document Jakarta RESTful Web Services using MicroProfile OpenAPI, we need to annotate the resource classes and methods with the OpenAPI annotation. @@ -67,16 +143,7 @@ Below is an illustrative example of how you might annotate a method in the `Prod [source, java] ---- -import org.eclipse.microprofile.openapi.annotations.Operation; -import org.eclipse.microprofile.openapi.annotations.media.Content; -import org.eclipse.microprofile.openapi.annotations.media.Schema; -import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; -import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; - -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; +// ... @ApplicationScoped @Path("/products") @@ -107,217 +174,1438 @@ public class ProductResource { } ---- -Explanation: - -* `@Operation`: Provides a summary and description for the `getProducts()` method. +The annotations provide the following documentation: -* `@APIResponse`: Describes the possible responses from the `getProducts()` operation. In this case, a successful response (HTTP 200) is described, indicating that the method returns an array of Product entities. +* `@Tag`: Groups related endpoints together in the documentation +* `@Operation`: Describes what the endpoint does +* `@APIResponses`: Documents possible HTTP responses from the operation +* `@APIResponse`: Defines a specific response with status code and content +* `@Content`: Specifies the response media type and structure +* `@Schema`: References the data model returned (in this case, `Product.class`) -* `@Schema`: Specifies the schema of the response content. Here, it is used to indicate that the method returns an array of Product objects. +These annotations enrich the `ProductResource` class with metadata necessary for generating comprehensive OpenAPI documentation automatically. -These annotations enrich the `ProductResource` class with metadata necessary for generating comprehensive and descriptive OpenAPI documentation automatically. - -We have also annotated the `getProducts()` method with the @APIResponse annotation to document the successful response from the operation. The `responseCode` field is used to specify the status code of the response, and the `description` field is used to provide a brief description of the response. There are two possible responses – a successful response containing a list of produdts with a 200 status code, and an unsuccessful response with a 400 status code, if no products are found. The content field is used to specify the schema of the response content. In this example, the response content is a list of `Product`s. - -Finally, we need to add the following property to the src/main/resources/META-INF/microprofile-config.properties file: +Add the following property to the `src/main/resources/META-INF/microprofile-config.properties` file: +[source, properties] ---- mp.openapi.scan=true ---- -This property tells MicroProfile OpenAPI to scan our classes for annotations and generate API documentation for them. +This property tells MicroProfile OpenAPI to scan your classes for annotations and generate API documentation for them. -Now that we have configured MicroProfile OpenAPI, we can build and run our application. +Build and run your application after configuring MicroProfile OpenAPI. -== How to view the generated documentation +== Viewing the generated documentation -To view the generated documentation, we can use the OpenAPI UI tool. The Open API UI tool is a web-based tool that can be used to view the documentation for a REST API. - -The OpenAPI UI tool can be accessed at the following URL: +To view the generated documentation, use the OpenAPI endpoint. Access the OpenAPI specification at the following URL: +[source] ---- -http://localhost:/openapi/ +http://:/openapi/ ---- -Replace `` with the actual port used by your runtime, for e.g. 9080 which is the default port at Open Liberty server. +Replace `` and `` with the actual hostname and port used by your runtime (for example, `localhost:9080`, which is the default port for Open Liberty server). -The `/openapi` endpoint is used to get information about the OpenAPI specification generated from the comments in the source code annotations. It returns information in YAML format. +The `/openapi` endpoint returns the OpenAPI specification generated from the annotations in your source code. It returns information in YAML format. -When we access the `http://localhost:5050/openapi` URL, we should see the API documentation that was generated by MicroProfile OpenAPI: +When you access the `\http://localhost:9080/openapi` URL, you see the API documentation that MicroProfile OpenAPI generated: [source, yaml] ---- -openapi: 3.0.3 -info: - title: Generated API - version: "1.0" -servers: -- url: http://localhost:9080/catalog -paths: - /api/products: - get: - responses: - "200": - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Product' - put: - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Product' - responses: - "200": - description: OK - post: - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Product' - responses: - "200": - description: OK - /api/products/products/{id}: - delete: - parameters: - - name: id - in: path - required: true - schema: - format: int64 - type: integer - responses: - "200": - description: OK - /api/products/{id}: - get: - parameters: - - name: id - in: path - required: true - schema: - format: int64 - type: integer - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/Product' +openapi: 3.1.0 components: schemas: Product: - required: - - name - - description - - price type: object properties: id: - format: int64 type: integer + format: int64 name: type: string description: type: string price: - format: double type: number + format: double +info: + title: Generated API + version: "1.0" +paths: + /api/products: + get: + summary: List all products + description: Retrieves a complete list of products in the catalog. + tags: + - Products + responses: + "200": + description: Successfully retrieved product list + content: + application/json: + schema: + $ref: '#/components/schemas/Product' + "500": + description: Internal server error +tags: +- name: Products + description: Product catalog operations +servers: +- url: https://localhost:5050/mp-ecomm-store ---- -As we can see, MicroProfile OpenAPI has generated API documentation for our resource class. We can use this documentation to learn about the API and how to use it. +MicroProfile OpenAPI generates API documentation for your resource class. You can use this documentation to learn about the API and how to use it. This documentation includes the following information: + +* The `/products` endpoint description +* The GET operation summary and details +* Expected response codes (200, 500) +* The JSON structure of the Product response +* Grouping under the Products tag -MicroProfile OpenAPI allows developers to produce these specifications directly from their codebase, leveraging annotations and/or providing OpenAPI documents statically. This direct generation ensures that the API documentation is always up to date with the code. +MicroProfile OpenAPI allows developers to produce these specifications directly from their codebase by using annotations or providing OpenAPI documents statically. This direct generation ensures that the API documentation remains synchronized with the code. -== Exploring the APIs using Swagger UI +Visualization tools like Swagger UI can render this specification, client generators can consume it, and testing frameworks can use it for validation. -To open Swagger UI for the API documentation generated using MicroProfile OpenAPI, you will need to deploy your application to a server that supports MicroProfile, such as Open Liberty, WildFly, Quarkus, or Payara Micro. These servers automatically generate the OpenAPI documentation for your RESTful services based on the annotations in your code. +== Exploring APIs using Swagger UI -Next, visit the following URL to launch the Swagger UI: +To open Swagger UI for the API documentation generated using MicroProfile OpenAPI, deploy your application to a server that supports MicroProfile, such as Open Liberty, WildFly, Quarkus, or Payara Micro. These servers automatically generate the OpenAPI documentation for your RESTful services based on the annotations in your code. + +Visit the following URL to launch Swagger UI: ---- http://localhost:9080/openapi/ui ---- -Swagger UI is then used to render this documentation in a user-friendly web interface. Below is the screenshot of swagger UI for the Product REST Resource. +Swagger UI renders this documentation in a user-friendly web interface. The following figure shows the Swagger UI for the Product REST resource. -:figure-caption: Swagger UI -.Swagger UI -image::figure4-1.png[MicroProfile OpenAPI] +.Swagger UI displaying the Product Catalog API +image::figure4-1.png[Swagger UI interface showing Product API endpoints,800] == Annotations -The MicroProfile OpenAPI annotations can be used to document any Jakarta Restful Web Services resource. The annotations can also be used in conjunction with other Jakarta Restful Webservices annotations, such as @Path and @Produces. The most common annotations that are used to document RESTful web services are list in Table 4-1. +You can use MicroProfile OpenAPI annotations to document any Jakarta RESTful Web Services resource. You can also use the annotations with other Jakarta RESTful Web Services annotations, such as `@Path` and `@Produces`. Table 4-1 lists the most common annotations used to document RESTful web services. +.MicroProfile OpenAPI annotations for documenting Jakarta REST resources [cols="1,3", options="header"] |=== -| Annotations | Details +| Annotation | Description -| @OpenAPIDefinition +| `@OpenAPIDefinition` | Provides metadata about the entire API. It can include information such as the title, description, version, terms of service, and contact information. -| @Info -| Used inside @OpenAPIDefinition to provide API metadata like title, version, description. +| `@Info` +| Used inside `@OpenAPIDefinition` to provide API metadata like title, version, and description. -| @Contact -| Specifies contact information for the API, used within @Info. +| `@Contact` +| Specifies contact information for the API, used within `@Info`. -| @License -| Defines the license information for the API, also used within @Info. +| `@License` +| Defines the license information for the API, also used within `@Info`. -| @Operation +| `@Operation` | Describes a single API operation on a resource. -| @APIResponse -| It is used to document a response from an operation. +| `@APIResponse` +| Documents a response from an operation. -| @APIResponses -| A container for multiple @APIResponse annotations, allowing documentation of different responses for a single API operation. +| `@APIResponses` +| A container for multiple `@APIResponse` annotations, allowing documentation of different responses for a single API operation. -| @RequestBody +| `@RequestBody` | Describes the request body of an HTTP request, specifying the content of the body and whether it is required. -| @Schema +| `@Schema` | Provides schema details for a response or request body, specifying the data type, format, and constraints. -| @Parameter +| `@Parameter` | Provides information on parameters to the operation, including query parameters, header parameters, and path parameters. -| @Tag -| Adds metadata to a single tag that is used by the Operation. It helps in categorizing operations by resources or any other qualifier. +| `@Tag` +| Adds metadata to a single tag used by the operation. It helps in categorizing operations by resources or any other qualifier. -| @Content +| `@Content` | Specifies the media type and schema of the operation's request or response body. -| @Components -| Allows the definition of reusable components such as schemas, responses, parameters, and more, which can be referenced by other annotations. +| `@Components` +| Allows the definition of reusable components such as schemas, responses, parameters, and more, which other annotations can reference. -| @SecurityRequirement -| Specifies a security requirement for an operation, referencing security schemes defined in the @Components. +| `@SecurityRequirement` +| Specifies a security requirement for an operation, referencing security schemes defined in `@Components`. -| @ExternalDocumentation +| `@ExternalDocumentation` | Provides additional external documentation for an API or operation. -| @Callback +| `@Callback` | Specifies a callback URL for an asynchronous operation. -| @Callbacks +| `@Callbacks` | Specifies multiple `@Callback` annotations. -| @Server -| Describes a server that hosts the API, specifying URL and description, which can be global or specific to operations or paths +| `@Server` +| Describes a server that hosts the API, specifying URL and description, which can be global or specific to operations or paths. + +|=== + +All of these annotations are defined in the `org.eclipse.microprofile.openapi.annotations` package. + +== New features in MicroProfile OpenAPI 4.1 + +MicroProfile OpenAPI 4.1 introduces several enhancements that improve developer productivity and align with the OpenAPI v3.1 specification. These features simplify API documentation, add support for modern Java language features, and provide better schema validation capabilities. + +=== OpenAPI v3.1 specification support + +MicroProfile OpenAPI 4.1 fully supports the OpenAPI v3.1 specification, which brings several important improvements. + +==== JSON Schema 2020-12 alignment + +OpenAPI v3.1 adopts JSON Schema 2020-12 as its schema vocabulary, replacing the custom JSON Schema dialect used in v3.0. This provides: + +* Valid JSON Schema documents that work with standard validators +* Better interoperability with JSON Schema tooling and libraries +* Access to newer JSON Schema features like `prefixItems`, `$dynamicRef`, and enhanced pattern properties + +You can now use standard JSON Schema validation tools to test your API schemas independently of OpenAPI tooling. + +==== Improved nullable type handling + +OpenAPI v3.1 eliminates the proprietary `nullable: true` keyword in favor of JSON Schema's standard approach using type arrays (`type: ["string", "null"]`). + +*Before (OpenAPI v3.0):* +[source, yaml] +---- +schema: + type: string + nullable: true # Custom OpenAPI extension +---- + +*Now (OpenAPI v3.1):* +[source, yaml] +---- +schema: + type: ["string", "null"] # Standard JSON Schema +---- + +When documenting query parameters, path parameters, or request body fields that can be null, MicroProfile OpenAPI 4.1 generates OpenAPI v3.1 specifications that use standard JSON Schema nullable notation (`type: ["string", "null"]`) instead of the deprecated `nullable: true` keyword. + +==== Enhanced schema composition + +OpenAPI v3.1 provides improved support for `oneOf`, `anyOf`, and `allOf` keywords, which enables more flexible and precise schema modeling for: + +* Polymorphic types (using discriminators) +* Union types (multiple possible schemas) +* Inheritance relationships (schema extension and composition) + +=== Support for Java records + +Java records provide a concise way to declare immutable data carriers with several advantages: + +* Concise syntax that requires less boilerplate code compared to traditional Plain Old Java Objects (POJOs) +* Immutability that promotes safer API contracts +* Type safety that provides compile-time guarantees for your data models +* Clarity of intent that signals the type is a simple data carrier + +MicroProfile OpenAPI 4.1 provides native support for Java records and automatically generates schema definitions from record components. + +==== Basic record schema generation + +MicroProfile OpenAPI automatically discovers record components and generates corresponding OpenAPI schemas. The following example shows a category record for organizing products: + +*Java record definition:* +[source, java] +---- +package io.microprofile.tutorial.store.product.entity; + +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "CategoryRecord", description = "Product category information") +public record CategoryRecord( + @NotNull + @Schema(description = "Category ID", example = "1") + Long id, + + @NotNull + @Schema(description = "Category name", example = "Electronics") + String name, + + @Schema(description = "Parent category ID for hierarchical structure", nullable = true) + Long parentId, + + @NotNull + @Schema(description = "Display order", example = "1") + Integer displayOrder +) {} +---- + +The generated OpenAPI schema for the record: + +[source, yaml] +---- +openapi: 3.1.0 +components: + schemas: + CategoryRecord: + description: Product category information + type: object + required: + - id + - name + - displayOrder + properties: + id: + type: integer + format: int64 + description: Category ID + examples: + - 1 + name: + type: string + description: Category name + examples: + - Electronics + parentId: + type: + - integer + - "null" + format: int64 + description: Parent category ID for hierarchical structure + displayOrder: + type: integer + format: int32 + description: Display order + examples: + - 1 +---- + +Use `@NotNull` validation annotations or `@Schema(required = true)` to explicitly mark components as required in the generated specification. For nullable components, you can either use `@Schema(nullable = true)` or wrap the type in `Optional` to indicate they can accept null values. In this example, `@NotNull` annotations mark `id`, `name`, and `displayOrder` as required, while `parentId` is marked as nullable for root categories that have no parent. + +=== Enhanced annotation type safety + +MicroProfile OpenAPI 4.1 improves type safety by adding explicit `@Target` meta-annotations to several OpenAPI annotations. This enhancement works behind the scenes to help IDEs and build tools provide better warnings when you use annotations in inappropriate locations. + +==== What this means for developers + +The `@Target` meta-annotation defines where an annotation can be applied (classes, methods, fields, parameters, and so on). Prior to MicroProfile OpenAPI 4.1, some annotations lacked explicit `@Target` definitions, which made it harder for development tools to validate annotation usage. + +With MicroProfile OpenAPI 4.1, annotations like `@Schema`, `@SchemaProperty`, and others now have explicit `@Target` constraints that: + +* Provide warnings for incorrect annotation usage +* Help build tools detect potential issues during compilation +* Make the API more predictable and self-documenting +* Improve code completion accuracy in modern IDEs + +==== Example: Compile-time warnings for incorrect usage + +Modern IDEs may provide warnings about improper annotation placement: + +*Questionable usage:* +[source, java] +---- +@Schema(description = "Product resource") // IDE may warn: unusual location for @Schema +@Path("/products") +public class ProductResource { + // While this compiles, the @Schema annotation on the resource class itself + // may not have the intended effect +} +---- + +*Recommended usage:* +[source, java] +---- +@Path("/products") +public class ProductResource { + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Get all products") + @APIResponse( + responseCode = "200", + content = @Content(schema = @Schema(implementation = Product.class)) + ) + public List getAllProducts() { + return productService.findAll(); + } +} +---- + +==== Working with flexible schema types + +The enhanced type safety works seamlessly with flexible schema definitions: + +*Example: Product with dynamic attributes* +[source, java] +---- +package io.microprofile.tutorial.store.product.entity; + +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.media.SchemaProperty; +import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; +import java.util.Map; +import java.util.List; +import lombok.Data; + +@Data +@Schema( + description = "Product with flexible custom attributes", + properties = { + @SchemaProperty(name = "tags") + } +) +public class FlexibleProduct { + + @Schema(description = "Product ID", example = "1") + private Long id; + + @Schema(description = "Product name", example = "Wireless Mouse") + private String name; + + @Schema(description = "Product price", example = "29.99") + private Double price; + + @Schema( + description = "Product specifications (e.g., color, material, size)", + type = SchemaType.OBJECT, + example = "{\"color\": \"black\", \"material\": \"plastic\", \"weight\": \"100g\"}" + ) + private Map specifications; + + @Schema( + description = "Product tags for categorization and search", + type = SchemaType.ARRAY, + implementation = String.class, + example = "[\"wireless\", \"electronics\", \"accessories\"]" + ) + private List tags; +} +---- + +The `@SchemaProperty` annotation allows you to reference properties at the class level, particularly useful when combined with field-level `@Schema` annotations. + +*Generated OpenAPI schema:* +[source, yaml] +---- +FlexibleProduct: + description: Product with flexible custom attributes + properties: + tags: + description: Product tags for categorization and search + type: array + examples: + - - wireless + - electronics + - accessories + items: + type: string + id: + type: integer + format: int64 + description: Product ID + examples: + - 1 + name: + type: string + description: Product name + examples: + - Wireless Mouse + price: + type: number + format: double + description: Product price + examples: + - 29.99 + specifications: + type: object + additionalProperties: {} + description: "Product specifications (e.g., color, material, size)" + examples: + - color: black + material: plastic + weight: 100g + type: object +---- + +==== Benefits of enhanced type safety + +The addition of explicit `@Target` annotations provides: + +* Early error detection: Misused annotations are caught at compile time, not runtime +* Better IDE experience: Code completion only suggests annotations valid for the current context +* Self-documenting API: Annotation definitions clearly communicate their intended use +* Reduced debugging time: Fewer runtime errors related to incorrect annotation placement +* More maintainable code: Clear constraints make it easier for teams to use annotations correctly + +=== JSON Schema dialect support + +MicroProfile OpenAPI 4.1 introduces the `jsonSchemaDialect` property, which allows you to specify which JSON Schema dialect your OpenAPI document uses. This property is part of OpenAPI 3.1's alignment with JSON Schema standards. + +==== Understanding JSON Schema dialects + +A JSON Schema dialect defines which version and vocabulary of JSON Schema is being used. OpenAPI 3.1 supports multiple dialects: + +* Default dialect: `\https://spec.openapis.org/oas/3.1/dialect/base` (the standard OpenAPI 3.1 JSON Schema dialect) +* Full JSON Schema: `\https://json-schema.org/draft/2020-12/schema` (complete JSON Schema 2020-12 specification) +* Custom dialects: Organization-specific or tool-specific schema vocabularies + +When you set the dialect explicitly, you ensure compatibility with tools and validators that support specific JSON Schema versions. + +==== Specifying the dialect programmatically + +You can set the JSON Schema dialect using the `OASModelReader` interface: + +[source, java] +---- +package io.microprofile.tutorial.store.config; + +import org.eclipse.microprofile.openapi.OASFactory; +import org.eclipse.microprofile.openapi.OASModelReader; +import org.eclipse.microprofile.openapi.models.OpenAPI; + +public class CustomModelReader implements OASModelReader { + + @Override + public OpenAPI buildModel() { + return OASFactory.createOpenAPI() + .openapi("3.1.0") + // Use default OpenAPI 3.1 dialect (recommended for most use cases) + .jsonSchemaDialect("https://spec.openapis.org/oas/3.1/dialect/base") + + // Alternative: Use full JSON Schema 2020-12 dialect for advanced features + // .jsonSchemaDialect("https://json-schema.org/draft/2020-12/schema") + + .info(OASFactory.createInfo() + .title("Product Catalog API") + .version("1.0.0") + .description("API for managing products in the e-commerce store")); + } +} +---- + +The full JSON Schema 2020-12 dialect enables advanced features like: + +* `prefixItems` for tuple validation +* `$dynamicRef` and `$dynamicAnchor` for dynamic schema references +* `unevaluatedProperties` and `unevaluatedItems` for strict validation +* Enhanced pattern properties and additional vocabulary extensions + +==== Activating the model reader + +Configure the model reader in `microprofile-config.properties`: + +[source, properties] +---- +mp.openapi.model.reader=io.microprofile.tutorial.store.config.CustomModelReader +---- + +==== Dialect usage + +While you can set the `jsonSchemaDialect` programmatically through the OpenAPI model, the dialect property controls how JSON Schema validation and processing occurs internally. The dialect setting ensures that: + +* Schema validation follows the specified JSON Schema version rules +* Type definitions are interpreted according to the dialect's vocabulary +* Schema composition keywords (`oneOf`, `anyOf`, `allOf`) behave according to the dialect specification +* Nullable types use the appropriate representation for the dialect +* Advanced JSON Schema features are available when using the full 2020-12 dialect + +==== When to specify the dialect + +You should explicitly set the `jsonSchemaDialect` when: + +* Using advanced JSON Schema features: `prefixItems`, `$dynamicRef`, `unevaluatedProperties` +* Integrating with external validators: Tools that require JSON Schema 2020-12 compliance +* Requiring strict schema validation: Need features like `unevaluatedProperties` for comprehensive validation +* Ensuring tool compatibility: Working with schema generators or validators that expect specific dialects +* Using custom vocabularies: Organization-specific schema extensions + +For most applications using standard MicroProfile OpenAPI features, the default dialect (`\https://spec.openapis.org/oas/3.1/dialect/base`) is used automatically and does not need to be explicitly set. + +==== Comparison of dialects + +.JSON Schema dialect feature comparison +[cols="3,2,2", options="header"] +|=== +| Feature and description | OpenAPI 3.1 base dialect | Full JSON Schema 2020-12 + +| *Basic validation:* Basic type validation (string, number, boolean, and so on) +| ✓ Supported +| ✓ Supported + +| *Schema composition:* Combining schemas using `oneOf`, `anyOf`, `allOf` +| ✓ Supported +| ✓ Supported + +| *Nullable types:* Representing values that can be null +| ✓ Supported with type arrays +| ✓ Supported with type arrays + +| *Tuple validation (`prefixItems`):* Validate arrays with different types per position (for example, [`string`, `number`, `boolean`]) +| ✗ Not available +| ✓ Supported + +| *Dynamic references (`$dynamicRef`):* Advanced schema composition with runtime-resolved reference +| ✗ Not available +| ✓ Supported + +| *Strict validation (`unevaluatedProperties`):* Reject additional properties not covered by schema +| ✗ Not available +| ✓ Supported + +| *Custom vocabularies (`$vocabulary`):* Define and use custom validation keywords +| ✗ Not available +| ✓ Supported +| *Primary use case* +| Standard REST API documentation and validation +| Advanced schema validation, tooling integration, strict compliance + +| *Recommended for* +| Most microservices and REST APIs +| Complex validation requirements, schema tooling, external validators |=== -All of these annotations are defined in the org.eclipse.microprofile.openapi.annotations package. +=== Extensible interface methods + +MicroProfile OpenAPI 4.1 adds two new methods to the `Extensible` interface: `getExtension(String)` and `hasExtension(String)`. These methods provide a more convenient way to work with vendor extensions (custom properties prefixed with `x-`) in your OpenAPI model. + +==== Understanding vendor extensions + +Vendor extensions allow you to add custom metadata to your OpenAPI specification beyond the standard properties. Extensions must be prefixed with `x-` and can contain any valid JSON value. Common use cases include: + +* Custom timeout or rate limit configurations +* Internal routing or gateway metadata +* Documentation generation hints +* Tool-specific processing instructions +* Security or compliance annotations + +==== New convenience methods + +Prior to MicroProfile OpenAPI 4.1, checking for and retrieving extensions required working directly with the extensions map. The new methods simplify this process: + +* `hasExtension(String name)`: Returns `true` if the extension exists +* `getExtension(String name)`: Returns the extension value or `null` if not found + +==== Adding extensions to operations + +You can add vendor extensions to operations using the `@Extension` annotation: + +[source, java] +---- +@GET +@Path("/{id}") +@Produces(MediaType.APPLICATION_JSON) +@Operation( + summary = "Get product by ID", + extensions = { + @Extension(name = "x-custom-timeout", value = "60"), + @Extension(name = "x-rate-limit", value = "100"), + @Extension(name = "x-cache-ttl", value = "300") + } +) +public Response getProductById(@PathParam("id") Long id) { + // Method implementation returns product details + return Response.ok(productService.findById(id)).build(); +} +---- + +*Generated OpenAPI specification with extensions:* +[source, yaml] +---- +paths: + /api/products/{id}: + get: + summary: Get product by ID + description: Retrieves detailed product information + x-custom-timeout: 60 + x-rate-limit: 100 + x-cache-ttl: 300 + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Product found +---- + +==== Using extensible interface methods in filters + +OAS filters execute during OpenAPI document generation and allow you to programmatically modify the OpenAPI model before it is served. Filters prove useful when you need to add, modify, or remove content from the OpenAPI specification based on logic that cannot be expressed through annotations alone. + +An `OASFilter` allows you to: + +* Read existing extensions from your annotated code and make decisions based on their values +* Add new extensions dynamically based on business logic or computed values +* Modify the OpenAPI document programmatically at runtime +* Enforce policies across all operations (for example, ensure all operations have certain metadata) +* Integrate with external systems by reading configuration and adding appropriate extensions + +For example, the following filter reads the `x-custom-timeout` extension that was defined in the `@Operation` annotation and uses it to dynamically add another extension (`x-requires-approval`) if the timeout exceeds a threshold: + +[source, java] +---- +package io.microprofile.tutorial.store.product.config; + +import org.eclipse.microprofile.openapi.models.Operation; +import org.eclipse.microprofile.openapi.OASFilter; +import java.util.LinkedHashMap; +import java.util.Map; + +public class ExtensionFilter implements OASFilter { + + @Override + public Operation filterOperation(Operation operation) { + if (operation == null) { + return operation; + } + + try { + // Get existing extensions (or create new map if null) + Map existingExtensions = operation.getExtensions(); + if (existingExtensions == null) { + return operation; // No extensions to process + } + + // Create a new map with existing extensions + Map newExtensions = new LinkedHashMap<>(existingExtensions); + + // Check if a custom timeout extension exists + if (newExtensions.containsKey("x-custom-timeout")) { + Object timeout = newExtensions.get("x-custom-timeout"); + if (timeout != null) { + int timeoutValue = Integer.parseInt(timeout.toString()); + if (timeoutValue > 30) { + newExtensions.put("x-requires-approval", "true"); + } + } + } + + // Check for rate limiting configuration + if (newExtensions.containsKey("x-rate-limit")) { + Object rateLimit = newExtensions.get("x-rate-limit"); + if (rateLimit != null) { + int rateLimitValue = Integer.parseInt(rateLimit.toString()); + if (rateLimitValue > 500) { + newExtensions.put("x-high-volume", "true"); + } + } + } + + // Check for authentication requirements + if (newExtensions.containsKey("x-requires-auth")) { + Object authLevel = newExtensions.get("x-requires-auth"); + if (authLevel != null && "admin".equals(authLevel.toString())) { + newExtensions.put("x-security-notice", + "This operation requires administrator privileges"); + } + } + + // Set the modified extensions map back + operation.setExtensions(newExtensions); + + } catch (Exception e) { + System.err.println("Error in ExtensionFilter: " + e.getMessage()); + e.printStackTrace(); + } + + return operation; + } +} +---- + + +Configure the filter in `microprofile-config.properties`: + +[source, properties] +---- +mp.openapi.filter=io.microprofile.tutorial.store.product.config.ExtensionFilter +---- + +==== Practical use cases for filters + +Filters prove useful for: + +* Dynamic documentation enrichment: Add computed metadata based on existing extensions +* Validation: Log warnings for operations missing required extensions +* Policy enforcement: Ensure all admin operations have proper security annotations +* Metrics: Count operations by type or collect extension statistics +* Gateway configuration: Generate routing or rate-limiting rules for API gateways +* Code generation: Add hints for client SDK generators +* Custom tooling: Process the OpenAPI document programmatically for custom tools + +=== Documenting asynchronous operations with callbacks + +MicroProfile OpenAPI 4.1 supports documenting asynchronous operations that use callbacks. Callbacks prove useful for webhook-style APIs where your service initiates an asynchronous process and notifies the client when it completes by making an HTTP request back to a URL provided by the client. + +==== Understanding callbacks + +A callback in OpenAPI describes an HTTP request that your API will make to the client. Common use cases include: + +* Webhook notifications: Notify clients when long-running operations complete +* Event subscriptions: Send events to client-provided endpoints +* Payment processing: Notify clients of payment status changes +* Batch processing: Alert clients when batch jobs finish +* Order fulfillment: Update clients on order status changes + +The callback pattern works as follows: + +1. Client makes a request to your API and provides a callback URL. +2. Your API accepts the request and returns immediately (typically 202 Accepted). +3. Your API processes the request asynchronously. +4. When complete, your API makes an HTTP request to the client's callback URL. + +==== Documenting callbacks with @Callback + +The `@Callback` annotation documents the HTTP request your API will make back to the client: + +[source, java] +---- +package io.microprofile.tutorial.store.product.resource; + +//... + +@Path("/products") +@ApplicationScoped +@Tag(name = "Products", description = "Async product operations") +public class AsyncProductResource { + + @POST + @Path("/process-async") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Operation( + summary = "Process product asynchronously", + description = "Initiates asynchronous product processing and calls back when complete" + ) + @APIResponse( + responseCode = "202", + description = "Processing initiated" + ) + @Callback( + name = "productProcessed", + callbackUrlExpression = "{$request.body#/callbackUrl}", + operations = { + @CallbackOperation( + method = "post", + summary = "Product processing completed", + description = "Called when async product processing is complete", + requestBody = @RequestBody( + description = "Processing result", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = ProcessResult.class) + ) + ) + ) + } + ) + public Response processProductAsync( + @RequestBody( + description = "Product data and callback URL", + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = AsyncProductRequest.class) + ) + ) AsyncProductRequest request + ) { + // Validate request + if (request.getCallbackUrl() == null || request.getProduct() == null) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Missing required fields") + .build(); + } + + // Initiate async processing + // In real implementation, trigger actual async processing here + + return Response.accepted() + .entity("{\"message\": \"Processing initiated\"}") + .build(); + } +} +---- + +==== Request and response models + +The implementation for `AsyncProductRequest` is as follows: + +[source, java] +---- +package io.microprofile.tutorial.store.product.entity; + +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "Async product processing request") +public class AsyncProductRequest { + + @Valid + @NotNull + @Schema(description = "Product to process", required = true) + private Product product; + + @NotNull + @Schema( + description = "URL to call when processing completes", + required = true, + example = "https://client.example.com/webhooks/product-processed" + ) + private String callbackUrl; +} +---- + +The `ProcessResult` class is implemented as follows: + +[source, java] +---- +package io.microprofile.tutorial.store.product.entity; + +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import java.time.Instant; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "Product processing result") +public class ProcessResult { + + @Schema(description = "ID of the processed product", example = "12345") + private Long productId; + + @Schema(description = "Processing status", example = "SUCCESS") + private ProcessingStatus status; + + @Schema(description = "Result message", example = "Product processed successfully") + private String message; + + @Schema(description = "Processing timestamp", example = "2024-02-04T10:30:00Z") + private String timestamp; + + // Convenience constructor that auto-generates timestamp + public ProcessResult(Long productId, ProcessingStatus status, String message) { + this.productId = productId; + this.status = status; + this.message = message; + this.timestamp = Instant.now().toString(); + } + + @Schema(description = "Processing status values") + public enum ProcessingStatus { + SUCCESS, + FAILED, + PARTIAL + } +} +---- + +==== Generated OpenAPI specification + +The callback is documented in the OpenAPI specification: + +[source, yaml] +---- +paths: + /api/products/process-async: + post: + summary: Process product asynchronously + description: Initiates asynchronous product processing and calls back when complete + requestBody: + description: Product data and callback URL + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AsyncProductRequest' + responses: + '202': + description: Processing initiated + callbacks: + productProcessed: + '{$request.body#/callbackUrl}': + post: + summary: Product processing completed + description: Called when async product processing is complete + requestBody: + description: Processing result + content: + application/json: + schema: + $ref: '#/components/schemas/ProcessResult' + +components: + schemas: + AsyncProductRequest: + description: Async product processing request + type: object + required: + - product + - callbackUrl + properties: + product: + $ref: '#/components/schemas/Product' + callbackUrl: + type: string + description: URL to call when processing completes + example: https://client.example.com/webhooks/product-processed + + ProcessResult: + description: Product processing result + type: object + properties: + productId: + type: integer + format: int64 + description: ID of the processed product + example: 12345 + status: + type: string + description: Processing status + enum: [SUCCESS, FAILED, PARTIAL] + example: SUCCESS + message: + type: string + description: Result message + example: Product processed successfully + timestamp: + type: string + description: Processing timestamp + example: 2024-02-04T10:30:00Z +---- + +==== Understanding the callback components + +The `@Callback` annotation parameters include: + +* `name`: Identifier for this callback (used in the OpenAPI specification) +* `callbackUrlExpression`: JSON Pointer expression to extract the callback URL from the request (for example, `{$request.body#/callbackUrl}` means "Use the `callbackUrl` field from the request body") +* `operations`: Array of `@CallbackOperation` defining what HTTP requests your API will make + +The `@CallbackOperation` parameters include: + +* `method`: HTTP method your API will use (typically `post`) +* `summary`: Brief description of the callback +* `requestBody`: Defines what your API will send to the client's callback URL + +==== Example usage flow + +The following sequence demonstrates the callback pattern: + +*Step 1: Client initiates async processing* + +[source, bash] +---- +POST /api/products/process-async +Content-Type: application/json + +{ + "product": { + "id": 12345, + "name": "Wireless Mouse", + "price": 29.99 + }, + "callbackUrl": "https://client.example.com/webhooks/product-processed" +} +---- + +*Step 2: API responds immediately* + +[source, bash] +---- +HTTP/1.1 202 Accepted +Content-Type: application/json + +{ + "message": "Processing initiated" +} +---- + +*Step 3: API processes asynchronously* + +The system processes the request in the background. + +*Step 4: API calls client's callback URL when complete* + +[source, bash] +---- +POST https://client.example.com/webhooks/product-processed +Content-Type: application/json + +{ + "productId": 12345, + "status": "SUCCESS", + "message": "Product processed successfully", + "timestamp": "2024-02-04T10:30:00Z" +} +---- + +==== Benefits of documenting callbacks + +When you document callbacks in your OpenAPI specification, you provide: + +* Clear contract: Clients know exactly what to expect from your callback +* Implementation guide: Client developers know what endpoint to implement +* Testing support: Tools can generate mock callback endpoints +* Validation: Schema validation ensures your callbacks send correct data +* Code generation: Tools can generate callback handler code for clients + +=== Documenting security schemes + +Security is a critical aspect of API documentation. MicroProfile OpenAPI 4.1 provides comprehensive support for documenting various security schemes including API keys, HTTP authentication, OAuth2, and OpenID Connect. + +==== Defining security schemes + +You define security schemes at the application level using `@SecurityScheme` annotations: + +[source, java] +---- +//... + +@ApplicationPath("/api") +@OpenAPIDefinition( + info = @Info( + title = "Product Catalog API", + version = "1.0.0", + description = "E-commerce product catalog API with multiple security schemes" + ) +) +@SecuritySchemes({ + @SecurityScheme( + securitySchemeName = "apiKey", + type = SecuritySchemeType.APIKEY, + description = "API Key authentication for service-to-service communication", + in = SecuritySchemeIn.HEADER, + apiKeyName = "X-API-Key" + ), + @SecurityScheme( + securitySchemeName = "bearerAuth", + type = SecuritySchemeType.HTTP, + description = "JWT Bearer token authentication for user requests", + scheme = "bearer", + bearerFormat = "JWT" + ), + @SecurityScheme( + securitySchemeName = "oauth2", + type = SecuritySchemeType.OAUTH2, + description = "OAuth2 authentication with authorization code flow", + flows = @OAuthFlows( + authorizationCode = @OAuthFlow( + authorizationUrl = "https://auth.example.com/oauth/authorize", + tokenUrl = "https://auth.example.com/oauth/token", + scopes = { + @OAuthScope( + name = "read:products", + description = "Read product information" + ), + @OAuthScope( + name = "write:products", + description = "Create and modify product information" + ), + @OAuthScope( + name = "delete:products", + description = "Delete products" + ) + } + ) + ) + ) +}) +public class ProductApplication extends Application { +} +---- + +==== Applying security to operations + +You can apply security requirements to individual operations or to entire resource classes. + +*Applying security to individual operations:* + +[source, java] +---- +package io.microprofile.tutorial.store.product.resource; + +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.security.SecurityRequirement; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.media.Content; +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +@Path("/products") +public class SecuredProductResource { + + @GET + @Path("/{id}") + @Produces(MediaType.APPLICATION_JSON) + @Operation( + summary = "Get product by ID", + description = "Retrieves detailed product information. Requires JWT authentication." + ) + @SecurityRequirement(name = "bearerAuth") + @APIResponse( + responseCode = "200", + description = "Product found", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = Product.class) + ) + ) + @APIResponse( + responseCode = "401", + description = "Unauthorized - Missing or invalid JWT token" + ) + @APIResponse( + responseCode = "404", + description = "Product not found" + ) + public Response getSecuredProduct(@PathParam("id") Long id) { + // Security framework verifies JWT token + Product product = productService.findById(id); + + if (product == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + return Response.ok(product).build(); + } + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Operation( + summary = "Create a new product", + description = "Creates a new product in the catalog. Requires OAuth2 write:products scope." + ) + @SecurityRequirement(name = "oauth2", scopes = {"write:products"}) + @APIResponse( + responseCode = "201", + description = "Product created successfully", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = Product.class) + ) + ) + @APIResponse( + responseCode = "401", + description = "Unauthorized - Missing or invalid OAuth2 token" + ) + @APIResponse( + responseCode = "403", + description = "Forbidden - Token lacks required write:products scope" + ) + @APIResponse( + responseCode = "400", + description = "Bad request - Invalid product data" + ) + public Response createSecuredProduct( + @Schema(description = "Product to create", required = true) + Product product + ) { + // Security framework verifies OAuth2 token and scopes + + if (product == null || product.getName() == null) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Product name is required") + .build(); + } + + Product created = productService.create(product); + return Response.status(Response.Status.CREATED) + .entity(created) + .build(); + } +} +---- + +==== Applying security to operations + +You can apply security requirements to individual operations or to entire resource classes. + +*Applying security to individual operations:* + +[source, java] +---- +package io.microprofile.tutorial.store.product.resource; + +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.security.SecurityRequirement; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.media.Content; +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +@Path("/products") +public class SecuredProductResource { + + @GET + @Path("/{id}") + @Produces(MediaType.APPLICATION_JSON) + @Operation( + summary = "Get product by ID", + description = "Retrieves detailed product information. Requires JWT authentication." + ) + @SecurityRequirement(name = "bearerAuth") + @APIResponse( + responseCode = "200", + description = "Product found", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = Product.class) + ) + ) + @APIResponse( + responseCode = "401", + description = "Unauthorized - Missing or invalid JWT token" + ) + @APIResponse( + responseCode = "404", + description = "Product not found" + ) + public Response getSecuredProduct(@PathParam("id") Long id) { + // Security framework verifies JWT token + Product product = productService.findById(id); + + if (product == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + return Response.ok(product).build(); + } + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Operation( + summary = "Create a new product", + description = "Creates a new product in the catalog. Requires OAuth2 write:products scope." + ) + @SecurityRequirement(name = "oauth2", scopes = {"write:products"}) + @APIResponse( + responseCode = "201", + description = "Product created successfully", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = Product.class) + ) + ) + @APIResponse( + responseCode = "401", + description = "Unauthorized - Missing or invalid OAuth2 token" + ) + @APIResponse( + responseCode = "403", + description = "Forbidden - Token lacks required write:products scope" + ) + @APIResponse( + responseCode = "400", + description = "Bad request - Invalid product data" + ) + public Response createSecuredProduct( + @Schema(description = "Product to create", required = true) + Product product + ) { + // Security framework verifies OAuth2 token and scopes + + if (product == null || product.getName() == null) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Product name is required") + .build(); + } + + Product created = productService.create(product); + return Response.status(Response.Status.CREATED) + .entity(created) + .build(); + } +} +---- + +==== Security in Swagger UI + +When you view the documented API in Swagger UI, security schemes appear in several ways: + +* A lock icon (🔒) appears next to secured operations +* An Authorize button allows testers to input credentials +* Required scopes display for each operation +* Operations can be tested with provided authentication + +This makes it easy for API consumers to understand authentication requirements and test secured endpoints interactively. == Summary -By integrating the MicroProfile OpenAPI, developers can generate detailed, OpenAPI-compliant documentation automatically, fostering better understanding and interaction among services. By annotating `ProductResource` class, we generated API documentation as per Open API specification. This will ensure the services are readily discoverable, understandable, and usable, thereby accelerating development cycles and fostering a more robust and collaborative developer ecosystem. +In this chapter, you explored how MicroProfile OpenAPI 4.1 enables automatic generation of comprehensive, OpenAPI v3.1-compliant documentation for your RESTful web services. When you integrate MicroProfile OpenAPI into your Jakarta REST applications, you can create well-documented APIs that are discoverable, understandable, and easy to consume. + +=== Core concepts mastered + +You learned about the OpenAPI Specification, a standard, language-agnostic format for describing REST APIs. You discovered how MicroProfile OpenAPI integrates this specification with Jakarta REST through annotations that automatically generate documentation from your code, which keeps your API contracts synchronized with your implementation. + +=== Key features explored + +Throughout the chapter, you explored MicroProfile OpenAPI 4.1's significant enhancements. + +*OpenAPI v3.1 support* + +You learned how MicroProfile OpenAPI 4.1 fully supports OpenAPI v3.1, bringing important improvements including JSON Schema 2020-12 alignment, improved nullable type handling using standard type arrays (`type: ["string", "null"]`), and enhanced schema composition with `oneOf`, `anyOf`, and `allOf` keywords. You discovered how `Optional` fields are automatically represented as nullable types in the generated specification, which provides clear and standards-compliant documentation. + +*Java records support* + +You explored how MicroProfile OpenAPI 4.1 provides native support for Java records, which allows you to use modern, immutable data carriers like `CategoryRecord` with automatic schema generation. You learned to combine `@Schema` annotations with `@NotNull` validation to create well-documented, type-safe data models. + +*Enhanced type safety* +You discovered how explicit `@Target` meta-annotations improve compile-time checking and IDE support, which helps you use OpenAPI annotations correctly and prevents misuse during development. + +*JSON Schema dialects* + +You learned to specify JSON Schema dialects programmatically using the `jsonSchemaDialect` property, which enables better compatibility with external validators and tooling. You explored the differences between the default OpenAPI 3.1 dialect and full JSON Schema 2020-12 support. + +*Vendor extensions* + +You explored the new `hasExtension()` and `getExtension()` convenience methods that simplify working with vendor extensions (`x-` properties). Through practical examples with `ExtensionFilter`, you learned how to read existing extensions and dynamically add metadata to your OpenAPI documentation for API gateway configuration, monitoring, and policy enforcement. + +*Asynchronous operations* + +You learned to document asynchronous operations using the `@Callback` annotation, which is essential for webhook-style APIs. Using the `AsyncProductRequest` and `ProcessResult` models, you documented how your API initiates async processing and notifies clients when operations complete. + +*Security schemes* + +You explored how to document multiple security schemes including API Keys, JWT Bearer tokens, and OAuth2 with authorization flows. You learned to apply security requirements at both the operation and resource class levels, and how to specify required OAuth2 scopes for fine-grained access control. + +=== What you have learned + +Having finished this chapter, you can now: + +* Automatically generate OpenAPI 3.1 specifications from Jakarta REST code +* Document endpoints using `@Operation`, `@APIResponse`, and `@Tag` annotations +* Construct type-safe schemas using Java records and `@Schema` +* Handle flexible schemas for dynamic attributes +* Document asynchronous operations with callbacks +* Configure multiple security schemes +* Add OAS filters for dynamic documentation +* Interactively Test APIs interactively with Swagger UI + +MicroProfile OpenAPI 4.1's alignment with OpenAPI v3.1 guarentees your API documentation ahdres to the latest standards in the API landscape. This annotation-driven approach keeps documentation in sync with your implementation, thereby avoiding the common problem of outdated specifications. The generated OpenAPI documents facilities: + +* Automatic client SDK generation across multiple programming languages +* Interactive API exploration via Swagger UI +* Integration with API gateways for routing, rate limiting, and security policies +* Contract testing and validation frameworks +* API discovery and cataloging in enterprise environments + +This specification streamlines API documentation, transforming it from a manual, error-prone chore into an automated, reliable process that evolves in tandem with your code. + +With these learnings, your APIs should be now well-documented, and discoverable.