This guide explains how your Quarkus application can expose its API description through an OpenAPI specification and how you can test it via a user-friendly UI named Swagger UI.
- Prerequisites
- Architecture
- Solution
- Creating the Maven project
- Expose a REST Resource
- Expose OpenAPI Specifications
- Providing Application Level OpenAPI Annotations
- Enhancing the OpenAPI Schema with Filters
- Loading OpenAPI Schema From Static Files
- Changing the OpenAPI version
- Auto-generation of Operation Id
- Use Swagger UI for development
- Configuration Reference
Prerequisites
To complete this guide, you need:
-
Roughly 15 minutes
-
An IDE
-
JDK 17+ installed with
JAVA_HOME
configured appropriately -
Apache Maven 3.9.8
-
Optionally the Quarkus CLI if you want to use it
-
Optionally Mandrel or GraalVM installed and configured appropriately if you want to build a native executable (or Docker if you use a native container build)
Architecture
In this guide, we create a straightforward REST application to demonstrate how fast you can expose your API specification and benefit from a user interface to test it.
Solution
We recommend that you follow the instructions in the next sections and create the application step by step. However, you can skip right to the completed example.
Clone the Git repository: git clone https://github.com/quarkusio/quarkus-quickstarts.git
, or download an archive.
The solution is located in the openapi-swaggerui-quickstart
directory.
Creating the Maven project
First, we need a new project. Create a new project with the following command:
For Windows users:
-
If using cmd, (don’t use backward slash
\
and put everything on the same line) -
If using Powershell, wrap
-D
parameters in double quotes e.g."-DprojectArtifactId=openapi-swaggerui-quickstart"
Expose a REST Resource
We will create a Fruit
bean and a FruitResource
REST resource
(feel free to take a look to the Writing JSON REST services guide if your want more details on how to build a REST API with Quarkus).
package org.acme.openapi.swaggerui;
public class Fruit {
public String name;
public String description;
public Fruit() {
}
public Fruit(String name, String description) {
this.name = name;
this.description = description;
}
}
package org.acme.openapi.swaggerui;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.Path;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Set;
@Path("/fruits")
public class FruitResource {
private Set<Fruit> fruits = Collections.newSetFromMap(Collections.synchronizedMap(new LinkedHashMap<>()));
public FruitResource() {
fruits.add(new Fruit("Apple", "Winter fruit"));
fruits.add(new Fruit("Pineapple", "Tropical fruit"));
}
@GET
public Set<Fruit> list() {
return fruits;
}
@POST
public Set<Fruit> add(Fruit fruit) {
fruits.add(fruit);
return fruits;
}
@DELETE
public Set<Fruit> delete(Fruit fruit) {
fruits.removeIf(existingFruit -> existingFruit.name.contentEquals(fruit.name));
return fruits;
}
}
You can also create a test:
package org.acme.openapi.swaggerui;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import jakarta.ws.rs.core.MediaType;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.containsInAnyOrder;
@QuarkusTest
public class FruitResourceTest {
@Test
public void testList() {
given()
.when().get("/fruits")
.then()
.statusCode(200)
.body("$.size()", is(2),
"name", containsInAnyOrder("Apple", "Pineapple"),
"description", containsInAnyOrder("Winter fruit", "Tropical fruit"));
}
@Test
public void testAdd() {
given()
.body("{\"name\": \"Pear\", \"description\": \"Winter fruit\"}")
.header("Content-Type", MediaType.APPLICATION_JSON)
.when()
.post("/fruits")
.then()
.statusCode(200)
.body("$.size()", is(3),
"name", containsInAnyOrder("Apple", "Pineapple", "Pear"),
"description", containsInAnyOrder("Winter fruit", "Tropical fruit", "Winter fruit"));
given()
.body("{\"name\": \"Pear\", \"description\": \"Winter fruit\"}")
.header("Content-Type", MediaType.APPLICATION_JSON)
.when()
.delete("/fruits")
.then()
.statusCode(200)
.body("$.size()", is(2),
"name", containsInAnyOrder("Apple", "Pineapple"),
"description", containsInAnyOrder("Winter fruit", "Tropical fruit"));
}
}
Expose OpenAPI Specifications
Quarkus provides the Smallrye OpenAPI extension compliant with the MicroProfile OpenAPI specification in order to generate your API OpenAPI v3 specification.
You just need to add the openapi
extension to your Quarkus application:
quarkus extension add quarkus-smallrye-openapi
./mvnw quarkus:add-extension -Dextensions='quarkus-smallrye-openapi'
./gradlew addExtension --extensions='quarkus-smallrye-openapi'
This will add the following to your build file:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
implementation("io.quarkus:quarkus-smallrye-openapi")
Now, we are ready to run our application:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
Once your application is started, you can make a request to the default /q/openapi
endpoint:
$ curl http://localhost:8080/q/openapi
openapi: 3.0.3
info:
title: Generated API
version: "1.0"
paths:
/fruits:
get:
responses:
200:
description: OK
content:
application/json: {}
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Fruit'
responses:
200:
description: OK
content:
application/json: {}
delete:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Fruit'
responses:
200:
description: OK
content:
application/json: {}
components:
schemas:
Fruit:
properties:
description:
type: string
name:
type: string
If you do not like the default endpoint location
|
You can request the OpenAPI in JSON format using the
|
Hit CTRL+C
to stop the application.
Providing Application Level OpenAPI Annotations
There are some MicroProfile OpenAPI annotations which describe global API information, such as the following:
-
API Title
-
API Description
-
Version
-
Contact Information
-
License
All of this information (and more) can be included in your Java code by using appropriate OpenAPI annotations
on a Jakarta REST Application
class. Because a Jakarta REST Application
class is not required in Quarkus, you will
likely have to create one. It can simply be an empty class that extends jakarta.ws.rs.core.Application
. This
empty class can then be annotated with various OpenAPI annotations such as @OpenAPIDefinition
. For example:
@OpenAPIDefinition(
tags = {
@Tag(name="widget", description="Widget operations."),
@Tag(name="gasket", description="Operations related to gaskets")
},
info = @Info(
title="Example API",
version = "1.0.1",
contact = @Contact(
name = "Example API Support",
url = "http://exampleurl.com/contact",
email = "techsupport@example.com"),
license = @License(
name = "Apache 2.0",
url = "https://www.apache.org/licenses/LICENSE-2.0.html"))
)
public class ExampleApiApplication extends Application {
}
Another option, that is a feature provided by SmallRye and not part of the specification, is to use configuration to add this global API information.
This way, you do not need to have a Jakarta REST Application
class, and you can name the API differently per environment.
For example, add the following to your application.properties
:
quarkus.smallrye-openapi.info-title=Example API
%dev.quarkus.smallrye-openapi.info-title=Example API (development)
%test.quarkus.smallrye-openapi.info-title=Example API (test)
quarkus.smallrye-openapi.info-version=1.0.1
quarkus.smallrye-openapi.info-description=Just an example service
quarkus.smallrye-openapi.info-terms-of-service=Your terms here
quarkus.smallrye-openapi.info-contact-email=techsupport@example.com
quarkus.smallrye-openapi.info-contact-name=Example API Support
quarkus.smallrye-openapi.info-contact-url=http://exampleurl.com/contact
quarkus.smallrye-openapi.info-license-name=Apache 2.0
quarkus.smallrye-openapi.info-license-url=https://www.apache.org/licenses/LICENSE-2.0.html
This will give you similar information as the @OpenAPIDefinition
example above.
Enhancing the OpenAPI Schema with Filters
You can change the Generated OpenAPI Schema using one or more filter. Filters can run during build time, or at runtime.
/**
* Filter to add custom elements
*/
@OpenApiFilter(OpenApiFilter.RunStage.BUILD) (1)
public class MyBuildTimeFilter implements OASFilter { (2)
private IndexView view;
public MyBuildTimeFilter(IndexView view) { (3)
this.view = view;
}
@Override
public void filterOpenAPI(OpenAPI openAPI) { (4)
Collection<ClassInfo> knownClasses = this.view.getKnownClasses();
Info info = OASFactory.createInfo();
info.setDescription("Created from Annotated Buildtime filter with " + knownClasses.size() + " known indexed classes");
openAPI.setInfo(info);
}
}
1 | Annotate method with @OpenApiFilter and the run stage (BUILD,RUN,BOTH) |
2 | Implement OASFilter and override any of the methods |
3 | For Build stage filters, the index can be passed in (optional) |
4 | Get a hold of the generated OpenAPI Schema, and enhance as required |
Remember that setting fields on the schema will override what has been generated, you might want to get and add to (so modify). Also know that the generated values might be null, so you will have to check for that.
Loading OpenAPI Schema From Static Files
Instead of dynamically creating OpenAPI schemas from annotation scanning, Quarkus also supports serving static OpenAPI documents.
The static file to serve must be a valid document conforming to the OpenAPI specification.
An OpenAPI document that conforms to the OpenAPI Specification is itself a valid JSON object, that can be represented in yaml
or json
formats.
To see this in action, we’ll put OpenAPI documentation under META-INF/openapi.yaml
for our /fruits
endpoints.
Quarkus also supports alternative OpenAPI document paths if you prefer.
openapi: 3.0.1
info:
title: Static OpenAPI document of fruits resource
description: Fruit resources Open API documentation
version: "1.0"
servers:
- url: http://localhost:8080/q/openapi
description: Optional dev mode server description
paths:
/fruits:
get:
responses:
200:
description: OK - fruits list
content:
application/json: {}
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Fruit'
responses:
200:
description: new fruit resource created
content:
application/json: {}
delete:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Fruit'
responses:
200:
description: OK - fruit resource deleted
content:
application/json: {}
components:
schemas:
Fruit:
properties:
description:
type: string
name:
type: string
By default, a request to /q/openapi
will serve the combined OpenAPI document from the static file and the model generated from application endpoints code.
We can however change this to only serve the static OpenAPI document by adding mp.openapi.scan.disable=true
configuration into application.properties
.
Now, a request to /q/openapi
endpoint will serve the static OpenAPI document instead of the generated one.
About OpenAPI document paths
Quarkus supports various paths to store your OpenAPI document under. We recommend you place it under
Live reload of static OpenAPI document is supported during development. A modification to your OpenAPI document will be picked up on fly by Quarkus. |
Changing the OpenAPI version
By default, when the document is generated, the OpenAPI version used will be 3.0.3
. If you use a static file as mentioned above, the version in the file
will be used. You can also define the version in SmallRye using the following configuration:
mp.openapi.extensions.smallrye.openapi=3.0.2
This might be useful if your API goes through a Gateway that needs a certain version.
Auto-generation of Operation Id
The Operation Id can be set using the @Operation
annotation, and is in many cases useful when using a tool to generate a client stub from the schema.
The Operation Id is typically used for the method name in the client stub. In SmallRye, you can auto-generate this Operation Id by using the following configuration:
mp.openapi.extensions.smallrye.operationIdStrategy=METHOD
Now you do not need to use the @Operation
annotation. While generating the document, the method name will be used for the Operation Id.
Property value | Description |
---|---|
|
Use the method name. |
|
Use the class name (without the package) plus the method. |
|
Use the class name plus the method name. |
Use Swagger UI for development
When building APIs, developers want to test them quickly. Swagger UI is a great tool permitting to visualize and interact with your APIs. The UI is automatically generated from your OpenAPI specification.
The Quarkus smallrye-openapi
extension comes with a swagger-ui
extension embedding a properly configured Swagger UI page.
By default, Swagger UI is only available when Quarkus is started in dev or test mode. If you want to make it available in production too, you can include the following configuration in your
This is a build time property, it cannot be changed at runtime after your application is built. |
By default, Swagger UI is accessible at /q/swagger-ui
.
You can update the /swagger-ui
sub path by setting the quarkus.swagger-ui.path
property in your application.properties
:
quarkus.swagger-ui.path=my-custom-path
The value |
Now, we are ready to run our application:
./mvnw compile quarkus:dev
You can check the Swagger UI path in your application’s log:
00:00:00,000 INFO [io.qua.swa.run.SwaggerUiServletExtension] Swagger UI available at /q/swagger-ui
Once your application is started, you can go to http://localhost:8080/q/swagger-ui and play with your API.
You can visualize your API’s operations and schemas.
You can also interact with your API in order to quickly test it.
Hit CTRL+C
to stop the application.
Styling
You can style the swagger ui by supplying your own logo and css.
Logo
To supply your own logo, you need to place a file called logo.png
in src/main/resources/META-INF/branding
.
This will set the logo for all UIs (not just swagger ui), so in this case also GraphQL-UI and Health-UI (if included).
If you only want to apply this logo to swagger-ui (and not globally to all UIs) call the file smallrye-open-api-ui.png
rather than logo.png
.
CSS
To supply your own css that override/enhance style in the html, you need to place a file called style.css
in src/main/resources/META-INF/branding
.
This will add that css to all UIs (not just swagger ui), so in this case also GraphQL-UI and Health-UI (if included).
If you only want to apply this style to swagger-ui (and not globally to all UIs) call the file smallrye-open-api-ui.css
rather than style.css
.
For more information on styling, read this blog entry: https://quarkus.io/blog/stylish-api/
Cross Origin Resource Sharing
If you plan to consume this application from a Single Page Application running on a different domain, you will need to configure CORS (Cross-Origin Resource Sharing). Please read the CORS filter section of the "Cross-origin resource sharing" guide for more details.
Configuration Reference
OpenAPI
Configuration property fixed at build time - All other configuration properties are overridable at runtime
Configuration property |
Type |
Default |
---|---|---|
string |
|
|
path |
||
boolean |
|
|
boolean |
|
|
If management interface is turned on the openapi schema document will be published under the management interface. This allows you to exclude OpenAPI from management by setting the value to false Environment variable: |
boolean |
|
list of path |
||
|
||
string |
|
|
string |
|
|
Map<String,String> |
||
boolean |
|
|
boolean |
|
|
boolean |
||
boolean |
|
|
string |
||
string |
||
string |
|
|
string |
|
|
string |
|
|
string |
|
|
string |
|
|
string |
||
string |
||
string |
||
string |
||
string |
||
string |
||
string |
||
string |
||
string |
||
string |
||
string |
||
string |
||
string |
||
string |
||
|
||
boolean |
|
|
list of string |
Swagger UI
Configuration property fixed at build time - All other configuration properties are overridable at runtime
Configuration property |
Type |
Default |
---|---|---|
string |
|
|
boolean |
|
|
Map<String,String> |
||
string |
||
string |
||
|
||
string |
||
boolean |
||
boolean |
||
int |
||
int |
||
string |
||
boolean |
||
|
||
If set, enables filtering. The top bar will show an edit box that you can use to filter the tagged operations that are shown. Can be Boolean to enable or disable, or a string, in which case filtering will be enabled using that string as the filter expression. Filtering is case-sensitive matching the filter expression anywhere inside the tag. Environment variable: |
string |
|
int |
||
Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically), 'method' (sort by HTTP method) or a function (see Array.prototype.sort() to know how sort function works). Default is the order returned by the server unchanged. Environment variable: |
string |
|
boolean |
||
boolean |
||
Apply a sort to the tag list of each API. It can be 'alpha' (sort by paths alphanumerically) or a function (see Array.prototype.sort() to learn how to write a sort function). Two tag name strings are passed to the sorter for each pass. Default is the order determined by Swagger UI. Environment variable: |
string |
|
string |
||
string |
||
string |
||
MUST be a function. Function to intercept remote definition, "Try it out", and OAuth 2.0 requests. Accepts one argument requestInterceptor(request) and must return the modified request, or a Promise that resolves to the modified request. Environment variable: |
string |
|
list of string |
||
MUST be a function. Function to intercept remote definition, "Try it out", and OAuth 2.0 responses. Accepts one argument responseInterceptor(response) and must return the modified response, or a Promise that resolves to the modified response. Environment variable: |
string |
|
boolean |
||
list of |
||
By default, Swagger UI attempts to validate specs against swagger.io’s online validator. You can use this parameter to set a different validator URL, for example for locally deployed validators (Validator Badge). Setting it to either none, 127.0.0.1 or localhost will disable validation. Environment variable: |
string |
|
boolean |
||
string |
||
string |
||
boolean |
||
string |
||
list of string |
||
list of string |
||
string |
||
string |
||
string |
||
string |
||
string |
||
string |
||
string |
||
OAuth only activated for the accessCode flow. During the authorization_code request to the tokenUrl, pass the Client Password using the HTTP Basic Authentication scheme - Used in the initOAuth method. Environment variable: |
boolean |
|
OAuth only applies to authorization code flows. Proof Key for Code Exchange brings enhanced security for OAuth public clients - Used in the initOAuth method. Environment variable: |
boolean |
|
string |
||
string |
||
string |
||
string |
||
string |
||
boolean |
|
|
boolean |
|
|
boolean |
|