DEV Community

Cover image for Migration from Swagger 2 to OpenAPI 3
Erwan Le Tutour
Erwan Le Tutour

Posted on

10

Migration from Swagger 2 to OpenAPI 3

archives

A lot of API are documented using Swagger, it’s a good thing that API are documented four us dev for understanding how they work and how to call them.
But a lot of these API are documented using Swagger 2, now that OpenApi is released (since 2017, the actual version is the 3.1 and is available since 15/02/2021) some projects didn’t update their documentation tools, I will try in this article to help you do so.

What you will need

In order to follow this tutorial, you will need a REST API, so you can :

Step 1 : getting ride of SpringFox dependencies

When we first implemented our Swagger, we add these dependencies to have

At this point, we should have some compilation problems because of some annotations due to the missing dependencies that we have replaced.
We will corrige that now.

Step 2: changing the annotations of the endpoints

In the previous tutorial, we documented our API using a configuration class

package com.erwan.human.config;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.Collections;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(path())
.build()
.apiInfo(apiInfo())
.useDefaultResponseMessages(false);
}
private Predicate<String> path() {
return Predicates.not(PathSelectors.regex("/error"));
}
private ApiInfo apiInfo() {
return new ApiInfo(
"Human Cloning API",
"API for creating clone to fight in the clones war",
"1.0.O",
"",
new Contact("Erwan Le Tutour", "https://github.com/ErwanLT", "erwanletutour.elt@gmail.com"),
"MIT License",
"https://opensource.org/licenses/mit-license.php",
Collections.emptyList());
}
}

Here we have 2 choices
  • declaring a new configuration class

  • Using annotations in one of our controllers

I will give you 2 equivalent example of the previous code in OpenApi way

Configuration class

@Configuration
public class OpenApiConfiguration {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI().info(apiInfo());
}
private Info apiInfo() {
return new Info()
.title("Human cloning API")
.description("API for creating clone who will fight in the clones wars")
.version("2.0")
.contact(apiContact())
.license(apiLicence());
}
private License apiLicence() {
return new License()
.name("MIT Licence")
.url("https://opensource.org/licenses/mit-license.php");
}
private Contact apiContact() {
return new Contact()
.name("Erwan LE TUTOUR")
.email("erwanletutour.elt@gmail.com")
.url("https://github.com/ErwanLT");
}
}

With annotations

@OpenAPIDefinition(info = @Info(title = "Human cloning API",
description = "API for creating clone who will fight in the clones wars",
version = "2.0",
contact = @Contact(
name = "LE TUTOUR Erwan",
email = "erwanletutour.elt@gmail.com",
url = "https://github.com/ErwanLT"
),
license = @License(
name = "MIT Licence",
url = "https://opensource.org/licenses/mit-license.php"
)
))
public class HumanCloningController {
}
view raw documented.java hosted with ❤ by GitHub

What it will look like in the UI

The previous example will look the same in the UI page, so it’s up to you to choose what method you want to use

Step 3: changing the annotations of the endpoints

Swagger 2.0 annotation

Let’s take an example

@PostMapping()
@PreAuthorize("hasAnyAuthority('ROLE_KAMINOAIN', 'ROLE_EMPEROR')")
@ApiOperation(value = "Create a clone to fight in the clones war",
produces = "application/json")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Clone created", response = Clone.class),
@ApiResponse(code = 500, message = "An error occured")
})
public Clone createClone(@RequestBody Clone clone){
return repository.save(clone);
}

Here is a POST method documented with classique Swagger 2 annotations
  • @ApiOperation : Describes an operation or typically a HTTP method against a specific path.

  • @ApiResponses : A wrapper to allow a list of multiple ApiResponse objects. Here I have 2 @ApiResponse to describe my 200 and 500 HTTP status return code.
    I can also describe what my status will return, the 200 will respond with an objet, so I added the object class to response field.

That will render like the following picture in the UI page

Here a second example with this time a GET method

@GetMapping("/{id}")
@PreAuthorize("hasAnyAuthority('ROLE_KAMINOAIN', 'ROLE_EMPEROR')")
@ApiOperation(value = "Find a clone by it's ID")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Clone found",response = Clone.class),
@ApiResponse(code = 404, message = "Clone not found")
})
public Clone findById(@ApiParam(name = "The clone ID", example = "12",required = true) @PathVariable("id") Long id) throws BeanNotFound {
return getOne(id);
}
view raw get.java hosted with ❤ by GitHub

Here in addition of the previous annotations, I have added the documentation of the method parameter using @ApiParam.

With the new dependency, the annotation described are no longer the same.
So let’s see what has changed.

OpenAPI annotations

@Operation(summary = "Create a clone", description = "Create a clone with the information of the body.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Clone created", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = Clone.class)) }),
@ApiResponse(responseCode = "500", description = "An error occured.", content = @Content) })
@PostMapping
@PreAuthorize("hasAnyAuthority('ROLE_KAMINOAIN', 'ROLE_EMPEROR')")
public Clone createClone(@RequestBody Clone clone){
return repository.save(clone);
}
@GetMapping("/{id}")
@PreAuthorize("hasAnyAuthority('ROLE_KAMINOAIN', 'ROLE_EMPEROR')")
@Operation(description = "Find a clone by it's ID")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Clone found",content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = Clone.class)) }),
@ApiResponse(responseCode = "404", description = "No clones found", content = @Content)
})
public Clone findById(@Parameter(name = "The clone ID", example = "12",required = true) @PathVariable("id") Long id) throws BeanNotFound {
return getOne(id);
}
view raw getOpenApi.java hosted with ❤ by GitHub

With the 2 above example who is the same method that i used previously we can now see some of the changes that was operated.
  • @ApiOperation *-> *@Operation, the value field become summary and notes *become *description.

  • @ApiResponse : the field code become responseCode and is no longer an integer but a string, also the field message become description.
    The biggest change is that the response field is now an annotation. @content.

  • @ApiParam become @Parameter

They are other change, but since they are not used here, i recommend you to use the openAPI documentation.

Step 4: changing the annotations of the models

Swagger 2.0 annotation

So in Swagger 2 when i wanted to document an object, my class looked somewhere like this

package com.erwan.human.domaine;
import com.erwan.human.reference.CloneType;
import com.sun.istack.NotNull;
import com.sun.istack.Nullable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Getter
@Setter
@ApiModel
public class Clone {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
@ApiModelProperty(value = "The generated ID when saved in database",
name = "ID",
dataType = "Long",
required = false,
position = 0)
private Long id;
@ApiModelProperty( value = "The birthplace of my clone",
name = "birthPlace",
dataType = "String",
required = false,
position = 1
)
private final String birthPlace = "Kamino";
@NotNull
@ApiModelProperty( value = "The code of my clone",
name = "codeName",
dataType = "String",
required = true,
position = 2
)
private String codeName;
@NotNull
@Enumerated
@ApiModelProperty( value = "The type of my clone",
name = "type",
dataType = "String",
required = true,
position = 3,
allowableValues = "flametrooper, medic, gunner, scoot, jetpack"
)
private CloneType type;
@Nullable
@ApiModelProperty( value = "The platoon of my clone",
name = "platoon",
dataType = "Integer",
required = false,
position = 4
)
private int platoon;
@ApiModelProperty( value = "The platoon of my clone",
name = "platoon",
dataType = "Integer",
required = false,
position = 5
)
private String affiliation = "Galactic Republic";
}

As you can see, my classe is annoted with the @ApiModel and it’s properties with @ApiModelProperty.
*The *@ApiModelProperty
allow us to add definitions such as description (value), name, data type, example values, and allowed values.

OpenAPI annotations

If you look closely at my ApiResponse with status code 200, you will see that the response is now @content, and that we gave the schema field the class that will be returned like this @Schema(implementation = MyClass.class).
With that annotation, OpenApi know which class to load, so i don’t have to annotate my class with an @ApiModel like annotation, but I still can document my property.

@Entity
@Getter
@Setter
public class Clone {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
@Schema(description = "The generated ID when saved in database",
name = "id",
required = false)
private Long id;
@Schema(description = "The clone code name",
name = "brithPlace",
required = false,
example = "Kamino")
@Size(min = 3, max = 40)
private final String birthPlace = "Kamino";
@Schema(description = "The clone code name",
name = "codeName",
required = true)
@NotNull
private String codeName;
@Schema(description = "The clone specialisation",
name = "type",
required = true)
@NotNull
@Enumerated
private CloneType type;
@Schema(description = "The clone's platoon",
name = "platoon",
required = false,
example = "501")
@Nullable
private int platoon;
@Schema(description = "The clone affilation",
name = "affilation",
required = false,
minLength = 3,
maxLength = 40,
example = "Galactic Republic")
private String affiliation = "Galactic Republic";
}

So, what I have done here ?
Like previously, my fields are documented, but I have used 2 differentes method for some validation (size).

I can use the bean validation annotation, or I can use the property of the Schema annotation, the result will be the same.

The position property don’t exist anymore, the fields are in the same order as the class.

Thanks for your reading time, as previously, the code used in this tutorial is findable in this Github repository, branch toOpenApi.

Image of Datadog

How to Diagram Your Cloud Architecture

Cloud architecture diagrams provide critical visibility into the resources in your environment and how they’re connected. In our latest eBook, AWS Solution Architects Jason Mimick and James Wenzel walk through best practices on how to build effective and professional diagrams.

Download the Free eBook

Top comments (1)

Collapse
 
ponder6168 profile image
ponder6168

Thanks for the help. This is the first place I found which showed me how to convert the ApiInfo() from v2 to v3.

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay