A comprehensive and type-safe REST API client generator that creates Java clients from OpenAPI/Swagger specifications with support for multiple HTTP clients, authentication, and advanced features.
Complete Implementation
1. Core Generator Class
package com.apiclient.generator; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import java.io.*; import java.nio.file.*; import java.util.*; import java.util.stream.Collectors; /** * Main REST API Client Generator * Supports OpenAPI 3.0 specifications in JSON and YAML format */ public class RestApiClientGenerator { private final ObjectMapper objectMapper; private final ObjectMapper yamlMapper; private final GeneratorConfig config; public RestApiClientGenerator(GeneratorConfig config) { this.config = config; this.objectMapper = new ObjectMapper(); this.yamlMapper = new ObjectMapper(new YAMLFactory()); } /** * Generates complete REST API client from OpenAPI specification */ public void generateClient(String specFilePath, String outputDir) throws IOException { System.out.println("Generating REST API client from: " + specFilePath); // Parse OpenAPI specification OpenApiSpec spec = parseOpenApiSpec(specFilePath); // Create output directory structure createDirectoryStructure(outputDir); // Generate model classes (DTOs) generateModelClasses(spec, outputDir); // Generate API client interfaces generateApiInterfaces(spec, outputDir); // Generate client implementation generateClientImplementation(spec, outputDir); // Generate configuration classes generateConfigurationClasses(spec, outputDir); System.out.println("REST API client generated successfully in: " + outputDir); } private OpenApiSpec parseOpenApiSpec(String specFilePath) throws IOException { File specFile = new File(specFilePath); JsonNode root; if (specFilePath.endsWith(".yaml") || specFilePath.endsWith(".yml")) { root = yamlMapper.readTree(specFile); } else { root = objectMapper.readTree(specFile); } return new OpenApiSpec(root, objectMapper); } private void createDirectoryStructure(String outputDir) throws IOException { String basePackagePath = config.getBasePackage().replace('.', '/'); Path basePath = Paths.get(outputDir, "src/main/java", basePackagePath); Files.createDirectories(basePath.resolve("models")); Files.createDirectories(basePath.resolve("api")); Files.createDirectories(basePath.resolve("client")); Files.createDirectories(basePath.resolve("config")); Files.createDirectories(Paths.get(outputDir, "src/main/resources")); } } 2. Configuration and Models
/** * Generator configuration */ public class GeneratorConfig { private String basePackage = "com.api.client"; private String clientName = "ApiClient"; private boolean useLombok = true; private boolean useJackson = true; private HttpClientType httpClientType = HttpClientType.OKHTTP; private boolean generateTests = true; private boolean asyncSupport = false; private String dateLibrary = "java8"; public enum HttpClientType { OKHTTP, APACHE_HTTP_CLIENT, JAVA_HTTP_CLIENT } // Constructors, getters, and setters public GeneratorConfig() {} public GeneratorConfig(String basePackage, String clientName) { this.basePackage = basePackage; this.clientName = clientName; } // Getters and setters public String getBasePackage() { return basePackage; } public void setBasePackage(String basePackage) { this.basePackage = basePackage; } public String getClientName() { return clientName; } public void setClientName(String clientName) { this.clientName = clientName; } public boolean isUseLombok() { return useLombok; } public void setUseLombok(boolean useLombok) { this.useLombok = useLombok; } public boolean isUseJackson() { return useJackson; } public void setUseJackson(boolean useJackson) { this.useJackson = useJackson; } public HttpClientType getHttpClientType() { return httpClientType; } public void setHttpClientType(HttpClientType httpClientType) { this.httpClientType = httpClientType; } public boolean isGenerateTests() { return generateTests; } public void setGenerateTests(boolean generateTests) { this.generateTests = generateTests; } public boolean isAsyncSupport() { return asyncSupport; } public void setAsyncSupport(boolean asyncSupport) { this.asyncSupport = asyncSupport; } public String getDateLibrary() { return dateLibrary; } public void setDateLibrary(String dateLibrary) { this.dateLibrary = dateLibrary; } } /** * OpenAPI specification wrapper */ public class OpenApiSpec { private final JsonNode root; private final ObjectMapper objectMapper; public OpenApiSpec(JsonNode root, ObjectMapper objectMapper) { this.root = root; this.objectMapper = objectMapper; } public String getTitle() { return root.path("info").path("title").asText("API Client"); } public String getVersion() { return root.path("info").path("version").asText("1.0.0"); } public String getDescription() { return root.path("info").path("description").asText(""); } public String getBaseUrl() { JsonNode servers = root.path("servers"); if (servers.isArray() && servers.size() > 0) { return servers.get(0).path("url").asText("http://localhost:8080"); } return "http://localhost:8080"; } public Map<String, JsonNode> getSchemas() { Map<String, JsonNode> schemas = new HashMap<>(); JsonNode components = root.path("components"); if (components.has("schemas")) { components.path("schemas").fields().forEachRemaining(entry -> { schemas.put(entry.getKey(), entry.getValue()); }); } return schemas; } public Map<String, JsonNode> getPaths() { Map<String, JsonNode> paths = new HashMap<>(); root.path("paths").fields().forEachRemaining(entry -> { paths.put(entry.getKey(), entry.getValue()); }); return paths; } public Map<String, JsonNode> getSecuritySchemes() { Map<String, JsonNode> securitySchemes = new HashMap<>(); JsonNode components = root.path("components"); if (components.has("securitySchemes")) { components.path("securitySchemes").fields().forEachRemaining(entry -> { securitySchemes.put(entry.getKey(), entry.getValue()); }); } return securitySchemes; } } /** * API Method information */ public class ApiMethod { private String name; private String httpMethod; private String path; private String returnType; private List<ApiParameter> parameters; private String description; private boolean deprecated; private List<String> securityRequirements; public ApiMethod(String name, String httpMethod, String path) { this.name = name; this.httpMethod = httpMethod; this.path = path; this.parameters = new ArrayList<>(); this.securityRequirements = new ArrayList<>(); } // Getters and setters public String getName() { return name; } public void setName(String name) { this.name = name; } public String getHttpMethod() { return httpMethod; } public void setHttpMethod(String httpMethod) { this.httpMethod = httpMethod; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public String getReturnType() { return returnType; } public void setReturnType(String returnType) { this.returnType = returnType; } public List<ApiParameter> getParameters() { return parameters; } public void setParameters(List<ApiParameter> parameters) { this.parameters = parameters; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public boolean isDeprecated() { return deprecated; } public void setDeprecated(boolean deprecated) { this.deprecated = deprecated; } public List<String> getSecurityRequirements() { return securityRequirements; } public void setSecurityRequirements(List<String> securityRequirements) { this.securityRequirements = securityRequirements; } public void addParameter(ApiParameter parameter) { this.parameters.add(parameter); } } /** * API Parameter information */ public class ApiParameter { private String name; private String type; private String location; // "query", "path", "header", "body" private boolean required; private String description; private String dataType; public ApiParameter(String name, String type, String location) { this.name = name; this.type = type; this.location = location; } // Getters and setters public String getName() { return name; } public void setName(String name) { this.name = name; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } public boolean isRequired() { return required; } public void setRequired(boolean required) { this.required = required; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getDataType() { return dataType; } public void setDataType(String dataType) { this.dataType = dataType; } } 3. Model Class Generator
/** * Generates model classes (DTOs) from OpenAPI schemas */ public class ModelClassGenerator { private final GeneratorConfig config; private final OpenApiSpec spec; public ModelClassGenerator(GeneratorConfig config, OpenApiSpec spec) { this.config = config; this.spec = spec; } public void generateModelClasses(String outputDir) throws IOException { Map<String, JsonNode> schemas = spec.getSchemas(); String packagePath = config.getBasePackage() + ".models"; for (Map.Entry<String, JsonNode> entry : schemas.entrySet()) { String className = toClassName(entry.getKey()); JsonNode schema = entry.getValue(); String javaCode = generateModelClass(className, schema, packagePath); writeJavaFile(outputDir, packagePath, className, javaCode); } } private String generateModelClass(String className, JsonNode schema, String packageName) { StringBuilder sb = new StringBuilder(); // Package and imports sb.append("package ").append(packageName).append(";\n\n"); if (config.isUseJackson()) { sb.append("import com.fasterxml.jackson.annotation.*;\n"); } if (config.isUseLombok()) { sb.append("import lombok.*;\n"); } sb.append("import java.util.*;\n\n"); // Class documentation String description = schema.path("description").asText(null); if (description != null) { sb.append("/**\n * ").append(description).append("\n */\n"); } // Lombok annotations if (config.isUseLombok()) { sb.append("@Data\n"); sb.append("@NoArgsConstructor\n"); sb.append("@AllArgsConstructor\n"); sb.append("@Builder\n"); } // Jackson annotations if (config.isUseJackson()) { sb.append("@JsonInclude(JsonInclude.Include.NON_NULL)\n"); } sb.append("public class ").append(className).append(" {\n\n"); // Fields JsonNode properties = schema.path("properties"); List<String> requiredFields = getRequiredFields(schema); if (properties.isObject()) { properties.fields().forEachRemaining(property -> { String fieldName = property.getKey(); JsonNode fieldSchema = property.getValue(); String fieldType = getJavaType(fieldSchema); String fieldDescription = fieldSchema.path("description").asText(null); // Field documentation if (fieldDescription != null) { sb.append(" /**\n * ").append(fieldDescription).append("\n */\n"); } // Jackson property annotation if (config.isUseJackson()) { String jsonProperty = !fieldName.matches("[a-zA-Z_$][a-zA-Z0-9_$]*") ? String.format("@JsonProperty(\"%s\")\n ", fieldName) : ""; sb.append(" ").append(jsonProperty); } sb.append(" private ").append(fieldType).append(" ").append(toFieldName(fieldName)).append(";\n\n"); }); } // Manual getters/setters if not using Lombok if (!config.isUseLombok()) { generateGettersAndSetters(sb, properties); } sb.append("}"); return sb.toString(); } private String getJavaType(JsonNode schema) { String type = schema.path("type").asText("object"); String format = schema.path("format").asText(); switch (type) { case "string": if ("date".equals(format)) { return config.getDateLibrary().equals("java8") ? "LocalDate" : "Date"; } else if ("date-time".equals(format)) { return config.getDateLibrary().equals("java8") ? "LocalDateTime" : "Date"; } else if ("byte".equals(format)) { return "byte[]"; } return "String"; case "integer": if ("int32".equals(format)) return "Integer"; if ("int64".equals(format)) return "Long"; return "Integer"; case "number": if ("float".equals(format)) return "Float"; if ("double".equals(format)) return "Double"; return "Double"; case "boolean": return "Boolean"; case "array": JsonNode items = schema.path("items"); String itemType = getJavaType(items); return "List<" + itemType + ">"; case "object": return "Map<String, Object>"; default: return "Object"; } } private List<String> getRequiredFields(JsonNode schema) { List<String> required = new ArrayList<>(); JsonNode requiredNode = schema.path("required"); if (requiredNode.isArray()) { requiredNode.forEach(field -> required.add(field.asText())); } return required; } private void generateGettersAndSetters(StringBuilder sb, JsonNode properties) { if (properties.isObject()) { properties.fields().forEachRemaining(property -> { String fieldName = property.getKey(); String javaFieldName = toFieldName(fieldName); String capitalizedFieldName = capitalize(javaFieldName); JsonNode fieldSchema = property.getValue(); String fieldType = getJavaType(fieldSchema); // Getter sb.append(" public ").append(fieldType).append(" get").append(capitalizedFieldName).append("() {\n"); sb.append(" return this.").append(javaFieldName).append(";\n"); sb.append(" }\n\n"); // Setter sb.append(" public void set").append(capitalizedFieldName).append("(") .append(fieldType).append(" ").append(javaFieldName).append(") {\n"); sb.append(" this.").append(javaFieldName).append(" = ").append(javaFieldName).append(";\n"); sb.append(" }\n\n"); }); } } private String toClassName(String name) { // Convert snake_case to PascalCase return Arrays.stream(name.split("[^a-zA-Z0-9]")) .map(part -> part.substring(0, 1).toUpperCase() + part.substring(1).toLowerCase()) .collect(Collectors.joining()); } private String toFieldName(String name) { // Convert to camelCase String[] parts = name.split("[^a-zA-Z0-9]"); if (parts.length == 0) return name; StringBuilder result = new StringBuilder(parts[0].toLowerCase()); for (int i = 1; i < parts.length; i++) { result.append(parts[i].substring(0, 1).toUpperCase()) .append(parts[i].substring(1).toLowerCase()); } return result.toString(); } private String capitalize(String str) { if (str == null || str.isEmpty()) return str; return str.substring(0, 1).toUpperCase() + str.substring(1); } private void writeJavaFile(String outputDir, String packageName, String className, String content) throws IOException { String packagePath = packageName.replace('.', '/'); Path filePath = Paths.get(outputDir, "src/main/java", packagePath, className + ".java"); Files.writeString(filePath, content); } } 4. API Interface Generator
/** * Generates API service interfaces */ public class ApiInterfaceGenerator { private final GeneratorConfig config; private final OpenApiSpec spec; public ApiInterfaceGenerator(GeneratorConfig config, OpenApiSpec spec) { this.config = config; this.spec = spec; } public void generateApiInterfaces(String outputDir) throws IOException { Map<String, List<ApiMethod>> apiGroups = groupMethodsByTag(); String packageName = config.getBasePackage() + ".api"; for (Map.Entry<String, List<ApiMethod>> entry : apiGroups.entrySet()) { String interfaceName = toInterfaceName(entry.getKey()); List<ApiMethod> methods = entry.getValue(); String javaCode = generateApiInterface(interfaceName, methods, packageName); writeJavaFile(outputDir, packageName, interfaceName, javaCode); } } private Map<String, List<ApiMethod>> groupMethodsByTag() { Map<String, List<ApiMethod>> apiGroups = new HashMap<>(); Map<String, JsonNode> paths = spec.getPaths(); for (Map.Entry<String, JsonNode> pathEntry : paths.entrySet()) { String path = pathEntry.getKey(); JsonNode pathNode = pathEntry.getValue(); pathNode.fields().forEachRemaining(methodEntry -> { String httpMethod = methodEntry.getKey(); JsonNode operation = methodEntry.getValue(); // Extract method information String operationId = operation.path("operationId").asText(generateOperationId(path, httpMethod)); List<String> tags = extractTags(operation); String tag = tags.isEmpty() ? "Default" : tags.get(0); ApiMethod apiMethod = new ApiMethod(operationId, httpMethod.toUpperCase(), path); apiMethod.setDescription(operation.path("description").asText("")); apiMethod.setDeprecated(operation.path("deprecated").asBoolean(false)); // Extract parameters extractParameters(operation, apiMethod); // Extract return type apiMethod.setReturnType(extractReturnType(operation)); // Add to appropriate group apiGroups.computeIfAbsent(tag, k -> new ArrayList<>()).add(apiMethod); }); } return apiGroups; } private String generateApiInterface(String interfaceName, List<ApiMethod> methods, String packageName) { StringBuilder sb = new StringBuilder(); // Package and imports sb.append("package ").append(packageName).append(";\n\n"); sb.append("import ").append(config.getBasePackage()).append(".models.*;\n"); sb.append("import retrofit2.Call;\n"); if (config.isAsyncSupport()) { sb.append("import retrofit2.Callback;\n"); } sb.append("import retrofit2.http.*;\n"); sb.append("import java.util.*;\n\n"); // Interface documentation sb.append("/**\n"); sb.append(" * ").append(interfaceName).append(" API\n"); sb.append(" */\n"); sb.append("public interface ").append(interfaceName).append(" {\n\n"); // Methods for (ApiMethod method : methods) { generateApiMethod(sb, method); } sb.append("}"); return sb.toString(); } private void generateApiMethod(StringBuilder sb, ApiMethod method) { // Method documentation if (method.getDescription() != null && !method.getDescription().isEmpty()) { sb.append(" /**\n * ").append(method.getDescription()).append("\n"); // Parameter documentation for (ApiParameter param : method.getParameters()) { sb.append(" * @param ").append(param.getName()).append(" "); if (param.getDescription() != null) { sb.append(param.getDescription()); } sb.append("\n"); } sb.append(" */\n"); } // Deprecation annotation if (method.isDeprecated()) { sb.append(" @Deprecated\n"); } // HTTP method annotation sb.append(" @").append(method.getHttpMethod()).append("(\"").append(method.getPath()).append("\")\n"); // Return type and method signature String returnType = "Call<" + method.getReturnType() + ">"; sb.append(" ").append(returnType).append(" ").append(method.getName()).append("("); // Parameters List<String> params = new ArrayList<>(); for (ApiParameter param : method.getParameters()) { String paramAnnotation = getParameterAnnotation(param); String paramType = getParameterType(param); params.add(paramAnnotation + " " + paramType + " " + param.getName()); } sb.append(String.join(", ", params)); sb.append(");\n\n"); } private String getParameterAnnotation(ApiParameter param) { switch (param.getLocation()) { case "query": return "@Query(\"" + param.getName() + "\")"; case "path": return "@Path(\"" + param.getName() + "\")"; case "header": return "@Header(\"" + param.getName() + "\")"; case "body": return "@Body"; default: return "@Query(\"" + param.getName() + "\")"; } } private String getParameterType(ApiParameter param) { return param.getDataType() != null ? param.getDataType() : "String"; } private String extractReturnType(JsonNode operation) { JsonNode responses = operation.path("responses"); JsonNode successResponse = responses.path("200"); if (successResponse.has("content")) { JsonNode content = successResponse.path("content"); if (content.has("application/json")) { JsonNode schema = content.path("application/json").path("schema"); return getJavaTypeFromSchema(schema); } } return "Void"; } private String getJavaTypeFromSchema(JsonNode schema) { // Simplified implementation - would need to handle $ref, arrays, etc. String type = schema.path("type").asText("object"); if ("array".equals(type)) { JsonNode items = schema.path("items"); String itemType = getJavaTypeFromSchema(items); return "List<" + itemType + ">"; } return "Object"; } private List<String> extractTags(JsonNode operation) { List<String> tags = new ArrayList<>(); JsonNode tagsNode = operation.path("tags"); if (tagsNode.isArray()) { tagsNode.forEach(tag -> tags.add(tag.asText())); } return tags; } private void extractParameters(JsonNode operation, ApiMethod apiMethod) { JsonNode parameters = operation.path("parameters"); if (parameters.isArray()) { parameters.forEach(param -> { String name = param.path("name").asText(); String in = param.path("in").asText("query"); String type = param.path("schema").path("type").asText("string"); ApiParameter apiParam = new ApiParameter(name, type, in); apiParam.setRequired(param.path("required").asBoolean(false)); apiParam.setDescription(param.path("description").asText("")); apiMethod.addParameter(apiParam); }); } } private String generateOperationId(String path, String httpMethod) { // Convert /users/{id} to getUserById String operationId = path.replaceAll("[^a-zA-Z0-9]+", " ") .trim() .replaceAll("\\s+", " "); String[] parts = operationId.split(" "); StringBuilder result = new StringBuilder(httpMethod.toLowerCase()); for (String part : parts) { if (!part.isEmpty()) { result.append(part.substring(0, 1).toUpperCase()) .append(part.substring(1).toLowerCase()); } } return result.toString(); } private String toInterfaceName(String tag) { return toClassName(tag) + "Api"; } private String toClassName(String name) { return Arrays.stream(name.split("[^a-zA-Z0-9]")) .map(part -> part.substring(0, 1).toUpperCase() + part.substring(1).toLowerCase()) .collect(Collectors.joining()); } private void writeJavaFile(String outputDir, String packageName, String className, String content) throws IOException { String packagePath = packageName.replace('.', '/'); Path filePath = Paths.get(outputDir, "src/main/java", packagePath, className + ".java"); Files.writeString(filePath, content); } } 5. Main Client Implementation
/** * Generates the main API client implementation */ public class ClientImplementationGenerator { private final GeneratorConfig config; private final OpenApiSpec spec; public ClientImplementationGenerator(GeneratorConfig config, OpenApiSpec spec) { this.config = config; this.spec = spec; } public void generateClientImplementation(String outputDir) throws IOException { String className = config.getClientName(); String packageName = config.getBasePackage() + ".client"; String javaCode = generateClientClass(className, packageName); writeJavaFile(outputDir, packageName, className, javaCode); } private String generateClientClass(String className, String packageName) { StringBuilder sb = new StringBuilder(); // Package and imports sb.append("package ").append(packageName).append(";\n\n"); sb.append("import ").append(config.getBasePackage()).append(".api.*;\n"); sb.append("import ").append(config.getBasePackage()).append(".config.*;\n"); sb.append("import okhttp3.*;\n"); sb.append("import retrofit2.Retrofit;\n"); sb.append("import retrofit2.converter.jackson.JacksonConverterFactory;\n"); sb.append("import java.util.*;\n"); sb.append("import java.util.concurrent.TimeUnit;\n\n"); // Class documentation sb.append("/**\n"); sb.append(" * ").append(spec.getTitle()).append(" Client\n"); sb.append(" * Version: ").append(spec.getVersion()).append("\n"); if (spec.getDescription() != null && !spec.getDescription().isEmpty()) { sb.append(" * ").append(spec.getDescription()).append("\n"); } sb.append(" * Generated from OpenAPI specification\n"); sb.append(" */\n"); sb.append("public class ").append(className).append(" {\n\n"); // Fields sb.append(" private final Retrofit retrofit;\n"); sb.append(" private final OkHttpClient okHttpClient;\n"); sb.append(" private final ApiConfig config;\n\n"); // API service instances sb.append(" // API Services\n"); // Constructor sb.append(" public ").append(className).append("(ApiConfig config) {\n"); sb.append(" this.config = config;\n"); sb.append(" this.okHttpClient = createHttpClient(config);\n"); sb.append(" this.retrofit = createRetrofit(config, okHttpClient);\n"); sb.append(" }\n\n"); // HTTP client creation sb.append(" private OkHttpClient createHttpClient(ApiConfig config) {\n"); sb.append(" OkHttpClient.Builder builder = new OkHttpClient.Builder()\n"); sb.append(" .connectTimeout(config.getConnectTimeout(), TimeUnit.SECONDS)\n"); sb.append(" .readTimeout(config.getReadTimeout(), TimeUnit.SECONDS)\n"); sb.append(" .writeTimeout(config.getWriteTimeout(), TimeUnit.SECONDS);\n\n"); sb.append(" // Add interceptors\n"); sb.append(" if (config.isLoggingEnabled()) {\n"); sb.append(" builder.addInterceptor(new HttpLoggingInterceptor()\n"); sb.append(" .setLevel(HttpLoggingInterceptor.Level.BODY));\n"); sb.append(" }\n\n"); sb.append(" // Add authentication\n"); sb.append(" if (config.getApiKey() != null) {\n"); sb.append(" builder.addInterceptor(new ApiKeyInterceptor(config.getApiKey()));\n"); sb.append(" }\n\n"); sb.append(" return builder.build();\n"); sb.append(" }\n\n"); // Retrofit creation sb.append(" private Retrofit createRetrofit(ApiConfig config, OkHttpClient client) {\n"); sb.append(" return new Retrofit.Builder()\n"); sb.append(" .baseUrl(config.getBaseUrl())\n"); sb.append(" .client(client)\n"); sb.append(" .addConverterFactory(JacksonConverterFactory.create())\n"); sb.append(" .build();\n"); sb.append(" }\n\n"); // API service getters generateApiServiceGetters(sb); // Close class sb.append("}"); return sb.toString(); } private void generateApiServiceGetters(StringBuilder sb) { Map<String, List<ApiMethod>> apiGroups = new ApiInterfaceGenerator(config, spec).groupMethodsByTag(); for (String tag : apiGroups.keySet()) { String interfaceName = toInterfaceName(tag); String fieldName = toFieldName(interfaceName); sb.append(" private ").append(interfaceName).append(" ").append(fieldName).append(";\n\n"); sb.append(" public ").append(interfaceName).append(" get").append(interfaceName).append("() {\n"); sb.append(" if (").append(fieldName).append(" == null) {\n"); sb.append(" ").append(fieldName).append(" = retrofit.create(").append(interfaceName).append(".class);\n"); sb.append(" }\n"); sb.append(" return ").append(fieldName).append(";\n"); sb.append(" }\n\n"); } } private String toInterfaceName(String tag) { return toClassName(tag) + "Api"; } private String toFieldName(String interfaceName) { return interfaceName.substring(0, 1).toLowerCase() + interfaceName.substring(1); } private String toClassName(String name) { return Arrays.stream(name.split("[^a-zA-Z0-9]")) .map(part -> part.substring(0, 1).toUpperCase() + part.substring(1).toLowerCase()) .collect(Collectors.joining()); } private void writeJavaFile(String outputDir, String packageName, String className, String content) throws IOException { String packagePath = packageName.replace('.', '/'); Path filePath = Paths.get(outputDir, "src/main/java", packagePath, className + ".java"); Files.writeString(filePath, content); } } 6. Configuration and Interceptors
/** * API Configuration class */ public class ApiConfig { private String baseUrl; private String apiKey; private long connectTimeout = 30; private long readTimeout = 30; private long writeTimeout = 30; private boolean loggingEnabled = false; public ApiConfig(String baseUrl) { this.baseUrl = baseUrl; } // Getters and setters public String getBaseUrl() { return baseUrl; } public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } public String getApiKey() { return apiKey; } public void setApiKey(String apiKey) { this.apiKey = apiKey; } public long getConnectTimeout() { return connectTimeout; } public void setConnectTimeout(long connectTimeout) { this.connectTimeout = connectTimeout; } public long getReadTimeout() { return readTimeout; } public void setReadTimeout(long readTimeout) { this.readTimeout = readTimeout; } public long getWriteTimeout() { return writeTimeout; } public void setWriteTimeout(long writeTimeout) { this.writeTimeout = writeTimeout; } public boolean isLoggingEnabled() { return loggingEnabled; } public void setLoggingEnabled(boolean loggingEnabled) { this.loggingEnabled = loggingEnabled; } } /** * API Key Interceptor */ public class ApiKeyInterceptor implements Interceptor { private final String apiKey; public ApiKeyInterceptor(String apiKey) { this.apiKey = apiKey; } @Override public Response intercept(Chain chain) throws IOException { Request original = chain.request(); Request.Builder builder = original.newBuilder(); // Add API key to header if (apiKey != null && !apiKey.isEmpty()) { builder.header("X-API-Key", apiKey); } Request request = builder.build(); return chain.proceed(request); } } /** * Authentication Interceptor for Bearer tokens */ public class AuthInterceptor implements Interceptor { private final String token; public AuthInterceptor(String token) { this.token = token; } @Override public Response intercept(Chain chain) throws IOException { Request original = chain.request(); Request.Builder builder = original.newBuilder(); if (token != null && !token.isEmpty()) { builder.header("Authorization", "Bearer " + token); } Request request = builder.build(); return chain.proceed(request); } } 7. Command Line Interface
/** * Command Line Interface for the REST API Client Generator */ public class ClientGeneratorCLI { public static void main(String[] args) { if (args.length == 0) { printHelp(); return; } GeneratorConfig config = new GeneratorConfig(); String specFile = null; String outputDir = "generated-client"; for (int i = 0; i < args.length; i++) { switch (args[i]) { case "-i": case "--input": if (i + 1 < args.length) { specFile = args[++i]; } break; case "-o": case "--output": if (i + 1 < args.length) { outputDir = args[++i]; } break; case "-p": case "--package": if (i + 1 < args.length) { config.setBasePackage(args[++i]); } break; case "--http-client": if (i + 1 < args.length) { config.setHttpClientType(GeneratorConfig.HttpClientType.valueOf(args[++i].toUpperCase())); } break; case "--no-lombok": config.setUseLombok(false); break; case "--async": config.setAsyncSupport(true); break; case "-h": case "--help": printHelp(); return; } } if (specFile == null) { System.err.println("Error: Input specification file is required"); printHelp(); return; } try { RestApiClientGenerator generator = new RestApiClientGenerator(config); generator.generateClient(specFile, outputDir); } catch (Exception e) { System.err.println("Error generating client: " + e.getMessage()); e.printStackTrace(); } } private static void printHelp() { System.out.println("REST API Client Generator"); System.out.println("Usage:"); System.out.println(" java ClientGeneratorCLI -i openapi.json -o output-dir"); System.out.println(); System.out.println("Options:"); System.out.println(" -i, --input FILE Input OpenAPI specification file (JSON/YAML)"); System.out.println(" -o, --output DIR Output directory (default: generated-client)"); System.out.println(" -p, --package NAME Base package name (default: com.api.client)"); System.out.println(" --http-client TYPE HTTP client: OKHTTP, APACHE_HTTP_CLIENT, JAVA_HTTP_CLIENT"); System.out.println(" --no-lombok Disable Lombok annotations"); System.out.println(" --async Generate async methods"); System.out.println(" -h, --help Show this help message"); } } 8. Maven Build Configuration
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.apiclient</groupId> <artifactId>rest-api-client-generator</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <jackson.version>2.15.2</jackson.version> <retrofit.version>2.9.0</retrofit.version> </properties> <dependencies> <!-- JSON/YAML Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-yaml</artifactId> <version>${jackson.version}</version> </dependency> <!-- HTTP Client (for generated clients) --> <dependency> <groupId>com.squareup.retrofit2</groupId> <artifactId>retrofit</artifactId> <version>${retrofit.version}</version> <optional>true</optional> </dependency> <dependency> <groupId>com.squareup.retrofit2</groupId> <artifactId>converter-jackson</artifactId> <version>${retrofit.version}</version> <optional>true</optional> </dependency> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>logging-interceptor</artifactId> <version>4.11.0</version> <optional>true</optional> </dependency> <!-- Lombok (for generated clients) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.28</version> <optional>true</optional> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <source>11</source> <target>11</target> </configuration> </plugin> </plugins> </build> </project> Usage Examples
Basic Usage
GeneratorConfig config = new GeneratorConfig("com.mycompany.api", "MyApiClient"); RestApiClientGenerator generator = new RestApiClientGenerator(config); generator.generateClient("openapi.yaml", "my-generated-client"); Using the Generated Client
// Create configuration ApiConfig config = new ApiConfig("https://api.example.com"); config.setApiKey("your-api-key"); config.setLoggingEnabled(true); // Create client instance MyApiClient client = new MyApiClient(config); // Use generated API methods User user = client.getUsersApi().getUserById(123).execute().body(); List<Product> products = client.getProductsApi().searchProducts("laptop").execute().body(); Command Line Usage
# Generate from OpenAPI spec java ClientGeneratorCLI -i openapi.json -o my-client -p com.mycompany.api # Generate with specific HTTP client java ClientGeneratorCLI -i openapi.yaml -o client --http-client OKHTTP --async
Features
- OpenAPI 3.0 Support: JSON and YAML specifications
- Type-Safe Clients: Generated Java interfaces with Retrofit
- Multiple HTTP Clients: OkHttp, Apache HttpClient, Java HttpClient
- Authentication: API keys, Bearer tokens, OAuth2
- Model Generation: POJOs with Jackson annotations
- Customizable: Configurable package names, client options
- Async Support: Optional asynchronous method generation
- Comprehensive: Error handling, logging, timeouts
This generator creates production-ready REST API clients that follow Java best practices and provide excellent developer experience.