Enterprise Java

MicroProfile OpenAPI for everyone

MicroProfile OpenAPI is primarily used for adding OpenAPI to JAX-RS Endpoints. In this blog post we will look at how the SmallRye Implementation extends this with some extra features, and support for more web frameworks, when used in Quarkus.

Using Quarkus

The example code is available here. You can also initialize a project using code.quarkus.io – just make sure to include the SmallRye OpenAPI Extension.

JAX-RS

Let’s start with a basic JAX-RS Example in Quarkus. We have a Greeting Object, that has a message and a to field, and we will create GET, POST and DELETE endpoints for the greeting.

Apart from the usual Quarkus setup, you also need the following in your pom.xml to create a JAX-RS Endpoint:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-smallrye-openapi</artifactId>
    </dependency>
 
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-resteasy</artifactId>
    </dependency>
 
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-resteasy-jsonb</artifactId>
    </dependency>

In Quarkus you do not need an Application class, we can just add the Endpoint class:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Path("/jax-rs")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class JaxRsGreeting {
 
    @GET
    @Path("/hello")
    public Greeting helloJaxRs() {
        return new Greeting("Hello", "JAX-RS");
    }
 
    @POST
    @Path("/hello")
    public Greeting newHelloJaxRs(Greeting greeting) {
        return greeting;
    }
 
    @DELETE
    @Path("/hello/{message}")
    public void deleteHelloJaxRs(@PathParam("message") String message) {
        // Here do the delete.
    }
}

So far we have not yet added any MicroProfile OpenAPI Annotations, but because we added the quarkus-smallrye-openapi extension, we will already have a Schema document generated under /openapi:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
---
openapi: 3.0.3
info:
  title: Generated API
  version: "1.0"
paths:
  /jax-rs/hello:
    get:
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Greeting'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'
  /jax-rs/hello/{message}:
    delete:
      parameters:
      - name: message
        in: path
        required: true
        schema:
          type: string
      responses:
        "204":
          description: No Content
components:
  schemas:
    Greeting:
      type: object
      properties:
        message:
          type: string
        to:
          type: string

See quarkus.io/guides/rest-json for more information.

OpenAPI

You can add more information to the generated schema document by using MicroProfile OpenAPI.

Header information using config

One feature that we added to SmallRye is the ability to add header information, that you typically add to the Application class using annotations, with MicroProfile config. This is useful in Quarkus where you do not need an Application class. So adding the following to the application.properties will give you some header information:

01
02
03
04
05
06
07
08
09
10
mp.openapi.extensions.smallrye.info.title=OpenAPI for Everyone
%dev.mp.openapi.extensions.smallrye.info.title=OpenAPI for Everyone (development)
%test.mp.openapi.extensions.smallrye.info.title=OpenAPI for Everyone (test)
mp.openapi.extensions.smallrye.info.version=1.0.0
mp.openapi.extensions.smallrye.info.description=Example on how to use OpenAPI everywhere
mp.openapi.extensions.smallrye.info.contact.email=phillip.kruger@redhat.com
mp.openapi.extensions.smallrye.info.contact.name=Phillip Kruger
mp.openapi.extensions.smallrye.info.contact.url=https://www.phillip-kruger.com
mp.openapi.extensions.smallrye.info.license.name=Apache 2.0
mp.openapi.extensions.smallrye.info.license.url=http://www.apache.org/licenses/LICENSE-2.0.html

Now look at the header of the generated schema document under /openapi:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
---
openapi: 3.0.3
info:
  title: OpenAPI for Everyone (development)
  description: Example on how to use OpenAPI everywhere
  contact:
    name: Phillip Kruger
    url: https://www.phillip-kruger.com
    email: phillip.kruger@redhat.com
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.0
 
  # rest of the schema document ...

Adding some OpenAPI Annotations to your operations

You can use any of the annotations in MicroProfile OpenAPI to further describe your endpoint, for example the Tag annotation:

1
2
3
4
5
6
7
@Path("/jax-rs")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "JAX-RS Resource", description = "Basic Hello World using JAX-RS")
public class JaxRsGreeting {
    \\...
}

Auto generate the operation id

Some tools that use the schema document to generate client stubs, need an operationId in the schema document that is used to name the client stub methods. We added support in SmallRye to auto generate this using either the method name (METHOD), class and method name (CLASS_METHOD), or package, class and method name (PACKAGE_CLASS_METHOD). To do this add the following to application.properties:

1
mp.openapi.extensions.smallrye.operationIdStrategy=METHOD

You will now see the operationId in the schema document for every operation:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
---
openapi: 3.0.3
 
  # header omitted ...
 
  /jax-rs/hello:
    get:
      tags:
      - JAX-RS Resource
      operationId: helloJaxRs
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'
    post:
      tags:
      - JAX-RS Resource
      operationId: newHelloJaxRs
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Greeting'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'
  /jax-rs/hello/{message}:
    delete:
      tags:
      - JAX-RS Resource
      operationId: deleteHelloJaxRs
      parameters:
      - name: message
        in: path
        required: true
        schema:
          type: string
      responses:
        "204":
          description: No Content

Changing the OpenAPI version

Some API gateways might require a certain OpenAPI version to work. The schema document generated by the SmallRye extension will generate with a 3.0.3 as the version, but since there is only minor differences between these versions, you can change that to 3.0.0, 3.0.1 or 3.0.2. You can do this by adding this in application.properties:

1
mp.openapi.extensions.smallrye.openapi=3.0.2

Now the version generated will be:

1
2
3
4
---
openapi: 3.0.2
 
  # Rest of the document ...

See quarkus.io/guides/openapi-swaggerui for more information.

Spring Web

Recently support for Spring Web has been added in SmallRye, this means that, not only will you see the default OpenAPI document when you use Spring Web in Quarkus, but you can also use MicroProfile OpenAPI to further describe your Spring Web endpoints.

Let’s add a Spring Rest Controller to our current application. First add this in your pom.xml:

1
2
3
4
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-spring-web</artifactId>
</dependency>

Now you can create a similar endpoint to the JAX-RS one we have looked at so far, but using Spring Web:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
@RestController
@RequestMapping(value = "/spring", produces = MediaType.APPLICATION_JSON_VALUE)
@Tag(name = "Spring Resource", description = "Basic Hello World using Spring")
public class SpringGreeting {
 
    @GetMapping("/hello")
    public Greeting helloSpring() {
        return new Greeting("Hello", "Spring");
    }
 
    @PostMapping("/hello")
    public Greeting newHelloSpring(@RequestBody Greeting greeting) {
        return greeting;
    }
 
    @DeleteMapping("/hello/{message}")
    public void deleteHelloSpring(@PathVariable(name = "message") String message) {
 
    }
}

The Spring annotations will be scanned and this will be added to your schema document:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
---
openapi: 3.0.3
 
  # header omitted ...
 
  /spring/hello:
    get:
      tags:
      - Spring Resource
      operationId: helloSpring
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'
    post:
      tags:
      - Spring Resource
      operationId: newHelloSpring
      requestBody:
        content:
          '*/*':
            schema:
              $ref: '#/components/schemas/Greeting'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'
  /spring/hello/{message}:
    delete:
      tags:
      - Spring Resource
      operationId: deleteHelloSpring
      parameters:
      - name: message
        in: path
        required: true
        schema:
          type: string
      responses:
        "204":
          description: No Content

See quarkus.io/guides/spring-web for more information.

Vert.x Reactive Routes

In Quarkus, you can also build Vert.x endpoints using Reactive Routes. Similarly to Spring Web, your endpoints will be available in the OpenAPI Schema and can be further described using MicroProfile OpenAPI. To add a Vert.x reactive Route in Quarkus, you need the following in your pom.xml:

1
2
3
4
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-vertx-web</artifactId>
</dependency>

Now you can create the endpoint:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
@ApplicationScoped
@RouteBase(path = "/vertx", produces = "application/json")
@Tag(name = "Vert.x Resource", description = "Basic Hello World using Vert.x")
public class VertxGreeting {
 
    @Route(path = "/hello", methods = HttpMethod.GET)
    public Greeting helloVertX() {
        return new Greeting("Hello", "Vert.x");
    }
 
    @Route(path = "/hello", methods = HttpMethod.POST)
    public Greeting newHelloVertX(@Body Greeting greeting) {
        return greeting;
    }
 
    @Route(path = "/hello/:message", methods = HttpMethod.DELETE)
    public void deleteHelloVertX(@Param("message") String message) {
 
    }
}

and now your Vert.x Routes are available in OpenAPI:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
---
openapi: 3.0.3
 
  # header omitted ...
 
  /vertx/hello:
    get:
      tags:
      - Vert.x Resource
      operationId: helloVertX
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'
    post:
      tags:
      - Vert.x Resource
      operationId: newHelloVertX
      requestBody:
        content:
          '*/*':
            schema:
              $ref: '#/components/schemas/Greeting'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'
  /vertx/hello/{message}:
    delete:
      tags:
      - Vert.x Resource
      operationId: deleteHelloVertX
      parameters:
      - name: message
        in: path
        required: true
        schema:
          type: string
      responses:
        "204":
          description: No Content

See quarkus.io/guides/reactive-routes for more information.

Endpoints generated with Panache

In Quarkus your can generate your JAX-RS endpoint using Panache. These generated classes will also be scanned and added to the OpenAPI schema document if you have the quarkus-smallrye-openapi extension in your pom.xml.

See quarkus.io/guides/rest-data-panache for more information.

Any other Web Framework

You can also add any other endpoint to your document by providing that part of the Schema document in a yaml file. Let’s say for instance you have a Servlet that exposes some methods and you want to add those to the schema document. Servlet is just an example, any web framework can work here.

So first we add this to the pom.xml to add Servlet support in Quarkus:

1
2
3
4
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-undertow</artifactId>
</dependency>

We can now create a Servlet Endpoint like this for example:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@WebServlet("/other/hello/*")
public class ServletGreeting extends HttpServlet {
 
    private static final Jsonb JSONB = JsonbBuilder.create();
 
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
        response.setContentType("application/json");
        Greeting greeting = new Greeting("Hello", "Other");
        PrintWriter out = response.getWriter();
        out.print(JSONB.toJson(greeting));
    }
 
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("application/json");
        Greeting greeting = JSONB.fromJson(request.getInputStream(), Greeting.class);
        PrintWriter out = response.getWriter();
        out.print(JSONB.toJson(greeting));
    }
 
    @Override
    protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // Here do the delete
    }
}

Now we need a OpenAPI Schema document that maps to these endpoints. You need to add this to a file called openapi.yml in src/main/resources/META-INF:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
---
openapi: 3.0.3
tags:
- name: Other Resource
  description: Basic Hello World using Something else
paths:
  /other/hello:
    get:
      tags:
      - Other Resource
      operationId: helloOther
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'
    post:
      tags:
      - Other Resource
      operationId: newHelloOther
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Greeting'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'
  /other/hello/{message}:
    delete:
      tags:
      - Other Resource
      operationId: deleteHelloOther
      parameters:
      - name: message
        in: path
        required: true
        schema:
          type: string
      responses:
        "204":
          description: No Content

This will be merged with the rest of the endpoints to expose all paths in your document. So in the end your /openapi output will look like this:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
---
openapi: 3.0.2
info:
  title: OpenAPI for Everyone (development)
  description: Example on how to use OpenAPI everywhere
  contact:
    name: Phillip Kruger
    url: https://www.phillip-kruger.com
    email: phillip.kruger@redhat.com
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.0
tags:
- name: Other Resource
  description: Basic Hello World using Something else
- name: Spring Resource
  description: Basic Hello World using Spring
- name: JAX-RS Resource
  description: Basic Hello World using JAX-RS
- name: Vert.x Resource
  description: Basic Hello World using Vert.x
paths:
  /other/hello:
    get:
      tags:
      - Other Resource
      operationId: helloOther
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'
    post:
      tags:
      - Other Resource
      operationId: newHelloOther
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Greeting'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'
  /other/hello/{message}:
    delete:
      tags:
      - Other Resource
      operationId: deleteHelloOther
      parameters:
      - name: message
        in: path
        required: true
        schema:
          type: string
      responses:
        "204":
          description: No Content
  /jax-rs/hello:
    get:
      tags:
      - JAX-RS Resource
      operationId: helloJaxRs
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'
    post:
      tags:
      - JAX-RS Resource
      operationId: newHelloJaxRs
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Greeting'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'
  /jax-rs/hello/{message}:
    delete:
      tags:
      - JAX-RS Resource
      operationId: deleteHelloJaxRs
      parameters:
      - name: message
        in: path
        required: true
        schema:
          type: string
      responses:
        "204":
          description: No Content
  /spring/hello:
    get:
      tags:
      - Spring Resource
      operationId: helloSpring
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'
    post:
      tags:
      - Spring Resource
      operationId: newHelloSpring
      requestBody:
        content:
          '*/*':
            schema:
              $ref: '#/components/schemas/Greeting'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'
  /spring/hello/{message}:
    delete:
      tags:
      - Spring Resource
      operationId: deleteHelloSpring
      parameters:
      - name: message
        in: path
        required: true
        schema:
          type: string
      responses:
        "204":
          description: No Content
  /vertx/hello:
    get:
      tags:
      - Vert.x Resource
      operationId: helloVertX
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'
    post:
      tags:
      - Vert.x Resource
      operationId: newHelloVertX
      requestBody:
        content:
          '*/*':
            schema:
              $ref: '#/components/schemas/Greeting'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'
  /vertx/hello/{message}:
    delete:
      tags:
      - Vert.x Resource
      operationId: deleteHelloVertX
      parameters:
      - name: message
        in: path
        required: true
        schema:
          type: string
      responses:
        "204":
          description: No Content
components:
  schemas:
    Greeting:
      type: object
      properties:
        message:
          type: string
        to:
          type: string

This contains resources from JAX-RS, Spring Web, Vert.x Reactive Routes and Servlet.

Swagger UI

In Quarkus, Swagger-UI is included by default and when you now browse to localhost:8080/swagger-ui you will see the UI with all your endpoints:

Summary

In this post we looked at how Quarkus extends the MicroProfile OpenAPI specification to make it even easier to document your Endpoints. We also looked at how you can document any web framework using it.

If you find any issues or have any suggestions, head over to the SmallRye project and let’s discuss it there.

Published on Java Code Geeks with permission by Phillip Krüger, partner at our JCG program. See the original article here: MicroProfile OpenAPI for everyone

Opinions expressed by Java Code Geeks contributors are their own.

Phillip Kruger

Phillip is a software developer and a systems architect who knacks for solving problems. He has a passion for clean code and evolutionary architecture. He blogs about all technical things.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button