REST WS, OpenAPI, client with Maven (Spring Boot)

1 Reference

2 Introduction

This article will automatically build a full REST WS client from a OpenAPI (former swagger) spec file.

Note: Although this technique is not specific to Spring Boot, the example assumes we're working on an Spring Boot project.

Warning: This sub page uses swagger-codegen-maven-plugin; see the newer openapi-generator-maven-plugin

3 OpenAPI spec file

Place the OpenAPI spec file inside the Maven project folder:

src/main/resources/openapis/

Note: For the example, the spec file name "BOF-20180712.json" will be used.

Remark: It should work in the same way if using a spec file in .yaml format instead of .json.

4 pom.xml

4.1 Let's configure the Maven project POM file for automatically generate Java client classes for the REST WS out of the OpenAPI (former swagger) spec file

At the 'properties' section, declare the swagger-codegen version:

<swagger.codegen.version>2.3.1</swagger.codegen.version>

At the 'build.pluginManagements.plugins' section, add Eclipse lifecycle mapping:

<plugin>

<groupId>org.eclipse.m2e</groupId>

<artifactId>lifecycle-mapping</artifactId>

<version>1.0.0</version>

<configuration>

<lifecycleMappingMetadata>

<pluginExecutions>

<pluginExecution>

<pluginExecutionFilter>

<groupId>io.swagger</groupId>

<artifactId>swagger-codegen-maven-plugin</artifactId>

<versionRange>[${swagger.codegen.version},)</versionRange>

<goals>

<goal>generate</goal>

</goals>

</pluginExecutionFilter>

<action>

<execute />

</action>

</pluginExecution>

</pluginExecutions>

</lifecycleMappingMetadata>

</configuration>

</plugin>


At the 'build.plugins' section, add the generation for Java sources out of the OpenAPI spec file:

<plugin>

<!-- Generate Java Client for OpenAPI REST WS: BOF -->

<groupId>io.swagger</groupId>

<artifactId>swagger-codegen-maven-plugin</artifactId>

<version>${swagger.codegen.version}</version>

<executions>

<execution>

<goals>

<goal>generate</goal>

</goals>

<configuration>

<inputSpec>${project.basedir}/src/main/resources/openapis/BOF-20180712.json</inputSpec>

<language>java</language>

<library>resttemplate</library>

<modelPackage>${project.groupId}.bof.model</modelPackage>

<apiPackage>${project.groupId}.bof.api</apiPackage>

<invokerPackage>${project.groupId}.bof.invoker</invokerPackage>

<configOptions>

</configOptions>

</configuration>

</execution>

</executions>

</plugin>

Try to build the project for checking if any additional dependency is missing (it might depend on the specific OpenAPI spec file being used).

4.2 Adding, if needed, additional dependencies

If project compilation fails, probably swagger-codegen generated Java sources with "import" statements pointing to dependencies not available in the project.

Fortunately, swagger-codegen also generated a pom.xml file with all the dependencies used by the generated sources:

target/generated-sources/swagger/pom.xml


In our example, a dependency for the deserialization of time fields is needed, so let's added to the project pom.xml:

<dependency>

<groupId>com.github.joschi.jackson</groupId>

<artifactId>jackson-datatype-threetenbp</artifactId>

<version>2.6.4</version>

</dependency>


4.3 Add the generated sources folder to the Eclipse build_class_path

At section "build.plugins":

<plugin>

<!-- adding generated source (it adds a new build_class_path to the project) -->

<groupId>org.codehaus.mojo</groupId>

<artifactId>build-helper-maven-plugin</artifactId>

<executions>

<execution>

<id>add-source</id>

<phase>generate-sources</phase>

<goals>

<goal>add-source</goal>

</goals>

<configuration>

<sources>

<source>${project.build.directory}/generated-sources/swagger/src/main/java</source>

</sources>

</configuration>

</execution>

</executions>

</plugin>


Remark: When using Eclipse 4.8, the plugin version inherited from Spring Boot might not be compatible. If that happens, override it in the 'properties' section as follows:

<build-helper-maven-plugin.version>3.0.0</build-helper-maven-plugin.version>


5 Configure Spring Boot to use the generated OpenAPI client

5.1 BofIntegrationConfig.java

Create this file at the same level than the main Spring Boot application one (annotated with @SpringBoot application):

package edu.cou.gpra_doc;


import edu.cou.igpra.bof.api.ProgramasApi;

import edu.cou.igpra.bof.invoker.ApiClient;


import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;


@Configuration

public class BofIntegrationConfig {


//REST WS Endpoint 'basePath'

private static final String bofWsBasePath = "https://bofws-bofws-test.cloudapps.uoc.es";


@Bean

public ProgramasApi programasApi() {

return new ProgramasApi(apiClient());

}


/**

* The ApiClient class is used for configuring authentication, the base path of

* the API, common headers, and it’s responsible for executing all API

* requests.<br>

*/

@Bean

public ApiClient apiClient() {

ApiClient apiClient = new ApiClient();

// Endpoint basePath

apiClient.setBasePath(bofWsBasePath);

/*- Sample oauth authentication

OAuth petStoreAuth = (OAuth) apiClient.getAuthentication("petstore_auth");

petStoreAuth.setAccessToken("special-key");

*/

return apiClient;

}


}

5.2 @Import(BofIntegrationConfig.class)

Add the annotation to the Spring Boot application class annotated with "@SpringBootApplication":

...

@SpringBootApplication

@Import(BofIntegrationConfig.class)

...


6 Use the just configured remote OpenAPI REST WS

@Service

public class BofHelper implements Bof {


//Inject the generated OpenAPI client

@Autowired

ProgramasApi programasApi;


//Method that invokes a remote operation via the generated client

public Programa getById(Long id, String X_COU_SCOPE, String idioma, String entity)

throws RestClientException {

Programa p = programasApi.getByIdUsingGET(id, X_COU_SCOPE, idioma, entity);

return p;

}


}


7 Troubleshooting

7.1 SSL certificate validation exception when invoking a remote operation

import java.security.SecureRandom;

import java.security.cert.X509Certificate;

import javax.net.ssl.HttpsURLConnection;

import javax.net.ssl.SSLContext;

import javax.net.ssl.TrustManager;

import javax.net.ssl.X509TrustManager;


// Create a trust manager that does not validate certificate chains

TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {

public X509Certificate[] getAcceptedIssuers() {

return null;

}


public void checkClientTrusted(X509Certificate[] certs, String authType) {

}


public void checkServerTrusted(X509Certificate[] certs, String authType) {

}

} };


// Install the all-trusting trust manager

try {

SSLContext sc = SSLContext.getInstance("TLS");

sc.init(null, trustAllCerts, new SecureRandom());

HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

} catch (Exception e) {

;

}