Exposing APIs has become an essential part of all modern applications. At the center of this revolution known as the API Economy we find RESTful APIs, which can transform any application into language agnostic services that can be called from anywhere: on-premises, private cloud, public cloud, etc.
For the clients and providers of these services to connect there needs to be a clear and complete contract. Similar to the WSDL contract for legacy Web Services, the OpenAPI v3.1 specification is the contract for RESTful Services.
This MicroProfile specification, called OpenAPI, aims to provide a set of Java interfaces and programming models which allow Java developers to natively produce OpenAPI v3.1 documents from their applications written using Jakarta RESTful Web Services (Jakarta REST).
There are different ways to augment a Jakarta REST application in order to produce an OpenAPI document, which are described in Documentation Mechanisms. The picture below provides a quick overview of the different types of components that make up the MP OpenAPI specification:
The remaining sections of this specification will go into the details of each component.
Configuration of various parts of this specification is provided via the MicroProfile Config mechanism, which means that vendors implementing the MP OpenAPI specification must also implement the MP Config specification.
There are various ways to inject these configuration values into an MP OpenAPI framework, including the default ConfigSource as well as custom ConfigSource.
Vendors implementing the MP OpenAPI specification can optionally provide additional native ways for these configuration values to be injected into the framework (e.g. via a server configuration file), as long as they also implement the MP Config specification.
Vendors must support all the Core configurations of this specification. Optionally, they may also support Vendor extensions that allow the configuration of framework-specific values for configurations that affect implementation behavior.
For convenience of vendors (and application developers using custom ConfigSources), the full list of supported configuration keys is available as constants in the OASConfig class.
The following is a list of configuration values that every vendor must support.
mp.openapi.model.reader
-
Configuration property to specify the fully qualified name of the OASModelReader implementation.
mp.openapi.filter
-
Configuration property to specify the fully qualified name of the OASFilter implementation.
mp.openapi.scan.disable
-
Configuration property to disable annotation scanning. Default value is
false
. mp.openapi.scan.packages
-
Configuration property to specify the list of packages to scan. Classes within the package and any subpackages will be scanned for annotations. For example,
mp.openapi.scan.packages=com.xyz.packageA,com.xyz.packageB
mp.openapi.scan.classes
-
Configuration property to specify the list of classes to scan. For example,
mp.openapi.scan.classes=com.xyz.MyClassA,com.xyz.MyClassB
mp.openapi.scan.exclude.packages
-
Configuration property to specify the list of packages to exclude from scans. Classes within the package and any subpackages will be excluded from scans. For example,
mp.openapi.scan.exclude.packages=com.xyz.packageC,com.xyz.packageD
mp.openapi.scan.exclude.classes
-
Configuration property to specify the list of classes to exclude from scans. For example,
mp.openapi.scan.exclude.classes=com.xyz.MyClassC,com.xyz.MyClassD
The following rules are used to determine whether a class is scanned for annotations:
-
A class is not scanned if it’s listed in
mp.openapi.scan.exclude.classes
-
A class is scanned if it’s listed in
mp.openapi.scan.classes
-
A class is not scanned if its package, or any of its parent packages are listed in
mp.openapi.scan.exclude.packages
, unless a more complete package or parent package is listed inmp.openapi.scan.packages
-
A class is scanned if its package or any of its parent packages are listed in
mp.openapi.scan.packages
-
A class is scanned if
mp.openapi.scan.classes
andmp.openapi.scan.packages
are both empty or not set
-
mp.openapi.scan.beanvalidation
-
Configuration property to enable or disable the scanning and processing of Jakarta Bean Validation annotations. Defaults to
true
. mp.openapi.servers
-
Configuration property to specify the list of global servers that provide connectivity information. For example,
mp.openapi.servers=https://xyz.com/v1,https://abc.com/v1
mp.openapi.servers.path.
-
Prefix of the configuration property to specify an alternative list of servers to service all operations in a path. For example,
mp.openapi.servers.path./airlines/bookings/{id}=https://xyz.io/v1
mp.openapi.servers.operation.
-
Prefix of the configuration property to specify an alternative list of servers to service an operation. Operations that want to specify an alternative list of servers must define an
operationId
, a unique string used to identify the operation. For example,mp.openapi.servers.operation.getBooking=https://abc.io/v1
mp.openapi.schema.
-
Prefix of the configuration property to specify a schema for a specific class, in JSON format. The remainder of the property key must be the fully-qualified class name. The value must be a valid OpenAPI schema object, specified in the JSON format. The use of this property is functionally equivalent to the use of the
@Schema
annotation on a Java class, but may be used in cases where the application developer does not have access to the source code of a class.When a
name
key is provided with a string value, the schema will be added to theschemas
collection in thecomponents
object of the resulting OpenAPI document usingname
's value as the key.For example, in the case where an application wishes to represent Java
Date
s in epoch milliseconds, the following configuration could be used (line escapes and indentation added for readability):mp.openapi.schema.java.util.Date = { \ "name": "EpochMillis", \ "type": "number", \ "format": "int64", \ "description": "Milliseconds since January 1, 1970, 00:00:00 GMT" \ }
There are many different ways to provide input for the generation of the resulting OpenAPI document.
The MP OpenAPI specification requires vendors to produce a valid OpenAPI document
from pure Jakarta REST applications. This means that vendors must process all the
relevant Jakarta REST annotations (such as @Path
and @Consumes
) as well as Java objects
(POJOs) used as input or output to Jakarta REST operations. This is a good place to
start for application developers that are new to OpenAPI: just deploy your existing
Jakarta REST application into a MP OpenAPI vendor and check out the output from /openapi
!
The application developer then has a few choices:
-
Augment those Jakarta REST annotations with the OpenAPI Annotations. Using annotations means developers don’t have to re-write the portions of the OpenAPI document that are already covered by the Jakarta REST framework (e.g. the HTTP method of an operation).
-
Take the initial output from
/openapi
as a starting point to document your APIs via Static OpenAPI files. It’s worth mentioning that these static files can also be written before any code, which is an approach often adopted by enterprises that want to lock-in the contract of the API. In this case, we refer to the OpenAPI document as the "source of truth", by which the client and provider must abide. -
Use the Programming model to provide a bootstrap (or complete) OpenAPI model tree.
Additionally, a Filter is described which can update the OpenAPI model after it has been built from the previously described documentation mechanisms.
Many of these annotations were derived from the Swagger Core library, which allows for a mostly-mechanical transformation of applications that are using that library and wish to take advantage to the official MP OpenAPI interfaces.
The following annotations are found in the org.eclipse.microprofile.openapi.annotations package.
Annotation | Description |
---|---|
Represents a callback URL that will be invoked. |
|
Represents an array of Callback URLs that can be invoked. |
|
Represents an operation that will be invoked during the callback. |
|
A container that holds various reusable objects for different aspects of the OpenAPI Specification. |
|
Enumeration used to define the value of the |
|
Enumeration representing the parameter’s |
|
Enumeration for the parameter’s |
|
Enumeration for the security scheme’s |
|
Enumeration for the security scheme’s |
|
Adds an extension with contained properties. |
|
Adds custom properties to an extension. |
|
References an external resource for extended documentation. |
|
Describes a single header object. |
|
Contact information for the exposed API. |
|
This annotation encapsulates metadata about the API. |
|
License information for the exposed API. |
|
Represents a design-time link for a response. |
|
Represents a parameter to pass to the linked operation. |
|
Provides schema and examples for a particular media type. |
|
Used within |
|
Used within |
|
Used to differentiate between other schemas which may satisfy the payload description. |
|
Single encoding definition to be applied to single Schema Object. |
|
Illustrates an example of a particular content. |
|
Used within |
|
Allows the definition of input and output data types. |
|
Allows the definition of a property nested within a parent @Schema. |
|
General metadata for an OpenAPI definition. |
|
Describes an operation or typically a HTTP method against a specific path. |
|
Describes a single operation parameter. |
|
Encapsulates input parameters. |
|
Describes a single request body. |
|
Describes a single request body with schema implementation class. |
|
Describes a set of operations available at the same location. Mostly only used to document webhooks as the paths for operations within the application can be discovered from Jakarta REST resources. |
|
Used within |
|
Describes a single response from an API operation. |
|
A container for multiple responses from an API operation. |
|
Describes a single response with schema implementation class from an API operation. |
|
Configuration details for a supported OAuth Flow. |
|
Allows configuration of the supported OAuth Flows. |
|
Represents an OAuth scope. |
|
Specifies a security requirement for an operation. |
|
Represents an array of security requirements where only one needs to be satisfied. |
|
Represents an array of security requirements that need to be satisfied. |
|
Defines a security scheme that can be used by the operations. |
|
Represents an array of security schemes that can be specified. |
|
Represents a server used in an operation or used by all operations in an OpenAPI document. |
|
A container for multiple server definitions. |
|
Represents a server variable for server URL template substitution. |
|
Represents a tag for the API endpoint. |
|
A container of multiple tags. |
When the same annotation is used on a class and a method, the values from the
method instance will take precedence for that particular method. This commonly
occurs with the @Server
and @Tag
annotations.
In other cases, such as with @Parameter
and @RequestBody
, the annotation values
from the method’s parameters takes precedence over corresponding annotation values
from the method itself - in this scenario the combined usage of these annotations is
allowed but discouraged, as it is error prone.
The @Schema
annotation has a complex set of possible combinations. It can placed
on POJOs (and their fields / methods) and referenced from many other annotations.
In the event that a @Schema#implementation
value points to a POJO that also contains
a @Schema
annotation, the values are merged but with precedence given to
the referrer annotation (i.e. the one that contains the implementation
key).
This allows POJO models to be reusable and configurable.
@GET
@Path("/findByStatus")
@Operation(summary = "Finds Pets by status",
description = "Multiple status values can be provided with comma separated strings")
public Response findPetsByStatus(...) { ... }
/pet/findByStatus:
get:
summary: Finds Pets by status
description: Multiple status values can be provided with comma separated strings
operationId: findPetsByStatus
@GET
@Path("/{username}")
@Operation(summary = "Get user by user name")
@APIResponse(description = "The user",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = User.class))),
@APIResponse(responseCode = "400", description = "User not found")
public Response getUserByName(
@Parameter(description = "The name that needs to be fetched. Use user1 for testing. ", required = true) @PathParam("username") String username)
{...}
/user/{username}:
get:
summary: Get user by user name
operationId: getUserByName
parameters:
- name: username
in: path
description: 'The name that needs to be fetched. Use user1 for testing. '
required: true
schema:
type: string
responses:
default:
description: The user
content:
application/json:
schema:
$ref: '#/components/schemas/User'
400:
description: User not found
@POST
@Path("/user")
@Operation(summary = "Create user",
description = "This can only be done by the logged in user.")
public Response methodWithRequestBody(
@RequestBody(description = "Created user object", required = true,
content = @Content(schema = @Schema(implementation = User.class))) User user,
@QueryParam("name") String name, @QueryParam("code") String code)
{ ... }
post:
summary: Create user
description: This can only be done by the logged in user.
operationId: methodWithRequestBody
parameters:
- name: name
in: query
schema:
type: string
- name: code
in: query
schema:
type: string
requestBody:
description: Created user object
content:
'*/*':
schema:
$ref: '#/components/schemas/User'
required: true
responses:
default:
description: no description
@OpenAPIDefinition(
servers = {
@Server(
description = "definition server 1",
url = "http://{var1}.definition1/{var2}",
variables = {
@ServerVariable(name = "var1",
description = "var 1",
defaultValue = "1",
enumeration = {"1", "2"}),
@ServerVariable(name = "var2",
description = "var 2",
defaultValue = "1",
enumeration = {"1", "2"})})})
@Server(
description = "class server 1",
url = "http://{var1}.class1/{var2}",
variables = {
@ServerVariable(
name = "var1",
description = "var 1",
defaultValue = "1",
enumeration = {"1", "2"}),
@ServerVariable(
name = "var2",
description = "var 2",
defaultValue = "1",
enumeration = {"1", "2"})})
@Server(
description = "class server 2",
url = "http://{var1}.class2",
variables = {
@ServerVariable(
name = "var1",
description = "var 1",
defaultValue = "1",
enumeration = {"1", "2"})})
public class ServersResource {
@GET
@Path("/")
@Server(
description = "method server 1",
url = "http://{var1}.method1",
variables = {
@ServerVariable(
name = "var1",
description = "var 1",
defaultValue = "1",
enumeration = {"1", "2"})})
@Server(
description = "method server 2",
url = "http://method2"
)
public Response getServers() {
return Response.ok().entity("ok").build();
}
}
openapi: 3.1.0
servers:
- url: http://{var1}.definition1/{var2}
description: definition server 1
variables:
var1:
description: var 1
enum:
- "1"
- "2"
default: "1"
var2:
description: var 2
enum:
- "1"
- "2"
default: "1"
paths:
/:
get:
operationId: getServers
responses:
default:
description: default response
servers:
- url: http://{var1}.class1/{var2}
description: class server 1
variables:
var1:
description: var 1
enum:
- "1"
- "2"
default: "1"
var2:
description: var 2
enum:
- "1"
- "2"
default: "1"
- url: http://{var1}.class2
description: class server 2
variables:
var1:
description: var 1
enum:
- "1"
- "2"
default: "1"
- url: http://{var1}.method1
description: method server 1
variables:
var1:
description: var 1
enum:
- "1"
- "2"
default: "1"
- url: http://method2
description: method server 2
variables: {}
@Schema(name="MyBooking", description="POJO that represents a booking.")
public class Booking {
@Schema(required = true, example = "32126319")
private String airMiles;
@Schema(required = true, example = "window")
private String seatPreference;
}
components:
schemas:
MyBooking:
description: POJO that represents a booking.
required:
- airMiles
- seatPreference
type: object
properties:
airMiles:
type: string
example: "32126319"
seatPreference:
type: string
example: window
@POST
public Response createBooking(
@RequestBody(description = "Create a new booking.",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = Booking.class))) Booking booking) {
post:
operationId: createBooking
requestBody:
description: Create a new booking.
content:
application/json:
schema:
$ref: '#/components/schemas/MyBooking'
For more samples please see the MicroProfile Wiki.
In some cases, additional schema restrictions can be inferred from Jakarta Bean Validation annotations and used to enhance the generated OpenAPI document.
If an implementation includes support for the Jakarta Bean Validation specification, then it must also process Jakarta Bean Validation annotations when creating OpenAPI schemas. Such implementations must add the properties listed in the table below to the schema model when:
-
the annotation is applied to to an element for which a schema is generated and
-
the annotation and generated schema type are listed together in the table below and
-
the annotation has a
group
attribute which is empty or includesjakarta.validation.groups.Default
and -
the user has not set any of the relevant property values using other annotations and
-
processing of bean validation annotations has not been disabled via configuration
Annotation |
Schema type |
Schema properties to set |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Application developers may wish to include a pre-generated OpenAPI document that was written separately from the code (e.g. with an editor such as this).
Depending on the scenario, the document may be fully complete or partially complete.
If a document is fully complete then the application developer will want to set the
mp.openapi.scan.disable
configuration property to true
. If a document is partially
complete, then the application developer will need to augment the OpenAPI snippet
with annotations, programming model, or via the filter.
Vendors are required to fetch a single document named openapi
with an extension
of yml
, yaml
or json
, inside the application module’s root
META-INF
folder. If there is more than one document found that matches one of
these extensions the behavior of which file is chosen is undefined (i.e. each vendor
may implement their own logic), which means that application developers should
only place a single openapi
document into that folder.
For convenience, you may also place your microprofile-config.properties
in the
root META-INF
folder, if you wish to keep both documents in the same directory.
This is in addition to the default locations defined by MicroProfile Config.
Application developers are able to provide OpenAPI elements via Java POJOs. The complete set of models are found in the org.eclipse.microprofile.openapi.models package.
The OASFactory is used to create all of the elements of an OpenAPI tree.
For example, the following snippet creates a simple Info element that contains a title, description, and version.
OASFactory.createObject(Info.class).title("Airlines").description("Airlines APIs").version("1.0.0");
The OASModelReader interface allows application developers to bootstrap the OpenAPI model tree
used by the processing framework. To use it, simply create an implementation of
this interface and register it using the mp.openapi.model.reader
configuration
key, where the value is the fully qualified name of the reader class.
mp.openapi.model.reader=com.mypackage.MyModelReader
Similar to static files, the model reader can be used to provide either complete
or partial model trees. If providing a complete OpenAPI model tree, application
developers should set the mp.openapi.scan.disable
configuration to true
.
Otherwise this partial model will be used as the base model during the processing
of the other Documentation Mechanisms.
Vendors are required to call the OASReader a single time, in the order defined by the Processing rules section. Only a single OASReader instance is allowed per application.
There are many scenarios where application developers may wish to update or remove certain elements and fields of the OpenAPI document. This is done via a filter, which is called once after all other documentation mechanisms have completed.
The OASFilter interface allows application developers
to receive callbacks for various key OpenAPI elements. The interface has a default
implementation for every method, which allows application developers to only override
the methods they care about. To use it, simply create an implementation of
this interface and register it using the mp.openapi.filter
configuration
key, where the value is the fully qualified name of the filter class.
mp.openapi.filter=com.mypackage.MyFilter
Vendors are required to call the registered filter once for each filtered element.
For example, the method filterPathItem
is called for each corresponding PathItem
element in the model tree. This allows application developers to filter the element
and any of its descendants.
The order of filter methods called is undefined, with two exceptions:
-
All filterable descendant elements of a filtered element must be called before its ancestor.
-
The
filterOpenAPI
method must be the last method called on a filter (which is just a specialization of the first exception).
The processed document available from the OpenAPI Endpoint is built from a variety of sources, which were outlined in the sub-headings of Documentation Mechanisms. Vendors are required to process these different sources in the following order:
-
Fetch configuration values from
mp.openapi
namespace -
Call OASModelReader
-
Fetch static OpenAPI file
-
Process annotations
-
Filter model via OASFilter
Example processing:
-
A vendor starts by fetching all available Configuration. If an
OASModelReader
was specified in that configuration list, itsbuildModel
method is called to form the starting OpenAPI model tree for this application. -
Any Vendor extensions are added on top of that starting model (overriding conflicts), or create a new model if an
OASModelReader
was not registered. -
The vendor searches for a file as defined in the section Static OpenAPI files. If found, it will read that document and merge with the model produced by previous processing steps (if any), where conflicting elements from the static file will override the values from the original model.
-
If annotation scanning was not disabled, the Jakarta REST and OpenAPI annotations from the application will be processed, further overriding any conflicting elements from the current model.
-
The final model is filtered by walking the model tree and invoking all registered OASFilter classes.
A fully processed OpenAPI document must be served from the root
URL /openapi
in response to an HTTP GET
request if any of the
following conditions are met:
-
an
OASModelReader
has been configured withmp.openapi.model.reader
-
an
OASFilter
has been configured withmp.openapi.filter
-
one of the allowed static files is present, i.e.
META-INF/openapi.(json|yaml|yml)
-
the application uses Jakarta REST
For example, GET http://myHost:myPort/openapi
.
This document represents the result of the applied Processing rules.
The protocol required is http
. Vendors are encouraged, but not required, to
support the https
protocol as well, to enable a secure connection to the OpenAPI
endpoint.
The default format of the /openapi
endpoint is YAML
.
Vendors must also support the JSON
format if the request contains an Accept
header with a value of application/json
, in which case the response must contain
a Content-Type
header with a value of application/json
.
No query parameters are required for the /openapi
endpoint. However, one
suggested but optional query parameter for vendors to support is format
,
where the value can be either JSON
or YAML
, to facilitate the toggle between
the default YAML
format and JSON
format.
Vendors are required to ensure that the combination of each global server
element and pathItem element resolve to the absolute backend URL of that
particular path. If that pathItem
contains a servers
element , then this
list of operation-level server
elements replaces the global list of servers
for that particular pathItem
.
For example: an application may have an ApplicationPath
annotation with the
value of /
, but is assigned the context root of /myApp
during deployment. In
this case, the server
elements (either global or operation-level) must either
end with /myApp
or a corresponding proxy. Alternatively it is valid, but discouraged, to
add that context root (/myApp
) to every pathItem
defined in that application.
The MicroProfile OpenAPI specification does not define how
the /openapi
endpoint may be partitioned in the event that the MicroProfile
runtime supports deployment of multiple applications. If an implementation wishes
to support multiple applications within a MicroProfile runtime, the semantics of
the /openapi
endpoint are expected to be the logical union of all the applications
in the runtime, which would imply merging multiple OpenAPI documents into a single
valid document (handling conflicting IDs and unique names).
This section will outline specific integrations between MicroProfile OpenAPI and other MicroProfile specifications.
It is common that a microservice (A) using MicroProfile OpenAPI will also use MicroProfile Rest Client to make outbound calls into another microservice (B). In this case, we do not want the interface for microservice (B) to appear in microservice (A)'s OAS3 document.
Therefore, vendors are required to exclude from the final OAS3 document any interface annotated with org.eclipse.microprofile.rest.client.inject.RegisterRestClient.
The MicroProfile OpenAPI spec does not require vendors to
support multiple languages based on the Accept-Language
. One reasonable
approach is for vendors to support unique keys (instead of hardcoded text) via
the various Documentation Mechanisms, so that the implementing framework can
perform a global replacement of the keys with the language-specific text that
matches the Accept-Language
request for the /openapi
endpoint. A cache of
processed languages can be kept to improve performance.
The MP OpenAPI specification does not mandate vendors to validate the resulting OpenAPI v3.1 model (after processing the 5 steps previously mentioned), which means that the behavior of invalid models is vendor specific (i.e. vendors may choose to ignore, reject, or pass-through invalid inputs).
The MP OpenAPI specification does not mandate but recommends vendors support CORS
for the /openapi
endpoint. Without CORS support, tools such as Swagger-UI might experience some errors.
However, the behavior of CORS requests is implementation dependent.