Enterprise Java

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 User schema.

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 openApiOutputDir to "$buildDir/generated".
  • Line 24: defines the openApiGenerateJavaWebClient task to generate webclient
  • Line 25: sets the inputSpec file path.
  • Line 26: sets up the outputDir.
  • Line 27: sets the apiPackage name. Note: the best practice is to name it as the project’s base package.
  • Line 28: sets the modelPackage name.
  • Line 29: sets the generatorName=java for general-purpose java client.
  • Line 30: sets the library to webclient for generating Spring WebFlux WebClient.
  • Line 32: sets additional configuration options.
  • Line 57: invokes the openApiGeneratorJavaWebClient before the compileJava task.
  • 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

http client from openapi spec
Figure 1 Project Setting

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 wraps org.springframework.web.reactive.function.client.WebClient and manages HTTP communication, including creates and configures the Spring WebClient instance, 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 is Plain Old Java Object(POJO) generated from the OpenAPI schema definition for the User model.

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 ApiClient class 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 UserApi has 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 User class 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 UsersApi and integrated with the JavaWebClientService bean.
  • Line 19: sets the basePath for the apiClient.

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:

  1. Automation and Consistency – Clients are generated from a single source of truth, reducing human error.
  2. Faster Development – No need to manually write HTTP clients or data transfer objects.
  3. Language Flexibility – OpenAPI Generator supports over 50 programming languages.
  4. Strong Typing – Generated models enforce schema validation and make code more robust.
  5. Up-to-Date Integration – When the API changes, simply regenerate the client for immediate updates.

However, the static Mustache template triggers the following drawbacks:

  1. Complex Configuration – The generator can be tricky to configure for large or legacy APIs.
  2. Code Overhead – Generated code may include unnecessary or verbose classes.
  3. Customization Difficulty – Modifying generated code directly is discouraged; templates or post-processing scripts may be needed.
  4. 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”DescriptionMustache Template LinkMethod to Get Client
webclientSpring WebFlux HttpClienthttps://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator/src/main/resources/Java/libraries/webclientnew UserApi(new ApiClient)
feignFeign Clienthttps://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator/src/main/resources/Java/libraries/feignFeign.builder().target(UserApi.class, basePath)
resttemplateRestTemplate Clienthttps://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator/src/main/resources/Java/libraries/feignnew UserApi(new ApiClient)

8. Download

This was an example of a Gradle project that generated the HTTP Webclient in a Spring Boot project

Download
You can download the full source code of this example here: Generating HTTP Client from OpenAPI Spec Example

Mary Zheng

Mary graduated from the Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She worked as a lead Software Engineer where she led and worked with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest

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

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button