HTTP Client From OpenAPI Spec And Spring Boot Example
1. Introduction
OpenAPI ( formerly Swagger) provides a standard way to describe Rest APIs and OpenAPI Generator tool to generate http client from openapi spec. The generator tool takes an OpenAPI specification, then uses the selected generator (e.g., java), library (e.g., webclient), and config options to fill Mustache template files with API metadata and models, producing fully generated source code tailored to that setup. In this example, I will demonstrate how to generate HTTP Client source code from an OpenAPI Spec YAML file in a Gradle project.
2. Setup
2.1 An OpenAPI Spec YAML File
In this step, I will create a “demoApi.yaml” file that describes a User API for POST, GET APIs.
demoApi.yaml
openapi: 3.0.0
info:
title: Sample User API
description: This is a sample API for managing users.
version: 1.0.0
servers:
- url: https://api.example.com/v1
description: Production server
- url: https://staging.example.com/v1
description: Staging server
tags:
- name: users
description: Operations related to users
paths:
/users:
get:
tags:
- users
summary: Get all users
operationId: getUsers
description: Retrieves a list of all registered users.
responses:
'200':
description: A list of users
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
'500':
description: Internal server error
post:
tags:
- users
summary: Create a new user
operationId: createUser
description: Creates a new user with the provided details.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/User'
responses:
'201':
description: User created successfully
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
description: Invalid input
/users/{userId}:
get:
tags:
- users
summary: Get user by ID
operationId: getUserById
description: Retrieves a single user by their ID.
parameters:
- in: path
name: userId
schema:
type: string
required: true
description: The ID of the user to retrieve.
responses:
'200':
description: User found
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: User not found
components:
schemas:
User:
type: object
properties:
id:
type: string
format: uuid
description: Unique identifier for the user.
name:
type: string
description: Name of the user.
email:
type: string
format: email
description: Email address of the user.
required:
- id
- name
- email
- Line 15,16: GET /users to fetch all users.
- Line 33: POST /users to create a new user
- Line 54, 55: GET /users/{id} to retrieve a user by ID
- Line 79: a
Userschema.
2.2 Build.gradle
In this step, I will edit the “build.gradle” file to generate HTTP WebClient via the “org.openapi.generator” plugin..
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.5.7'
id 'io.spring.dependency-management' version '1.1.7'
id 'org.openapi.generator' version '7.5.0'
}
group = 'org.jcg.zheng.demo'
version = '0.0.1-SNAPSHOT'
description = 'Demo project for Spring Boot'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
def openApiOutputDir = "$buildDir/generated"
task openApiGenerateJavaWebClient( type: org.openapitools.generator.gradle.plugin.tasks.GenerateTask) {
inputSpec = file("$projectDir/src/main/resources/demoApi.yaml").path
outputDir = "${openApiOutputDir}/java/webclient"
apiPackage = "org.jcg.zheng.demo.javawebclient.api"
modelPackage = "org.jcg.zheng.demo.javawebclient.model"
generatorName = "java" //general-purpose java client library
library = "webclient" //other options: webclient, resttemplate, feign, jersey2
enablePostProcessFile = true
configOptions = [
useJakartaEe: "true",
dateLibrary: "java8",
reactive: "false",
hideGenerationTimestamp: "true"
]
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
//here are dependencies for webclient
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
tasks.named('compileJava') {
dependsOn(tasks.openApiGenerateJavaWebClient)
source("$buildDir/generated/src/main/java")
}
sourceSets{
main {
java {
srcDir("${openApiOutputDir}/java/webclient/src/main/java")
}
}
}
- Line 5: imports the “
org.openapi.generator” plugin. - Line 22: defines the
openApiOutputDirto"$buildDir/generated". - Line 24: defines the
openApiGenerateJavaWebClienttask to generatewebclient - Line 25: sets the
inputSpecfile path. - Line 26: sets up the
outputDir. - Line 27: sets the
apiPackagename. Note: the best practice is to name it as the project’s base package. - Line 28: sets the
modelPackagename. - Line 29: sets the
generatorName=javafor general-purpose java client. - Line 30: sets the library to
webclientfor generating Spring WebFluxWebClient. - Line 32: sets additional configuration options.
- Line 57: invokes the
openApiGeneratorJavaWebClientbefore thecompileJavatask. - Line 64: adds the generated source directory to the
sourceSets.
2.3 HttpclientWebclientApplication
No change to the generated HttpclientWebclientApplication.java class.
HttpclientWebclientApplication.java
package org.jcg.zheng.demo.httpclient_webclient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class HttpclientWebclientApplication {
public static void main(String[] args) {
SpringApplication.run(HttpclientWebclientApplication.class, args);
}
}
3. Generate HTTP Client from Openapi Spec.
In this step, I will execute the gradlew build command to generate the HTTP WebClient.
gradlew build output
C:\MaryZheng\workspace\httpclient-webclient>gradlew build > Task :openApiGenerateJavaWebClient ################################################################################ # Thanks for using OpenAPI Generator. # # Please consider donation to help us maintain this project 🙏 # # https://opencollective.com/openapi_generator/donate # ################################################################################ Successfully generated code to C:\MaryZheng\workspace\httpclient-webclient\build/generated/java/webclient > Task :compileJava Note: C:\MaryZheng\workspace\httpclient-webclient\build\generated\java\webclient\src\main\java\org\jcg\zheng\demo\javawebclient\ApiClient.java uses or overrides a deprecated API. Note: Recompile with -Xlint:deprecation for details. Java HotSpot(TM) 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended [Incubating] Problems report is available at: file:///C:/MaryZheng/workspace/httpclient-webclient/build/reports/problems/problems-report.html BUILD SUCCESSFUL in 9s 8 actionable tasks: 8 executed C:\MaryZheng\workspace\httpclient-webclient>
- Line 9: the source code is generated successfully.
4. View Generated Files
Figure 1 shows four packages generated from the gradlew build command.
We will review the following classes:
ApiClient.java: this is the core helper class that wrapsorg.springframework.web.reactive.function.client.WebClientand manages HTTP communication, including creates and configures the SpringWebClientinstance, handles JSON encoding/decoding, converts Java objects to query/form params, authentication, and error handling. All generated API classes(UsersApi, etc.) depend on it.UsersApi.java: this is generated API client class that represents all HTTP operations(GET, POST, PUT, DELETE, etc.) based on the openApi Specification’s endpoints — e.g./users,/users/{id}.User.java: this isPlain Old Java Object(POJO)generated from the OpenAPI schema definition for theUsermodel.
4.1 Generated ApiClient
In this step, I will view the generated ApiClient.java class.
ApiClient.java
package org.jcg.zheng.demo.javawebclient;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.openapitools.jackson.nullable.JsonNullableModule;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.RequestEntity.BodyBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.web.client.RestClientException;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClient.ResponseSpec;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Flux;
import java.util.Optional;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TimeZone;
import jakarta.annotation.Nullable;
import java.time.OffsetDateTime;
import org.jcg.zheng.demo.javawebclient.auth.Authentication;
import org.jcg.zheng.demo.javawebclient.auth.HttpBasicAuth;
import org.jcg.zheng.demo.javawebclient.auth.HttpBearerAuth;
import org.jcg.zheng.demo.javawebclient.auth.ApiKeyAuth;
@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.5.0")
public class ApiClient extends JavaTimeFormatter {
public enum CollectionFormat {
CSV(","), TSV("\t"), SSV(" "), PIPES("|"), MULTI(null);
private final String separator;
private CollectionFormat(String separator) {
this.separator = separator;
}
private String collectionToString(Collection<?> collection) {
return StringUtils.collectionToDelimitedString(collection, separator);
}
}
private static final String URI_TEMPLATE_ATTRIBUTE = WebClient.class.getName() + ".uriTemplate";
private HttpHeaders defaultHeaders = new HttpHeaders();
private MultiValueMap<String, String> defaultCookies = new LinkedMultiValueMap<String, String>();
private String basePath = "https://api.example.com/v1";
private final WebClient webClient;
private final DateFormat dateFormat;
private final ObjectMapper objectMapper;
private Map<String, Authentication> authentications;
public ApiClient() {
this.dateFormat = createDefaultDateFormat();
this.objectMapper = createDefaultObjectMapper(this.dateFormat);
this.webClient = buildWebClient(this.objectMapper);
this.init();
}
public ApiClient(WebClient webClient) {
this(Optional.ofNullable(webClient).orElseGet(() -> buildWebClient()), createDefaultDateFormat());
}
public ApiClient(ObjectMapper mapper, DateFormat format) {
this(buildWebClient(mapper.copy()), format);
}
public ApiClient(WebClient webClient, ObjectMapper mapper, DateFormat format) {
this(Optional.ofNullable(webClient).orElseGet(() -> buildWebClient(mapper.copy())), format);
}
private ApiClient(WebClient webClient, DateFormat format) {
this.webClient = webClient;
this.dateFormat = format;
this.objectMapper = createDefaultObjectMapper(format);
this.init();
}
public static DateFormat createDefaultDateFormat() {
DateFormat dateFormat = new RFC3339DateFormat();
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
return dateFormat;
}
public static ObjectMapper createDefaultObjectMapper(@Nullable DateFormat dateFormat) {
if (null == dateFormat) {
dateFormat = createDefaultDateFormat();
}
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(dateFormat);
mapper.registerModule(new JavaTimeModule());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
JsonNullableModule jnm = new JsonNullableModule();
mapper.registerModule(jnm);
return mapper;
}
protected void init() {
// Setup authentications (key: authentication name, value: authentication).
authentications = new HashMap<String, Authentication>();
// Prevent the authentications from being modified.
authentications = Collections.unmodifiableMap(authentications);
}
/**
* Build the WebClientBuilder used to make WebClient.
* @param mapper ObjectMapper used for serialize/deserialize
* @return WebClient
*/
public static WebClient.Builder buildWebClientBuilder(ObjectMapper mapper) {
ExchangeStrategies strategies = ExchangeStrategies
.builder()
.codecs(clientDefaultCodecsConfigurer -> {
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(mapper, MediaType.APPLICATION_JSON));
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(mapper, MediaType.APPLICATION_JSON));
}).build();
WebClient.Builder webClientBuilder = WebClient.builder().exchangeStrategies(strategies);
return webClientBuilder;
}
/**
* Build the WebClientBuilder used to make WebClient.
* @return WebClient
*/
public static WebClient.Builder buildWebClientBuilder() {
return buildWebClientBuilder(createDefaultObjectMapper(null));
}
/**
* Build the WebClient used to make HTTP requests.
* @param mapper ObjectMapper used for serialize/deserialize
* @return WebClient
*/
public static WebClient buildWebClient(ObjectMapper mapper) {
return buildWebClientBuilder(mapper).build();
}
/**
* Build the WebClient used to make HTTP requests.
* @return WebClient
*/
public static WebClient buildWebClient() {
return buildWebClientBuilder(createDefaultObjectMapper(null)).build();
}
/**
* Get the current base path
* @return String the base path
*/
public String getBasePath() {
return basePath;
}
/**
* Set the base path, which should include the host
* @param basePath the base path
* @return ApiClient this client
*/
public ApiClient setBasePath(String basePath) {
this.basePath = basePath;
return this;
}
/**
* Get authentications (key: authentication name, value: authentication).
* @return Map the currently configured authentication types
*/
public Map<String, Authentication> getAuthentications() {
return authentications;
}
/**
* Get authentication for the given name.
*
* @param authName The authentication name
* @return The authentication, null if not found
*/
public Authentication getAuthentication(String authName) {
return authentications.get(authName);
}
/**
* Helper method to set access token for the first Bearer authentication.
* @param bearerToken Bearer token
*/
public void setBearerToken(String bearerToken) {
for (Authentication auth : authentications.values()) {
if (auth instanceof HttpBearerAuth) {
((HttpBearerAuth) auth).setBearerToken(bearerToken);
return;
}
}
throw new RuntimeException("No Bearer authentication configured!");
}
/**
* Helper method to set username for the first HTTP basic authentication.
* @param username the username
*/
public void setUsername(String username) {
for (Authentication auth : authentications.values()) {
if (auth instanceof HttpBasicAuth) {
((HttpBasicAuth) auth).setUsername(username);
return;
}
}
throw new RuntimeException("No HTTP basic authentication configured!");
}
/**
* Helper method to set password for the first HTTP basic authentication.
* @param password the password
*/
public void setPassword(String password) {
for (Authentication auth : authentications.values()) {
if (auth instanceof HttpBasicAuth) {
((HttpBasicAuth) auth).setPassword(password);
return;
}
}
throw new RuntimeException("No HTTP basic authentication configured!");
}
/**
* Helper method to set API key value for the first API key authentication.
* @param apiKey the API key
*/
public void setApiKey(String apiKey) {
for (Authentication auth : authentications.values()) {
if (auth instanceof ApiKeyAuth) {
((ApiKeyAuth) auth).setApiKey(apiKey);
return;
}
}
throw new RuntimeException("No API key authentication configured!");
}
/**
* Helper method to set API key prefix for the first API key authentication.
* @param apiKeyPrefix the API key prefix
*/
public void setApiKeyPrefix(String apiKeyPrefix) {
for (Authentication auth : authentications.values()) {
if (auth instanceof ApiKeyAuth) {
((ApiKeyAuth) auth).setApiKeyPrefix(apiKeyPrefix);
return;
}
}
throw new RuntimeException("No API key authentication configured!");
}
/**
* Set the User-Agent header's value (by adding to the default header map).
* @param userAgent the user agent string
* @return ApiClient this client
*/
public ApiClient setUserAgent(String userAgent) {
addDefaultHeader("User-Agent", userAgent);
return this;
}
/**
* Add a default header.
*
* @param name The header's name
* @param value The header's value
* @return ApiClient this client
*/
public ApiClient addDefaultHeader(String name, String value) {
if (defaultHeaders.containsKey(name)) {
defaultHeaders.remove(name);
}
defaultHeaders.add(name, value);
return this;
}
/**
* Add a default cookie.
*
* @param name The cookie's name
* @param value The cookie's value
* @return ApiClient this client
*/
public ApiClient addDefaultCookie(String name, String value) {
if (defaultCookies.containsKey(name)) {
defaultCookies.remove(name);
}
defaultCookies.add(name, value);
return this;
}
/**
* Get the date format used to parse/format date parameters.
* @return DateFormat format
*/
public DateFormat getDateFormat() {
return dateFormat;
}
/**
* Parse the given string into Date object.
*/
public Date parseDate(String str) {
try {
return dateFormat.parse(str);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
/**
* Format the given Date object into string.
*/
public String formatDate(Date date) {
return dateFormat.format(date);
}
/**
* Get the ObjectMapper used to make HTTP requests.
* @return ObjectMapper objectMapper
*/
public ObjectMapper getObjectMapper() {
return objectMapper;
}
/**
* Get the WebClient used to make HTTP requests.
* @return WebClient webClient
*/
public WebClient getWebClient() {
return webClient;
}
/**
* Format the given parameter object into string.
* @param param the object to convert
* @return String the parameter represented as a String
*/
public String parameterToString(Object param) {
if (param == null) {
return "";
} else if (param instanceof Date) {
return formatDate( (Date) param);
} else if (param instanceof OffsetDateTime) {
return formatOffsetDateTime((OffsetDateTime) param);
} else if (param instanceof Collection) {
StringBuilder b = new StringBuilder();
for(Object o : (Collection<?>) param) {
if(b.length() > 0) {
b.append(",");
}
b.append(String.valueOf(o));
}
return b.toString();
} else {
return String.valueOf(param);
}
}
/**
* Converts a parameter to a {@link MultiValueMap} for use in REST requests
* @param collectionFormat The format to convert to
* @param name The name of the parameter
* @param value The parameter's value
* @return a Map containing the String value(s) of the input parameter
*/
public MultiValueMap<String, String> parameterToMultiValueMap(CollectionFormat collectionFormat, String name, Object value) {
final MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
if (name == null || name.isEmpty() || value == null) {
return params;
}
if(collectionFormat == null) {
collectionFormat = CollectionFormat.CSV;
}
if (value instanceof Map) {
@SuppressWarnings("unchecked")
final Map<String, Object> valuesMap = (Map<String, Object>) value;
for (final Entry<String, Object> entry : valuesMap.entrySet()) {
params.add(entry.getKey(), parameterToString(entry.getValue()));
}
return params;
}
Collection<?> valueCollection = null;
if (value instanceof Collection) {
valueCollection = (Collection<?>) value;
} else {
params.add(name, parameterToString(value));
return params;
}
if (valueCollection.isEmpty()){
return params;
}
if (collectionFormat.equals(CollectionFormat.MULTI)) {
for (Object item : valueCollection) {
params.add(name, parameterToString(item));
}
return params;
}
List<String> values = new ArrayList<String>();
for(Object o : valueCollection) {
values.add(parameterToString(o));
}
params.add(name, collectionFormat.collectionToString(values));
return params;
}
/**
* Check if the given {@code String} is a JSON MIME.
* @param mediaType the input MediaType
* @return boolean true if the MediaType represents JSON, false otherwise
*/
public boolean isJsonMime(String mediaType) {
// "* / *" is default to JSON
if ("*/*".equals(mediaType)) {
return true;
}
try {
return isJsonMime(MediaType.parseMediaType(mediaType));
} catch (InvalidMediaTypeException e) {
}
return false;
}
/**
* Check if the given MIME is a JSON MIME.
* JSON MIME examples:
* application/json
* application/json; charset=UTF8
* APPLICATION/JSON
* @param mediaType the input MediaType
* @return boolean true if the MediaType represents JSON, false otherwise
*/
public boolean isJsonMime(MediaType mediaType) {
return mediaType != null && (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType) || mediaType.getSubtype().matches("^.*(\\+json|ndjson)[;]?\\s*$"));
}
/**
* Check if the given {@code String} is a Problem JSON MIME (RFC-7807).
* @param mediaType the input MediaType
* @return boolean true if the MediaType represents Problem JSON, false otherwise
*/
public boolean isProblemJsonMime(String mediaType) {
return "application/problem+json".equalsIgnoreCase(mediaType);
}
/**
* Select the Accept header's value from the given accepts array:
* if JSON exists in the given array, use it;
* otherwise use all of them (joining into a string)
*
* @param accepts The accepts array to select from
* @return List The list of MediaTypes to use for the Accept header
*/
public List<MediaType> selectHeaderAccept(String[] accepts) {
if (accepts.length == 0) {
return null;
}
for (String accept : accepts) {
MediaType mediaType = MediaType.parseMediaType(accept);
if (isJsonMime(mediaType) && !isProblemJsonMime(accept)) {
return Collections.singletonList(mediaType);
}
}
return MediaType.parseMediaTypes(StringUtils.arrayToCommaDelimitedString(accepts));
}
/**
* Select the Content-Type header's value from the given array:
* if JSON exists in the given array, use it;
* otherwise use the first one of the array.
*
* @param contentTypes The Content-Type array to select from
* @return MediaType The Content-Type header to use. If the given array is empty, null will be returned.
*/
public MediaType selectHeaderContentType(String[] contentTypes) {
if (contentTypes.length == 0) {
return null;
}
for (String contentType : contentTypes) {
MediaType mediaType = MediaType.parseMediaType(contentType);
if (isJsonMime(mediaType)) {
return mediaType;
}
}
return MediaType.parseMediaType(contentTypes[0]);
}
/**
* Select the body to use for the request
* @param obj the body object
* @param formParams the form parameters
* @param contentType the content type of the request
* @return Object the selected body
*/
protected BodyInserter<?, ? super ClientHttpRequest> selectBody(Object obj, MultiValueMap<String, Object> formParams, MediaType contentType) {
if(MediaType.APPLICATION_FORM_URLENCODED.equals(contentType)) {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
formParams
.toSingleValueMap()
.entrySet()
.forEach(es -> map.add(es.getKey(), String.valueOf(es.getValue())));
return BodyInserters.fromFormData(map);
} else if(MediaType.MULTIPART_FORM_DATA.equals(contentType)) {
return BodyInserters.fromMultipartData(formParams);
} else {
return obj != null ? BodyInserters.fromValue(obj) : null;
}
}
/**
* Invoke API by sending HTTP request with the given options.
*
* @param <T> the return type to use
* @param path The sub-path of the HTTP URL
* @param method The request method
* @param pathParams The path parameters
* @param queryParams The query parameters
* @param body The request body object
* @param headerParams The header parameters
* @param formParams The form parameters
* @param accept The request's Accept header
* @param contentType The request's Content-Type header
* @param authNames The authentications to apply
* @param returnType The return type into which to deserialize the response
* @return The response body in chosen type
*/
public <T> ResponseSpec invokeAPI(String path, HttpMethod method, Map<String, Object> pathParams, MultiValueMap<String, String> queryParams, Object body, HttpHeaders headerParams, MultiValueMap<String, String> cookieParams, MultiValueMap<String, Object> formParams, List<MediaType> accept, MediaType contentType, String[] authNames, ParameterizedTypeReference<T> returnType) throws RestClientException {
final WebClient.RequestBodySpec requestBuilder = prepareRequest(path, method, pathParams, queryParams, body, headerParams, cookieParams, formParams, accept, contentType, authNames);
return requestBuilder.retrieve();
}
/**
* Include queryParams in uriParams taking into account the paramName
* @param queryParams The query parameters
* @param uriParams The path parameters
* return templatized query string
*/
private String generateQueryUri(MultiValueMap<String, String> queryParams, Map<String, Object> uriParams) {
StringBuilder queryBuilder = new StringBuilder();
queryParams.forEach((name, values) -> {
if (CollectionUtils.isEmpty(values)) {
if (queryBuilder.length() != 0) {
queryBuilder.append('&');
}
queryBuilder.append(name);
} else {
int valueItemCounter = 0;
for (Object value : values) {
if (queryBuilder.length() != 0) {
queryBuilder.append('&');
}
queryBuilder.append(name);
if (value != null) {
String templatizedKey = name + valueItemCounter++;
uriParams.put(templatizedKey, value.toString());
queryBuilder.append('=').append("{").append(templatizedKey).append("}");
}
}
}
});
return queryBuilder.toString();
}
private WebClient.RequestBodySpec prepareRequest(String path, HttpMethod method, Map<String, Object> pathParams,
MultiValueMap<String, String> queryParams, Object body, HttpHeaders headerParams,
MultiValueMap<String, String> cookieParams, MultiValueMap<String, Object> formParams, List<MediaType> accept,
MediaType contentType, String[] authNames) {
updateParamsForAuth(authNames, queryParams, headerParams, cookieParams);
final UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(basePath).path(path);
String finalUri = builder.build(false).toUriString();
Map<String, Object> uriParams = new HashMap<>();
uriParams.putAll(pathParams);
if (queryParams != null && !queryParams.isEmpty()) {
//Include queryParams in uriParams taking into account the paramName
String queryUri = generateQueryUri(queryParams, uriParams);
//Append to finalUri the templatized query string like "?param1={param1Value}&.......
finalUri += "?" + queryUri;
}
final WebClient.RequestBodySpec requestBuilder = webClient.method(method).uri(finalUri, uriParams);
if (accept != null) {
requestBuilder.accept(accept.toArray(new MediaType[accept.size()]));
}
if(contentType != null) {
requestBuilder.contentType(contentType);
}
addHeadersToRequest(headerParams, requestBuilder);
addHeadersToRequest(defaultHeaders, requestBuilder);
addCookiesToRequest(cookieParams, requestBuilder);
addCookiesToRequest(defaultCookies, requestBuilder);
requestBuilder.attribute(URI_TEMPLATE_ATTRIBUTE, path);
requestBuilder.body(selectBody(body, formParams, contentType));
return requestBuilder;
}
/**
* Add headers to the request that is being built
* @param headers The headers to add
* @param requestBuilder The current request
*/
protected void addHeadersToRequest(HttpHeaders headers, WebClient.RequestBodySpec requestBuilder) {
for (Entry<String, List<String>> entry : headers.entrySet()) {
List<String> values = entry.getValue();
for(String value : values) {
if (value != null) {
requestBuilder.header(entry.getKey(), value);
}
}
}
}
/**
* Add cookies to the request that is being built
* @param cookies The cookies to add
* @param requestBuilder The current request
*/
protected void addCookiesToRequest(MultiValueMap<String, String> cookies, WebClient.RequestBodySpec requestBuilder) {
for (Entry<String, List<String>> entry : cookies.entrySet()) {
List<String> values = entry.getValue();
for(String value : values) {
if (value != null) {
requestBuilder.cookie(entry.getKey(), value);
}
}
}
}
/**
* Update query and header parameters based on authentication settings.
*
* @param authNames The authentications to apply
* @param queryParams The query parameters
* @param headerParams The header parameters
* @param cookieParams the cookie parameters
*/
protected void updateParamsForAuth(String[] authNames, MultiValueMap<String, String> queryParams, HttpHeaders headerParams, MultiValueMap<String, String> cookieParams) {
for (String authName : authNames) {
Authentication auth = authentications.get(authName);
if (auth == null) {
throw new RestClientException("Authentication undefined: " + authName);
}
auth.applyToParams(queryParams, headerParams, cookieParams);
}
}
/**
* Formats the specified collection path parameter to a string value.
*
* @param collectionFormat The collection format of the parameter.
* @param values The values of the parameter.
* @return String representation of the parameter
*/
public String collectionPathParameterToString(CollectionFormat collectionFormat, Collection<?> values) {
// create the value based on the collection format
if (CollectionFormat.MULTI.equals(collectionFormat)) {
// not valid for path params
return parameterToString(values);
}
// collectionFormat is assumed to be "csv" by default
if(collectionFormat == null) {
collectionFormat = CollectionFormat.CSV;
}
return collectionFormat.collectionToString(values);
}
}
- The generated
ApiClientclass has 731 lines.
4.2 Generated UserApi
In this step, I will view the generated UserApi.java class.
UserApi.java
package org.jcg.zheng.demo.javawebclient.api;
import org.jcg.zheng.demo.javawebclient.ApiClient;
import org.jcg.zheng.demo.javawebclient.model.User;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.web.reactive.function.client.WebClient.ResponseSpec;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Flux;
@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.5.0")
public class UsersApi {
private ApiClient apiClient;
public UsersApi() {
this(new ApiClient());
}
@Autowired
public UsersApi(ApiClient apiClient) {
this.apiClient = apiClient;
}
public ApiClient getApiClient() {
return apiClient;
}
public void setApiClient(ApiClient apiClient) {
this.apiClient = apiClient;
}
/**
* Create a new user
* Creates a new user with the provided details.
* <p><b>201</b> - User created successfully
* <p><b>400</b> - Invalid input
* @param user The user parameter
* @return User
* @throws WebClientResponseException if an error occurs while attempting to invoke the API
*/
private ResponseSpec createUserRequestCreation(User user) throws WebClientResponseException {
Object postBody = user;
// verify the required parameter 'user' is set
if (user == null) {
throw new WebClientResponseException("Missing the required parameter 'user' when calling createUser", HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase(), null, null, null);
}
// create path and map variables
final Map<String, Object> pathParams = new HashMap<String, Object>();
final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<String, String>();
final HttpHeaders headerParams = new HttpHeaders();
final MultiValueMap<String, String> cookieParams = new LinkedMultiValueMap<String, String>();
final MultiValueMap<String, Object> formParams = new LinkedMultiValueMap<String, Object>();
final String[] localVarAccepts = {
"application/json"
};
final List<MediaType> localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
final String[] localVarContentTypes = {
"application/json"
};
final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);
String[] localVarAuthNames = new String[] { };
ParameterizedTypeReference<User> localVarReturnType = new ParameterizedTypeReference<User>() {};
return apiClient.invokeAPI("/users", HttpMethod.POST, pathParams, queryParams, postBody, headerParams, cookieParams, formParams, localVarAccept, localVarContentType, localVarAuthNames, localVarReturnType);
}
/**
* Create a new user
* Creates a new user with the provided details.
* <p><b>201</b> - User created successfully
* <p><b>400</b> - Invalid input
* @param user The user parameter
* @return User
* @throws WebClientResponseException if an error occurs while attempting to invoke the API
*/
public Mono<User> createUser(User user) throws WebClientResponseException {
ParameterizedTypeReference<User> localVarReturnType = new ParameterizedTypeReference<User>() {};
return createUserRequestCreation(user).bodyToMono(localVarReturnType);
}
/**
* Create a new user
* Creates a new user with the provided details.
* <p><b>201</b> - User created successfully
* <p><b>400</b> - Invalid input
* @param user The user parameter
* @return ResponseEntity<User>
* @throws WebClientResponseException if an error occurs while attempting to invoke the API
*/
public Mono<ResponseEntity<User>> createUserWithHttpInfo(User user) throws WebClientResponseException {
ParameterizedTypeReference<User> localVarReturnType = new ParameterizedTypeReference<User>() {};
return createUserRequestCreation(user).toEntity(localVarReturnType);
}
/**
* Create a new user
* Creates a new user with the provided details.
* <p><b>201</b> - User created successfully
* <p><b>400</b> - Invalid input
* @param user The user parameter
* @return ResponseSpec
* @throws WebClientResponseException if an error occurs while attempting to invoke the API
*/
public ResponseSpec createUserWithResponseSpec(User user) throws WebClientResponseException {
return createUserRequestCreation(user);
}
/**
* Get user by ID
* Retrieves a single user by their ID.
* <p><b>200</b> - User found
* <p><b>404</b> - User not found
* @param userId The ID of the user to retrieve.
* @return User
* @throws WebClientResponseException if an error occurs while attempting to invoke the API
*/
private ResponseSpec getUserByIdRequestCreation(String userId) throws WebClientResponseException {
Object postBody = null;
// verify the required parameter 'userId' is set
if (userId == null) {
throw new WebClientResponseException("Missing the required parameter 'userId' when calling getUserById", HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase(), null, null, null);
}
// create path and map variables
final Map<String, Object> pathParams = new HashMap<String, Object>();
pathParams.put("userId", userId);
final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<String, String>();
final HttpHeaders headerParams = new HttpHeaders();
final MultiValueMap<String, String> cookieParams = new LinkedMultiValueMap<String, String>();
final MultiValueMap<String, Object> formParams = new LinkedMultiValueMap<String, Object>();
final String[] localVarAccepts = {
"application/json"
};
final List<MediaType> localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
final String[] localVarContentTypes = { };
final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);
String[] localVarAuthNames = new String[] { };
ParameterizedTypeReference<User> localVarReturnType = new ParameterizedTypeReference<User>() {};
return apiClient.invokeAPI("/users/{userId}", HttpMethod.GET, pathParams, queryParams, postBody, headerParams, cookieParams, formParams, localVarAccept, localVarContentType, localVarAuthNames, localVarReturnType);
}
/**
* Get user by ID
* Retrieves a single user by their ID.
* <p><b>200</b> - User found
* <p><b>404</b> - User not found
* @param userId The ID of the user to retrieve.
* @return User
* @throws WebClientResponseException if an error occurs while attempting to invoke the API
*/
public Mono<User> getUserById(String userId) throws WebClientResponseException {
ParameterizedTypeReference<User> localVarReturnType = new ParameterizedTypeReference<User>() {};
return getUserByIdRequestCreation(userId).bodyToMono(localVarReturnType);
}
/**
* Get user by ID
* Retrieves a single user by their ID.
* <p><b>200</b> - User found
* <p><b>404</b> - User not found
* @param userId The ID of the user to retrieve.
* @return ResponseEntity<User>
* @throws WebClientResponseException if an error occurs while attempting to invoke the API
*/
public Mono<ResponseEntity<User>> getUserByIdWithHttpInfo(String userId) throws WebClientResponseException {
ParameterizedTypeReference<User> localVarReturnType = new ParameterizedTypeReference<User>() {};
return getUserByIdRequestCreation(userId).toEntity(localVarReturnType);
}
/**
* Get user by ID
* Retrieves a single user by their ID.
* <p><b>200</b> - User found
* <p><b>404</b> - User not found
* @param userId The ID of the user to retrieve.
* @return ResponseSpec
* @throws WebClientResponseException if an error occurs while attempting to invoke the API
*/
public ResponseSpec getUserByIdWithResponseSpec(String userId) throws WebClientResponseException {
return getUserByIdRequestCreation(userId);
}
/**
* Get all users
* Retrieves a list of all registered users.
* <p><b>200</b> - A list of users
* <p><b>500</b> - Internal server error
* @return List<User>
* @throws WebClientResponseException if an error occurs while attempting to invoke the API
*/
private ResponseSpec getUsersRequestCreation() throws WebClientResponseException {
Object postBody = null;
// create path and map variables
final Map<String, Object> pathParams = new HashMap<String, Object>();
final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<String, String>();
final HttpHeaders headerParams = new HttpHeaders();
final MultiValueMap<String, String> cookieParams = new LinkedMultiValueMap<String, String>();
final MultiValueMap<String, Object> formParams = new LinkedMultiValueMap<String, Object>();
final String[] localVarAccepts = {
"application/json"
};
final List<MediaType> localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
final String[] localVarContentTypes = { };
final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);
String[] localVarAuthNames = new String[] { };
ParameterizedTypeReference<User> localVarReturnType = new ParameterizedTypeReference<User>() {};
return apiClient.invokeAPI("/users", HttpMethod.GET, pathParams, queryParams, postBody, headerParams, cookieParams, formParams, localVarAccept, localVarContentType, localVarAuthNames, localVarReturnType);
}
/**
* Get all users
* Retrieves a list of all registered users.
* <p><b>200</b> - A list of users
* <p><b>500</b> - Internal server error
* @return List<User>
* @throws WebClientResponseException if an error occurs while attempting to invoke the API
*/
public Flux<User> getUsers() throws WebClientResponseException {
ParameterizedTypeReference<User> localVarReturnType = new ParameterizedTypeReference<User>() {};
return getUsersRequestCreation().bodyToFlux(localVarReturnType);
}
/**
* Get all users
* Retrieves a list of all registered users.
* <p><b>200</b> - A list of users
* <p><b>500</b> - Internal server error
* @return ResponseEntity<List<User>>
* @throws WebClientResponseException if an error occurs while attempting to invoke the API
*/
public Mono<ResponseEntity<List<User>>> getUsersWithHttpInfo() throws WebClientResponseException {
ParameterizedTypeReference<User> localVarReturnType = new ParameterizedTypeReference<User>() {};
return getUsersRequestCreation().toEntityList(localVarReturnType);
}
/**
* Get all users
* Retrieves a list of all registered users.
* <p><b>200</b> - A list of users
* <p><b>500</b> - Internal server error
* @return ResponseSpec
* @throws WebClientResponseException if an error occurs while attempting to invoke the API
*/
public ResponseSpec getUsersWithResponseSpec() throws WebClientResponseException {
return getUsersRequestCreation();
}
}
- The generated
UserApihas 273 lines.
4.3 Generated User
In this step, I will view the generated User.java class.
User.java
/*
* Sample User API
* This is a sample API for managing users.
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
package org.jcg.zheng.demo.javawebclient.model;
import java.util.Objects;
import java.util.Arrays;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonTypeName;
/**
* User
*/
@JsonPropertyOrder({
User.JSON_PROPERTY_ID,
User.JSON_PROPERTY_NAME,
User.JSON_PROPERTY_EMAIL
})
@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.5.0")
public class User {
public static final String JSON_PROPERTY_ID = "id";
private UUID id;
public static final String JSON_PROPERTY_NAME = "name";
private String name;
public static final String JSON_PROPERTY_EMAIL = "email";
private String email;
public User() {
}
public User id(UUID id) {
this.id = id;
return this;
}
/**
* Unique identifier for the user.
* @return id
**/
@jakarta.annotation.Nonnull
@JsonProperty(JSON_PROPERTY_ID)
@JsonInclude(value = JsonInclude.Include.ALWAYS)
public UUID getId() {
return id;
}
@JsonProperty(JSON_PROPERTY_ID)
@JsonInclude(value = JsonInclude.Include.ALWAYS)
public void setId(UUID id) {
this.id = id;
}
public User name(String name) {
this.name = name;
return this;
}
/**
* Name of the user.
* @return name
**/
@jakarta.annotation.Nonnull
@JsonProperty(JSON_PROPERTY_NAME)
@JsonInclude(value = JsonInclude.Include.ALWAYS)
public String getName() {
return name;
}
@JsonProperty(JSON_PROPERTY_NAME)
@JsonInclude(value = JsonInclude.Include.ALWAYS)
public void setName(String name) {
this.name = name;
}
public User email(String email) {
this.email = email;
return this;
}
/**
* Email address of the user.
* @return email
**/
@jakarta.annotation.Nonnull
@JsonProperty(JSON_PROPERTY_EMAIL)
@JsonInclude(value = JsonInclude.Include.ALWAYS)
public String getEmail() {
return email;
}
@JsonProperty(JSON_PROPERTY_EMAIL)
@JsonInclude(value = JsonInclude.Include.ALWAYS)
public void setEmail(String email) {
this.email = email;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
User user = (User) o;
return Objects.equals(this.id, user.id) &&
Objects.equals(this.name, user.name) &&
Objects.equals(this.email, user.email);
}
@Override
public int hashCode() {
return Objects.hash(id, name, email);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class User {\n");
sb.append(" id: ").append(toIndentedString(id)).append("\n");
sb.append(" name: ").append(toIndentedString(name)).append("\n");
sb.append(" email: ").append(toIndentedString(email)).append("\n");
sb.append("}");
return sb.toString();
}
/**
* Convert the given object to string with each line indented by 4 spaces
* (except the first line).
*/
private String toIndentedString(Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}
- The generated
Userclass is lengthy comparing to a class with Lombok annotations.
5. Invoke Generated Class
5.1 Invoke Generated Class
In this step, I will create a JavaWebClientService.java to invoke the generated UserApi.
JavaWebClientService.java
package org.jcg.zheng.demo.httpclient_webclient;
import org.jcg.zheng.demo.javawebclient.ApiClient;
import org.jcg.zheng.demo.javawebclient.api.UsersApi;
import org.jcg.zheng.demo.javawebclient.model.User;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Service
public class JavaWebClientService {
private final UsersApi usersApi;
public JavaWebClientService() {
super();
this.usersApi = new UsersApi(new ApiClient());
this.usersApi.getApiClient().setBasePath("http://localhost:8080");
}
public Mono<User> createUser(User user) {
return usersApi.createUser(user);
}
public Mono<User> getUser(String userId) {
return usersApi.getUserById(userId);
}
public Flux<User> getUsers() {
return usersApi.getUsers();
}
}
- Line 18: initiates new
UsersApiand integrated with theJavaWebClientServicebean. - Line 19: sets the
basePathfor theapiClient.
5.2 Demonstrate
In this step, I will start the Spring Boot application and capture the server log.
Spring Boot Server Log
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.5.7) 2025-11-09T09:42:44.729-06:00 INFO 8360 --- [httpclient-webclient] [ main] o.j.z.d.h.HttpclientWebclientApplication : Starting HttpclientWebclientApplication using Java 21.0.8 with PID 8360 (C:\MaryZheng\workspace\httpclient-webclient\bin\main started by zzhen in C:\MaryZheng\workspace\httpclient-webclient) 2025-11-09T09:42:44.732-06:00 INFO 8360 --- [httpclient-webclient] [ main] o.j.z.d.h.HttpclientWebclientApplication : No active profile set, falling back to 1 default profile: "default" 2025-11-09T09:42:45.851-06:00 INFO 8360 --- [httpclient-webclient] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http) 2025-11-09T09:42:45.864-06:00 INFO 8360 --- [httpclient-webclient] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2025-11-09T09:42:45.864-06:00 INFO 8360 --- [httpclient-webclient] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.48] 2025-11-09T09:42:45.912-06:00 INFO 8360 --- [httpclient-webclient] [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2025-11-09T09:42:45.914-06:00 INFO 8360 --- [httpclient-webclient] [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1131 ms 2025-11-09T09:42:46.548-06:00 INFO 8360 --- [httpclient-webclient] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/' 2025-11-09T09:42:46.557-06:00 INFO 8360 --- [httpclient-webclient] [ main] o.j.z.d.h.HttpclientWebclientApplication : Started HttpclientWebclientApplication in 2.344 seconds (process running for 2.808)
- Line 18: the Spring Boot application is started successfully.
6. Advantages & Drawbacks of OpenAPI
Using OpenAPI for client generation offers the following advantages:
- Automation and Consistency – Clients are generated from a single source of truth, reducing human error.
- Faster Development – No need to manually write HTTP clients or data transfer objects.
- Language Flexibility – OpenAPI Generator supports over 50 programming languages.
- Strong Typing – Generated models enforce schema validation and make code more robust.
- Up-to-Date Integration – When the API changes, simply regenerate the client for immediate updates.
However, the static Mustache template triggers the following drawbacks:
- Complex Configuration – The generator can be tricky to configure for large or legacy APIs.
- Code Overhead – Generated code may include unnecessary or verbose classes.
- Customization Difficulty – Modifying generated code directly is discouraged; templates or post-processing scripts may be needed.
- Regeneration Risks – Regenerating code can overwrite manual changes if not properly separated.
7. Conclusion
Generating HTTP clients from an OpenAPI specification provides a reliable, consistent, and maintainable approach to API integration in Spring Boot applications. It reduces manual effort, eliminates boilerplate code, and helps maintain contract alignment between services. For the “java” generator, beside the “webclient” library used in this example, here are a few other common libraries supported by OpenAPI generator.
| Library for Generator=”java” | Description | Mustache Template Link | Method to Get Client |
| webclient | Spring WebFlux HttpClient | https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator/src/main/resources/Java/libraries/webclient | new UserApi(new ApiClient) |
| feign | Feign Client | https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator/src/main/resources/Java/libraries/feign | Feign.builder().target(UserApi.class, basePath) |
| resttemplate | RestTemplate Client | https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator/src/main/resources/Java/libraries/feign | new UserApi(new ApiClient) |
8. Download
This was an example of a Gradle project that generated the HTTP Webclient in a Spring Boot project
You can download the full source code of this example here: Generating HTTP Client from OpenAPI Spec Example





