Moving With the Times: Towards OpenAPI v3.0.0 Adoption in JAX-RS APIs
It is terrifying to see how fast time passes! The OpenAPI specification 3.0.0, a major revamp of so-get-used-to Swagger specification, was released, mostly, one year ago but it took awhile for tooling to catch up. However, with the recent official release of the Swagger Core 2.0.0 things are going to accelerate for sure.
To prove the point, Apache CXF, the well-known JAX-RS 2.1 implementation, is one of the first adopters of the OpenAPI 3.0.0 and in today's post we are going to take a look how easy your JAX-RS 2.1 APIs could benefit from it.
As always, to keep things simple we are going to design a people management web API with just a handful set of resources to support it, nothing too exciting here.
POST /api/people
GET /api/people/{email}
GET /api/people
DELETE /api/people/{email}
Our model would consist of a single Person class.
public class Person {
private String email;
private String firstName;
private String lastName;
}
To add a bit of magic, we will be using Spring Boot to get us up and running as fast as possible. With that, let us start to fill in the dependencies (assuming we are using Apache Maven for build management).
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxrs</artifactId>
<version>3.2.4</version>
</dependency>
In the recent 3.2.x releases, Apache CXF introduces a new module cxf-rt-rs-service-description-openapi-v3 dedicated to OpenAPI 3.0.0, based on Swagger Core 2.0.0.
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-service-description-openapi-v3</artifactId>
<version>3.2.4</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>swagger-ui</artifactId>
<version>3.13.6</version>
</dependency>
The presence of the Swagger UI is not strictly necessary, but this is an exceptionally useful and beautiful tool to explore your APIs (and if it is available on classpath, Apache CXF seamlessly integrates it into your applications as we are going to see in a bit).
The prerequisites are in place, let us do some coding! Before we begin, it is worth noting that Swagger Core 2.0.0 has many ways to populate the OpenAPI 3.0.0 definitions for your services, including property files, annotations, or programmatically. In this post, we are only going to use annotations.
@OpenAPIDefinition(
info = @Info(
title = "People Management API",
version = "0.0.1-SNAPSHOT",
license = @License(
name = "Apache 2.0 License",
url = "http://www.apache.org/licenses/LICENSE-2.0.html"
)
)
)
@ApplicationPath("api")
public class JaxRsApiApplication extends Application {
}
Looks pretty simple, the @OpenAPIDefinition sets the top-level definition for all our web APIs. Moving on to the PeopleRestService, we just add the @Tag annotation to, well, tag our API.
@Path( "/people" )
@Tag(name = "people")
public class PeopleRestService {
// ...
}
Awesome, nothing complicated so far. The meaty part starts with web API operation definitions, so let us take a look at the first example, the operation to fetch everyone.
@Produces(MediaType.APPLICATION_JSON)
@GET
@Operation(
description = "List all people",
responses = {
@ApiResponse(
content = @Content(
array = @ArraySchema(schema = @Schema(implementation = Person.class))
),
responseCode = "200"
)
}
)
public Collection<Person> getPeople() {
// ...
}
Quite a few annotations, but, by and large, looks pretty clean and straightforward. Let us take a look at another one, the endpoint to find the person by their e-mail address.
@Produces(MediaType.APPLICATION_JSON)
@Path("/{email}")
@GET
@Operation(
description = "Find person by e-mail",
responses = {
@ApiResponse(
content = @Content(schema = @Schema(implementation = Person.class)),
responseCode = "200"
),
@ApiResponse(
responseCode = "404",
description = "Person with such e-mail doesn't exists"
)
}
)
public Person findPerson(
@Parameter(description = "E-Mail address to lookup for", required = true)
@PathParam("email") final String email) {
// ...
}
In the same vein, the operation to remove the person by e-mail looks mostly identical.
@Path("/{email}")
@DELETE
@Operation(
description = "Delete existing person",
responses = {
@ApiResponse(
responseCode = "204",
description = "Person has been deleted"
),
@ApiResponse(
responseCode = "404",
description = "Person with such e-mail doesn't exists"
)
}
)
public Response deletePerson(
@Parameter(description = "E-Mail address to lookup for", required = true )
@PathParam("email") final String email) {
// ...
}
Great, let us wrap up by looking into arguably the most interesting endpoint which adds a new person (the choice to use the @FormParam is purely to illustrate the different flavor of the APIs).
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
@POST
@Operation(
description = "Create new person",
responses = {
@ApiResponse(
content = @Content(
schema = @Schema(implementation = Person.class),
mediaType = MediaType.APPLICATION_JSON
),
headers = @Header(name = "Location"),
responseCode = "201"
),
@ApiResponse(
responseCode = "409",
description = "Person with such e-mail already exists"
)
}
)
public Response addPerson(@Context final UriInfo uriInfo,
@Parameter(description = "E-Mail", required = true)
@FormParam("email") final String email,
@Parameter(description = "First Name", required = true)
@FormParam("firstName") final String firstName,
@Parameter(description = "Last Name", required = true)
@FormParam("lastName") final String lastName) {
// ...
}
If you have an experience with documenting web APIs using older Swagger specifications, you might find the approach quite familiar but more verbose (or maybe it's better to say 'formalized'). It is the result of the tremendous work which specification leads and the community has done to make it as complete and extensible as possible.
The APIs are defined and documented, so now it is time to try them out! The missing piece, though, is the Spring configuration where we would initialize and expose our JAX-RS web services.
@Configuration
@EnableAutoConfiguration
@ComponentScan(basePackageClasses = PeopleRestService.class)
public class AppConfig {
@Autowired private PeopleRestService peopleRestService;
@Bean(destroyMethod = "destroy")
public Server jaxRsServer(Bus bus) {
final JAXRSServerFactoryBean factory = new JAXRSServerFactoryBean();
factory.setApplication(new JaxRsApiApplication());
factory.setServiceBean(peopleRestService);
factory.setProvider(new JacksonJsonProvider());
factory.setFeatures(Arrays.asList(new OpenApiFeature()));
factory.setBus(bus);
factory.setAddress("/");
return factory.create();
}
@Bean
public ServletRegistrationBean cxfServlet() {
final ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new CXFServlet(), "/api/*");
servletRegistrationBean.setLoadOnStartup(1);
return servletRegistrationBean;
}
}
The OpenApiFeature is a key ingredient here which takes care of all the integration and introspection. The Spring Boot application is the last touch to finish the picture.
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(AppConfig.class, args);
}
}
Let us build and run it right away:
mvn clean package
java -jar target/jax-rs-2.1-openapi-0.0.1-SNAPSHOT.jar
Having the application started, the OpenAPI 3.0.0 specification of our web APIs should be live and available for consumption in the JSON format at:
http://localhost:8080/api/openapi.json
Or in YAML format at:
http://localhost:8080/api/openapi.json
Wouldn't it be great to explore our web APIs and play with them? Since we included a Swagger UI dependency, this is a no-brainer, just navigate to http://localhost:8080/api/api-docs?url=/api/openapi.json:
Special attention should be paid to the small icon Swagger UI places alongside your API version, hinting about its conformity to the OpenAPI 3.0.0 specification.
Just to note here, there is nothing special about using Spring Boot. In case you are using Apache CXF inside the OSGi container (like Apache Karaf, for example), the integration with OpenAPI 3.0.0 is also available (please check out the official documentation and samples if you are interested in the subject).
It all looks easy and simple, but what about migrating to OpenAPI 3.0.0 from older versions of the Swagger specifications? The Apache CXF has a powerful feature to convert older specifications on the fly, but, in general, the OpenApi.Tools portal is the right place to evaluate your options.
Should you migrate to OpenAPI 3.0.0? I honestly believe you should, or at least you should try experimenting with it, but please be aware that the tooling is still not mature, so expect a few roadblocks along the way (which you will be able to overcome by contributing patches, by the way!). But undoubtedly, the future is bright!
The complete project sources are available on GitHub.