Project Overview
A comprehensive OpenAPI/Swagger code generator that creates client SDKs, server stubs, and documentation from OpenAPI specifications. Supports multiple languages and frameworks with extensible templates.
Technology Stack
- Core: Java 17+
- OpenAPI Parsing: Swagger Parser
- Template Engine: FreeMarker
- Code Analysis: JavaParser
- Build Tools: Maven/Gradle integration
- Validation: JSON Schema Validator
- Testing: JUnit 5, Mockito
Project Structure
openapi-codegen/ ├── src/main/java/com/openapicodegen/ │ ├── core/ │ ├── generator/ │ ├── model/ │ ├── template/ │ ├── support/ │ ├── cli/ │ └── config/ ├── src/main/resources/templates/ │ ├── java/ │ ├── typescript/ │ ├── python/ │ └── go/ ├── generated-examples/ └── config/
Core Implementation
1. Data Models
package com.openapicodegen.model; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.PathItem; import io.swagger.v3.oas.models.parameters.Parameter; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.headers.Header; import java.util.*; public class CodeGenConfig { private String outputDir = "generated"; private String packageName; private String apiPackage; private String modelPackage; private String invokerPackage; private String groupId; private String artifactId; private String artifactVersion; private String library; private boolean generateApis = true; private boolean generateModels = true; private boolean generateSupportingFiles = true; private boolean useBeanValidation = false; private boolean useOptional = false; private boolean useRxJava = false; private boolean useSwaggerAnnotations = true; private Map<String, Object> additionalProperties = new HashMap<>(); private Set<String> importMappings = new HashSet<>(); private Set<String> typeMappings = new HashSet<>(); private Set<String> languageSpecificPrimitives = new HashSet<>(); // Constructors public CodeGenConfig() {} public CodeGenConfig(String packageName) { this.packageName = packageName; this.apiPackage = packageName + ".api"; this.modelPackage = packageName + ".model"; this.invokerPackage = packageName + ".invoker"; } // Builder pattern public static class Builder { private CodeGenConfig config = new CodeGenConfig(); public Builder outputDir(String outputDir) { config.outputDir = outputDir; return this; } public Builder packageName(String packageName) { config.packageName = packageName; config.apiPackage = packageName + ".api"; config.modelPackage = packageName + ".model"; config.invokerPackage = packageName + ".invoker"; return this; } public Builder groupId(String groupId) { config.groupId = groupId; return this; } public Builder artifactId(String artifactId) { config.artifactId = artifactId; return this; } public Builder artifactVersion(String artifactVersion) { config.artifactVersion = artifactVersion; return this; } public Builder useBeanValidation(boolean useBeanValidation) { config.useBeanValidation = useBeanValidation; return this; } public Builder useOptional(boolean useOptional) { config.useOptional = useOptional; return this; } public Builder additionalProperty(String key, Object value) { config.additionalProperties.put(key, value); return this; } public CodeGenConfig build() { return config; } } // Getters and Setters public String getOutputDir() { return outputDir; } public void setOutputDir(String outputDir) { this.outputDir = outputDir; } public String getPackageName() { return packageName; } public void setPackageName(String packageName) { this.packageName = packageName; } public String getApiPackage() { return apiPackage; } public void setApiPackage(String apiPackage) { this.apiPackage = apiPackage; } public String getModelPackage() { return modelPackage; } public void setModelPackage(String modelPackage) { this.modelPackage = modelPackage; } public String getInvokerPackage() { return invokerPackage; } public void setInvokerPackage(String invokerPackage) { this.invokerPackage = invokerPackage; } public String getGroupId() { return groupId; } public void setGroupId(String groupId) { this.groupId = groupId; } public String getArtifactId() { return artifactId; } public void setArtifactId(String artifactId) { this.artifactId = artifactId; } public String getArtifactVersion() { return artifactVersion; } public void setArtifactVersion(String artifactVersion) { this.artifactVersion = artifactVersion; } public String getLibrary() { return library; } public void setLibrary(String library) { this.library = library; } public boolean isGenerateApis() { return generateApis; } public void setGenerateApis(boolean generateApis) { this.generateApis = generateApis; } public boolean isGenerateModels() { return generateModels; } public void setGenerateModels(boolean generateModels) { this.generateModels = generateModels; } public boolean isGenerateSupportingFiles() { return generateSupportingFiles; } public void setGenerateSupportingFiles(boolean generateSupportingFiles) { this.generateSupportingFiles = generateSupportingFiles; } public boolean isUseBeanValidation() { return useBeanValidation; } public void setUseBeanValidation(boolean useBeanValidation) { this.useBeanValidation = useBeanValidation; } public boolean isUseOptional() { return useOptional; } public void setUseOptional(boolean useOptional) { this.useOptional = useOptional; } public boolean isUseRxJava() { return useRxJava; } public void setUseRxJava(boolean useRxJava) { this.useRxJava = useRxJava; } public boolean isUseSwaggerAnnotations() { return useSwaggerAnnotations; } public void setUseSwaggerAnnotations(boolean useSwaggerAnnotations) { this.useSwaggerAnnotations = useSwaggerAnnotations; } public Map<String, Object> getAdditionalProperties() { return additionalProperties; } public void setAdditionalProperties(Map<String, Object> additionalProperties) { this.additionalProperties = additionalProperties; } public Set<String> getImportMappings() { return importMappings; } public void setImportMappings(Set<String> importMappings) { this.importMappings = importMappings; } public Set<String> getTypeMappings() { return typeMappings; } public void setTypeMappings(Set<String> typeMappings) { this.typeMappings = typeMappings; } public Set<String> getLanguageSpecificPrimitives() { return languageSpecificPrimitives; } public void setLanguageSpecificPrimitives(Set<String> languageSpecificPrimitives) { this.languageSpecificPrimitives = languageSpecificPrimitives; } } 2. OpenAPI Model Extractor
package com.openapicodegen.model; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.PathItem; import io.swagger.v3.oas.models.parameters.Parameter; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.headers.Header; import io.swagger.v3.parser.OpenAPIV3Parser; import io.swagger.v3.parser.core.models.ParseOptions; import java.util.*; import java.util.stream.Collectors; public class OpenAPIExtractor { private final OpenAPI openAPI; public OpenAPIExtractor(OpenAPI openAPI) { this.openAPI = openAPI; } public static OpenAPIExtractor fromFile(String filePath) { ParseOptions options = new ParseOptions(); options.setResolve(true); options.setResolveFully(true); OpenAPI openAPI = new OpenAPIV3Parser().read(filePath, null, options); if (openAPI == null) { throw new IllegalArgumentException("Invalid OpenAPI specification: " + filePath); } return new OpenAPIExtractor(openAPI); } public static OpenAPIExtractor fromContent(String content) { ParseOptions options = new ParseOptions(); options.setResolve(true); options.setResolveFully(true); OpenAPI openAPI = new OpenAPIV3Parser().readContents(content, null, options).getOpenAPI(); if (openAPI == null) { throw new IllegalArgumentException("Invalid OpenAPI specification content"); } return new OpenAPIExtractor(openAPI); } public List<ApiOperation> extractOperations() { List<ApiOperation> operations = new ArrayList<>(); if (openAPI.getPaths() == null) { return operations; } openAPI.getPaths().forEach((path, pathItem) -> { extractOperationsFromPathItem(path, pathItem, operations); }); return operations; } private void extractOperationsFromPathItem(String path, PathItem pathItem, List<ApiOperation> operations) { if (pathItem.getGet() != null) { operations.add(createApiOperation(path, "GET", pathItem.getGet())); } if (pathItem.getPost() != null) { operations.add(createApiOperation(path, "POST", pathItem.getPost())); } if (pathItem.getPut() != null) { operations.add(createApiOperation(path, "PUT", pathItem.getPut())); } if (pathItem.getDelete() != null) { operations.add(createApiOperation(path, "DELETE", pathItem.getDelete())); } if (pathItem.getPatch() != null) { operations.add(createApiOperation(path, "PATCH", pathItem.getPatch())); } if (pathItem.getHead() != null) { operations.add(createApiOperation(path, "HEAD", pathItem.getHead())); } if (pathItem.getOptions() != null) { operations.add(createApiOperation(path, "OPTIONS", pathItem.getOptions())); } } private ApiOperation createApiOperation(String path, String httpMethod, Operation operation) { ApiOperation apiOperation = new ApiOperation(); apiOperation.setPath(path); apiOperation.setHttpMethod(httpMethod); apiOperation.setOperationId(operation.getOperationId()); apiOperation.setSummary(operation.getSummary()); apiOperation.setDescription(operation.getDescription()); apiOperation.setTags(operation.getTags()); apiOperation.setDeprecated(operation.getDeprecated() != null && operation.getDeprecated()); // Extract parameters if (operation.getParameters() != null) { List<ApiParameter> parameters = operation.getParameters().stream() .map(this::mapParameter) .collect(Collectors.toList()); apiOperation.setParameters(parameters); } // Extract responses if (operation.getResponses() != null) { Map<String, ApiResponse> responses = new HashMap<>(); operation.getResponses().forEach((code, response) -> { responses.put(code, mapResponse(code, response)); }); apiOperation.setResponses(responses); } return apiOperation; } private ApiParameter mapParameter(Parameter parameter) { ApiParameter apiParam = new ApiParameter(); apiParam.setName(parameter.getName()); apiParam.setIn(parameter.getIn()); apiParam.setRequired(parameter.getRequired() != null && parameter.getRequired()); apiParam.setDescription(parameter.getDescription()); apiParam.setSchema(parameter.getSchema()); apiParam.setStyle(parameter.getStyle()); apiParam.setExplode(parameter.getExplode()); return apiParam; } private ApiResponse mapResponse(String code, io.swagger.v3.oas.models.responses.ApiResponse response) { ApiResponse apiResponse = new ApiResponse(); apiResponse.setCode(code); apiResponse.setDescription(response.getDescription()); if (response.getHeaders() != null) { Map<String, ApiHeader> headers = new HashMap<>(); response.getHeaders().forEach((name, header) -> { headers.put(name, mapHeader(name, header)); }); apiResponse.setHeaders(headers); } if (response.getContent() != null && !response.getContent().isEmpty()) { // Get the first media type (usually application/json) var mediaType = response.getContent().values().iterator().next(); apiResponse.setSchema(mediaType.getSchema()); } return apiResponse; } private ApiHeader mapHeader(String name, Header header) { ApiHeader apiHeader = new ApiHeader(); apiHeader.setName(name); apiHeader.setDescription(header.getDescription()); apiHeader.setSchema(header.getSchema()); apiHeader.setRequired(header.getRequired() != null && header.getRequired()); return apiHeader; } public List<ApiModel> extractModels() { List<ApiModel> models = new ArrayList<>(); if (openAPI.getComponents() == null || openAPI.getComponents().getSchemas() == null) { return models; } openAPI.getComponents().getSchemas().forEach((name, schema) -> { models.add(createApiModel(name, schema)); }); return models; } private ApiModel createApiModel(String name, Schema schema) { ApiModel model = new ApiModel(); model.setName(name); model.setSchema(schema); model.setDescription(schema.getDescription()); model.setType(schema.getType()); model.setFormat(schema.getFormat()); // Extract properties if (schema.getProperties() != null) { Map<String, ApiProperty> properties = new HashMap<>(); schema.getProperties().forEach((propName, propSchema) -> { properties.put(propName, createApiProperty(propName, propSchema)); }); model.setProperties(properties); } // Extract required fields if (schema.getRequired() != null) { model.setRequiredFields(new ArrayList<>(schema.getRequired())); } // Handle inheritance if (schema.getAllOf() != null) { model.setAllOf(schema.getAllOf()); } if (schema.getOneOf() != null) { model.setOneOf(schema.getOneOf()); } if (schema.getAnyOf() != null) { model.setAnyOf(schema.getAnyOf()); } return model; } private ApiProperty createApiProperty(String name, Schema schema) { ApiProperty property = new ApiProperty(); property.setName(name); property.setSchema(schema); property.setType(schema.getType()); property.setFormat(schema.getFormat()); property.setDescription(schema.getDescription()); property.setRequired(false); // This will be set by the parent model property.setReadOnly(schema.getReadOnly() != null && schema.getReadOnly()); property.setWriteOnly(schema.getWriteOnly() != null && schema.getWriteOnly()); property.setNullable(schema.getNullable() != null && schema.getNullable()); // Handle arrays if ("array".equals(schema.getType()) && schema.getItems() != null) { property.setItemsSchema(schema.getItems()); } return property; } public OpenAPI getOpenAPI() { return openAPI; } public String getTitle() { return openAPI.getInfo() != null ? openAPI.getInfo().getTitle() : "Unknown API"; } public String getVersion() { return openAPI.getInfo() != null ? openAPI.getInfo().getVersion() : "1.0.0"; } public String getDescription() { return openAPI.getInfo() != null ? openAPI.getInfo().getDescription() : ""; } public String getBasePath() { if (openAPI.getServers() != null && !openAPI.getServers().isEmpty()) { return openAPI.getServers().get(0).getUrl(); } return "/"; } } 3. Model Classes
package com.openapicodegen.model; import io.swagger.v3.oas.models.media.Schema; import java.util.*; public class ApiOperation { private String path; private String httpMethod; private String operationId; private String summary; private String description; private List<String> tags; private boolean deprecated; private List<ApiParameter> parameters = new ArrayList<>(); private Map<String, ApiResponse> responses = new HashMap<>(); private Schema requestBody; // Getters and Setters public String getPath() { return path; } public void setPath(String path) { this.path = path; } public String getHttpMethod() { return httpMethod; } public void setHttpMethod(String httpMethod) { this.httpMethod = httpMethod; } public String getOperationId() { return operationId; } public void setOperationId(String operationId) { this.operationId = operationId; } public String getSummary() { return summary; } public void setSummary(String summary) { this.summary = summary; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public List<String> getTags() { return tags; } public void setTags(List<String> tags) { this.tags = tags; } public boolean isDeprecated() { return deprecated; } public void setDeprecated(boolean deprecated) { this.deprecated = deprecated; } public List<ApiParameter> getParameters() { return parameters; } public void setParameters(List<ApiParameter> parameters) { this.parameters = parameters; } public Map<String, ApiResponse> getResponses() { return responses; } public void setResponses(Map<String, ApiResponse> responses) { this.responses = responses; } public Schema getRequestBody() { return requestBody; } public void setRequestBody(Schema requestBody) { this.requestBody = requestBody; } public String getMethodName() { if (operationId != null && !operationId.trim().isEmpty()) { return sanitizeMethodName(operationId); } return generateMethodNameFromPath(); } private String sanitizeMethodName(String name) { // Remove special characters and make camelCase return name.replaceAll("[^a-zA-Z0-9_]", "_") .replaceAll("_+", "_") .replaceAll("^_|_$", ""); } private String generateMethodNameFromPath() { String[] parts = path.split("/"); StringBuilder methodName = new StringBuilder(httpMethod.toLowerCase()); for (String part : parts) { if (!part.isEmpty() && !part.startsWith("{") && !part.startsWith("}")) { methodName.append(Character.toUpperCase(part.charAt(0))) .append(part.substring(1)); } } return methodName.toString(); } } public class ApiParameter { private String name; private String in; // query, path, header, cookie private boolean required; private String description; private Schema schema; private String style; private Boolean explode; // Getters and Setters public String getName() { return name; } public void setName(String name) { this.name = name; } public String getIn() { return in; } public void setIn(String in) { this.in = in; } 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 Schema getSchema() { return schema; } public void setSchema(Schema schema) { this.schema = schema; } public String getStyle() { return style; } public void setStyle(String style) { this.style = style; } public Boolean getExplode() { return explode; } public void setExplode(Boolean explode) { this.explode = explode; } public String getJavaType() { return TypeMapper.mapType(schema); } public String getParameterName() { return sanitizeIdentifier(name); } private String sanitizeIdentifier(String identifier) { // Replace invalid characters and reserved words if (JavaReservedWords.isReserved(identifier)) { return identifier + "Param"; } return identifier.replaceAll("[^a-zA-Z0-9_]", "_"); } } public class ApiResponse { private String code; private String description; private Map<String, ApiHeader> headers = new HashMap<>(); private Schema schema; // Getters and Setters public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Map<String, ApiHeader> getHeaders() { return headers; } public void setHeaders(Map<String, ApiHeader> headers) { this.headers = headers; } public Schema getSchema() { return schema; } public void setSchema(Schema schema) { this.schema = schema; } public String getJavaType() { return TypeMapper.mapType(schema); } public boolean isSuccess() { return code.startsWith("2"); } } public class ApiHeader { private String name; private String description; private Schema schema; private boolean required; // Getters and Setters public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Schema getSchema() { return schema; } public void setSchema(Schema schema) { this.schema = schema; } public boolean isRequired() { return required; } public void setRequired(boolean required) { this.required = required; } } public class ApiModel { private String name; private Schema schema; private String description; private String type; private String format; private Map<String, ApiProperty> properties = new HashMap<>(); private List<String> requiredFields = new ArrayList<>(); private List<Schema> allOf; private List<Schema> oneOf; private List<Schema> anyOf; // Getters and Setters public String getName() { return name; } public void setName(String name) { this.name = name; } public Schema getSchema() { return schema; } public void setSchema(Schema schema) { this.schema = schema; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getFormat() { return format; } public void setFormat(String format) { this.format = format; } public Map<String, ApiProperty> getProperties() { return properties; } public void setProperties(Map<String, ApiProperty> properties) { this.properties = properties; } public List<String> getRequiredFields() { return requiredFields; } public void setRequiredFields(List<String> requiredFields) { this.requiredFields = requiredFields; } public List<Schema> getAllOf() { return allOf; } public void setAllOf(List<Schema> allOf) { this.allOf = allOf; } public List<Schema> getOneOf() { return oneOf; } public void setOneOf(List<Schema> oneOf) { this.oneOf = oneOf; } public List<Schema> getAnyOf() { return anyOf; } public void setAnyOf(List<Schema> anyOf) { this.anyOf = anyOf; } public String getClassName() { return sanitizeClassName(name); } private String sanitizeClassName(String name) { // Remove special characters and ensure PascalCase String className = name.replaceAll("[^a-zA-Z0-9]", " "); className = Arrays.stream(className.split("\\s+")) .map(part -> Character.toUpperCase(part.charAt(0)) + part.substring(1).toLowerCase()) .collect(Collectors.joining()); if (JavaReservedWords.isReserved(className)) { return className + "Model"; } return className; } public boolean hasProperties() { return properties != null && !properties.isEmpty(); } public boolean isEnum() { return schema.getEnum() != null && !schema.getEnum().isEmpty(); } public List<Object> getEnumValues() { return schema.getEnum() != null ? schema.getEnum() : Collections.emptyList(); } } public class ApiProperty { private String name; private Schema schema; private String type; private String format; private String description; private boolean required; private boolean readOnly; private boolean writeOnly; private boolean nullable; private Schema itemsSchema; // Getters and Setters public String getName() { return name; } public void setName(String name) { this.name = name; } public Schema getSchema() { return schema; } public void setSchema(Schema schema) { this.schema = schema; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getFormat() { return format; } public void setFormat(String format) { this.format = format; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public boolean isRequired() { return required; } public void setRequired(boolean required) { this.required = required; } public boolean isReadOnly() { return readOnly; } public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; } public boolean isWriteOnly() { return writeOnly; } public void setWriteOnly(boolean writeOnly) { this.writeOnly = writeOnly; } public boolean isNullable() { return nullable; } public void setNullable(boolean nullable) { this.nullable = nullable; } public Schema getItemsSchema() { return itemsSchema; } public void setItemsSchema(Schema itemsSchema) { this.itemsSchema = itemsSchema; } public String getJavaType() { return TypeMapper.mapType(schema); } public String getFieldName() { return sanitizeFieldName(name); } private String sanitizeFieldName(String name) { // Convert to camelCase and handle reserved words String fieldName = name.replaceAll("[^a-zA-Z0-9_]", " "); fieldName = Arrays.stream(fieldName.split("\\s+")) .map(part -> Character.toUpperCase(part.charAt(0)) + part.substring(1).toLowerCase()) .collect(Collectors.joining()); fieldName = Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1); if (JavaReservedWords.isReserved(fieldName)) { return fieldName + "Field"; } return fieldName; } public String getGetterName() { String prefix = "boolean".equals(getJavaType()) ? "is" : "get"; return prefix + Character.toUpperCase(getFieldName().charAt(0)) + getFieldName().substring(1); } public String getSetterName() { return "set" + Character.toUpperCase(getFieldName().charAt(0)) + getFieldName().substring(1); } } 4. Type Mapper
package com.openapicodegen.support; import io.swagger.v3.oas.models.media.Schema; import java.util.HashMap; import java.util.Map; public class TypeMapper { private static final Map<String, String> PRIMITIVE_TYPES = new HashMap<>(); private static final Map<String, String> CONTAINER_TYPES = new HashMap<>(); static { // Java primitive mappings PRIMITIVE_TYPES.put("string", "String"); PRIMITIVE_TYPES.put("number", "BigDecimal"); PRIMITIVE_TYPES.put("integer", "Integer"); PRIMITIVE_TYPES.put("boolean", "Boolean"); PRIMITIVE_TYPES.put("array", "List"); PRIMITIVE_TYPES.put("object", "Object"); // Format-specific mappings PRIMITIVE_TYPES.put("int32", "Integer"); PRIMITIVE_TYPES.put("int64", "Long"); PRIMITIVE_TYPES.put("float", "Float"); PRIMITIVE_TYPES.put("double", "Double"); PRIMITIVE_TYPES.put("byte", "byte[]"); PRIMITIVE_TYPES.put("binary", "byte[]"); PRIMITIVE_TYPES.put("date", "LocalDate"); PRIMITIVE_TYPES.put("date-time", "LocalDateTime"); PRIMITIVE_TYPES.put("uuid", "UUID"); // Container types CONTAINER_TYPES.put("array", "List"); CONTAINER_TYPES.put("map", "Map"); } public static String mapType(Schema schema) { if (schema == null) { return "Object"; } String type = schema.getType(); String format = schema.getFormat(); // Handle references if (schema.get$ref() != null) { return extractClassNameFromRef(schema.get$ref()); } // Handle arrays if ("array".equals(type) && schema.getItems() != null) { String itemType = mapType(schema.getItems()); return "List<" + itemType + ">"; } // Handle maps/objects with additionalProperties if ("object".equals(type) && schema.getAdditionalProperties() instanceof Schema) { Schema valueSchema = (Schema) schema.getAdditionalProperties(); String valueType = mapType(valueSchema); return "Map<String, " + valueType + ">"; } // Handle enums if (schema.getEnum() != null && !schema.getEnum().isEmpty()) { return "String"; // Enums will be generated as separate classes } // Handle format-specific types if (format != null && PRIMITIVE_TYPES.containsKey(format)) { return PRIMITIVE_TYPES.get(format); } // Handle basic types if (type != null && PRIMITIVE_TYPES.containsKey(type)) { return PRIMITIVE_TYPES.get(type); } return "Object"; } private static String extractClassNameFromRef(String ref) { // Extract class name from #/components/schemas/User String[] parts = ref.split("/"); return parts[parts.length - 1]; } public static String mapTypeToTypeScript(Schema schema) { if (schema == null) { return "any"; } String type = schema.getType(); String format = schema.getFormat(); if (schema.get$ref() != null) { return extractClassNameFromRef(schema.get$ref()); } if ("array".equals(type) && schema.getItems() != null) { String itemType = mapTypeToTypeScript(schema.getItems()); return itemType + "[]"; } if ("integer".equals(type)) { return "number"; } if ("string".equals(type)) { if ("date".equals(format) || "date-time".equals(format)) { return "Date"; } return "string"; } if ("boolean".equals(type)) { return "boolean"; } if ("number".equals(type)) { return "number"; } if ("object".equals(type)) { return "any"; } return "any"; } public static String mapTypeToPython(Schema schema) { if (schema == null) { return "Any"; } String type = schema.getType(); if (schema.get$ref() != null) { return extractClassNameFromRef(schema.get$ref()); } if ("array".equals(type)) { return "List"; } if ("integer".equals(type)) { return "int"; } if ("string".equals(type)) { return "str"; } if ("boolean".equals(type)) { return "bool"; } if ("number".equals(type)) { return "float"; } if ("object".equals(type)) { return "Dict"; } return "Any"; } } 5. Template Engine
package com.openapicodegen.template; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; import freemarker.template.TemplateExceptionHandler; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Map; public class TemplateEngine { private final Configuration freeMarkerConfig; public TemplateEngine() { this.freeMarkerConfig = createConfiguration(); } public TemplateEngine(String templateDirectory) { this.freeMarkerConfig = createConfiguration(templateDirectory); } private Configuration createConfiguration() { try { Configuration cfg = new Configuration(Configuration.VERSION_2_3_31); cfg.setClassForTemplateLoading(TemplateEngine.class, "/templates"); cfg.setDefaultEncoding("UTF-8"); cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); cfg.setLogTemplateExceptions(false); cfg.setWrapUncheckedExceptions(true); cfg.setFallbackOnNullLoopVariable(false); return cfg; } catch (Exception e) { throw new RuntimeException("Failed to initialize FreeMarker configuration", e); } } private Configuration createConfiguration(String templateDirectory) { try { Configuration cfg = new Configuration(Configuration.VERSION_2_3_31); cfg.setDirectoryForTemplateLoading(new File(templateDirectory)); cfg.setDefaultEncoding("UTF-8"); cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); cfg.setLogTemplateExceptions(false); cfg.setWrapUncheckedExceptions(true); cfg.setFallbackOnNullLoopVariable(false); return cfg; } catch (Exception e) { throw new RuntimeException("Failed to initialize FreeMarker configuration", e); } } public String processTemplate(String templateName, Map<String, Object> dataModel) { try { Template template = freeMarkerConfig.getTemplate(templateName); StringWriter writer = new StringWriter(); template.process(dataModel, writer); return writer.toString(); } catch (IOException | TemplateException e) { throw new RuntimeException("Failed to process template: " + templateName, e); } } public void processTemplateToFile(String templateName, Map<String, Object> dataModel, String outputFilePath) { try { String content = processTemplate(templateName, dataModel); writeToFile(outputFilePath, content); } catch (Exception e) { throw new RuntimeException("Failed to write template to file: " + outputFilePath, e); } } private void writeToFile(String filePath, String content) throws IOException { Path path = Paths.get(filePath); Files.createDirectories(path.getParent()); Files.writeString(path, content); } public boolean templateExists(String templateName) { try { Template template = freeMarkerConfig.getTemplate(templateName); return template != null; } catch (IOException e) { return false; } } public String[] getAvailableTemplates() { // This would require custom implementation to list templates // For simplicity, returning common templates return new String[]{ "java/model.ftl", "java/api.ftl", "java/api_test.ftl", "java/pom.ftl", "typescript/model.ftl", "typescript/api.ftl", "python/model.ftl", "python/api.ftl" }; } } 6. Code Generators
package com.openapicodegen.generator; import com.openapicodegen.model.*; import com.openapicodegen.template.TemplateEngine; import java.io.File; import java.util.*; public abstract class CodeGenerator { protected final CodeGenConfig config; protected final TemplateEngine templateEngine; public CodeGenerator(CodeGenConfig config) { this.config = config; this.templateEngine = new TemplateEngine(); } public abstract void generate(OpenAPIExtractor extractor); protected void createDirectory(String path) { File dir = new File(path); if (!dir.exists()) { dir.mkdirs(); } } protected String getOutputPath(String... paths) { StringBuilder outputPath = new StringBuilder(config.getOutputDir()); for (String path : paths) { outputPath.append(File.separator).append(path); } return outputPath.toString(); } protected Map<String, Object> createBaseDataModel(OpenAPIExtractor extractor) { Map<String, Object> dataModel = new HashMap<>(); dataModel.put("config", config); dataModel.put("openAPI", extractor.getOpenAPI()); dataModel.put("title", extractor.getTitle()); dataModel.put("version", extractor.getVersion()); dataModel.put("description", extractor.getDescription()); dataModel.put("basePath", extractor.getBasePath()); // Add additional properties dataModel.putAll(config.getAdditionalProperties()); return dataModel; } } public class JavaClientGenerator extends CodeGenerator { public JavaClientGenerator(CodeGenConfig config) { super(config); } @Override public void generate(OpenAPIExtractor extractor) { System.out.println("Generating Java client code..."); // Create directory structure createJavaDirectoryStructure(); // Generate models generateModels(extractor); // Generate API classes generateApis(extractor); // Generate supporting files generateSupportingFiles(extractor); System.out.println("Java client generation completed!"); } private void createJavaDirectoryStructure() { String baseDir = getOutputPath("src", "main", "java"); String[] packages = { config.getModelPackage().replace('.', '/'), config.getApiPackage().replace('.', '/'), config.getInvokerPackage().replace('.', '/') }; for (String pkg : packages) { createDirectory(getOutputPath("src", "main", "java", pkg)); } // Create test directory createDirectory(getOutputPath("src", "test", "java")); } private void generateModels(OpenAPIExtractor extractor) { if (!config.isGenerateModels()) return; List<ApiModel> models = extractor.extractModels(); Map<String, Object> baseModel = createBaseDataModel(extractor); for (ApiModel model : models) { Map<String, Object> dataModel = new HashMap<>(baseModel); dataModel.put("model", model); dataModel.put("className", model.getClassName()); String outputFile = getOutputPath("src", "main", "java", config.getModelPackage().replace('.', '/'), model.getClassName() + ".java"); templateEngine.processTemplateToFile("java/model.ftl", dataModel, outputFile); System.out.println("Generated model: " + model.getClassName()); } } private void generateApis(OpenAPIExtractor extractor) { if (!config.isGenerateApis()) return; List<ApiOperation> operations = extractor.extractOperations(); Map<String, Object> baseModel = createBaseDataModel(extractor); // Group operations by tag Map<String, List<ApiOperation>> operationsByTag = groupOperationsByTag(operations); for (Map.Entry<String, List<ApiOperation>> entry : operationsByTag.entrySet()) { String tag = entry.getKey(); List<ApiOperation> tagOperations = entry.getValue(); Map<String, Object> dataModel = new HashMap<>(baseModel); dataModel.put("tag", tag); dataModel.put("operations", tagOperations); dataModel.put("className", getApiClassName(tag)); String outputFile = getOutputPath("src", "main", "java", config.getApiPackage().replace('.', '/'), getApiClassName(tag) + ".java"); templateEngine.processTemplateToFile("java/api.ftl", dataModel, outputFile); System.out.println("Generated API: " + getApiClassName(tag)); // Generate tests generateApiTests(tag, tagOperations, baseModel); } } private Map<String, List<ApiOperation>> groupOperationsByTag(List<ApiOperation> operations) { Map<String, List<ApiOperation>> grouped = new HashMap<>(); for (ApiOperation operation : operations) { List<String> tags = operation.getTags(); if (tags == null || tags.isEmpty()) { // Use "DefaultApi" for operations without tags grouped.computeIfAbsent("Default", k -> new ArrayList<>()).add(operation); } else { for (String tag : tags) { grouped.computeIfAbsent(tag, k -> new ArrayList<>()).add(operation); } } } return grouped; } private String getApiClassName(String tag) { return tag + "Api"; } private void generateApiTests(String tag, List<ApiOperation> operations, Map<String, Object> baseModel) { Map<String, Object> dataModel = new HashMap<>(baseModel); dataModel.put("tag", tag); dataModel.put("operations", operations); dataModel.put("className", getApiClassName(tag) + "Test"); String outputFile = getOutputPath("src", "test", "java", config.getApiPackage().replace('.', '/'), getApiClassName(tag) + "Test.java"); templateEngine.processTemplateToFile("java/api_test.ftl", dataModel, outputFile); } private void generateSupportingFiles(OpenAPIExtractor extractor) { if (!config.isGenerateSupportingFiles()) return; Map<String, Object> dataModel = createBaseDataModel(extractor); // Generate pom.xml templateEngine.processTemplateToFile("java/pom.ftl", dataModel, getOutputPath("pom.xml")); // Generate README templateEngine.processTemplateToFile("java/readme.ftl", dataModel, getOutputPath("README.md")); // Generate ApiClient templateEngine.processTemplateToFile("java/api_client.ftl", dataModel, getOutputPath("src", "main", "java", config.getInvokerPackage().replace('.', '/'), "ApiClient.java")); // Generate Configuration templateEngine.processTemplateToFile("java/configuration.ftl", dataModel, getOutputPath("src", "main", "java", config.getInvokerPackage().replace('.', '/'), "Configuration.java")); } } public class TypeScriptGenerator extends CodeGenerator { public TypeScriptGenerator(CodeGenConfig config) { super(config); } @Override public void generate(OpenAPIExtractor extractor) { System.out.println("Generating TypeScript client code..."); createDirectory(getOutputPath("src")); createDirectory(getOutputPath("src", "models")); createDirectory(getOutputPath("src", "apis")); generateTypeScriptModels(extractor); generateTypeScriptApis(extractor); generateTypeScriptSupportingFiles(extractor); System.out.println("TypeScript client generation completed!"); } private void generateTypeScriptModels(OpenAPIExtractor extractor) { List<ApiModel> models = extractor.extractModels(); Map<String, Object> baseModel = createBaseDataModel(extractor); for (ApiModel model : models) { Map<String, Object> dataModel = new HashMap<>(baseModel); dataModel.put("model", model); dataModel.put("interfaceName", model.getClassName()); String outputFile = getOutputPath("src", "models", model.getClassName() + ".ts"); templateEngine.processTemplateToFile("typescript/model.ftl", dataModel, outputFile); } } private void generateTypeScriptApis(OpenAPIExtractor extractor) { List<ApiOperation> operations = extractor.extractOperations(); Map<String, List<ApiOperation>> operationsByTag = groupOperationsByTag(operations); Map<String, Object> baseModel = createBaseDataModel(extractor); for (Map.Entry<String, List<ApiOperation>> entry : operationsByTag.entrySet()) { Map<String, Object> dataModel = new HashMap<>(baseModel); dataModel.put("tag", entry.getKey()); dataModel.put("operations", entry.getValue()); dataModel.put("className", entry.getKey() + "Api"); String outputFile = getOutputPath("src", "apis", entry.getKey() + "Api.ts"); templateEngine.processTemplateToFile("typescript/api.ftl", dataModel, outputFile); } } private void generateTypeScriptSupportingFiles(OpenAPIExtractor extractor) { Map<String, Object> dataModel = createBaseDataModel(extractor); templateEngine.processTemplateToFile("typescript/package.ftl", dataModel, getOutputPath("package.json")); templateEngine.processTemplateToFile("typescript/tsconfig.ftl", dataModel, getOutputPath("tsconfig.json")); templateEngine.processTemplateToFile("typescript/api_client.ftl", dataModel, getOutputPath("src", "ApiClient.ts")); } } 7. Generator Factory
package com.openapicodegen.generator; import com.openapicodegen.model.CodeGenConfig; import java.util.HashMap; import java.util.Map; public class GeneratorFactory { private static final Map<String, Class<? extends CodeGenerator>> GENERATORS = new HashMap<>(); static { GENERATORS.put("java", JavaClientGenerator.class); GENERATORS.put("typescript", TypeScriptGenerator.class); GENERATORS.put("python", PythonGenerator.class); GENERATORS.put("spring", SpringServerGenerator.class); GENERATORS.put("nodejs", NodeJsServerGenerator.class); } public static CodeGenerator createGenerator(String language, CodeGenConfig config) { Class<? extends CodeGenerator> generatorClass = GENERATORS.get(language.toLowerCase()); if (generatorClass == null) { throw new IllegalArgumentException("Unsupported language: " + language); } try { return generatorClass.getConstructor(CodeGenConfig.class).newInstance(config); } catch (Exception e) { throw new RuntimeException("Failed to create generator for language: " + language, e); } } public static void registerGenerator(String language, Class<? extends CodeGenerator> generatorClass) { GENERATORS.put(language.toLowerCase(), generatorClass); } public static String[] getSupportedLanguages() { return GENERATORS.keySet().toArray(new String[0]); } } 8. Main Code Generator Service
package com.openapicodegen.core; import com.openapicodegen.generator.CodeGenerator; import com.openapicodegen.generator.GeneratorFactory; import com.openapicodegen.model.CodeGenConfig; import com.openapicodegen.model.OpenAPIExtractor; public class OpenAPICodeGenerator { public void generate(String openApiSpec, String language, CodeGenConfig config) { try { // Parse OpenAPI specification OpenAPIExtractor extractor = OpenAPIExtractor.fromFile(openApiSpec); // Create appropriate generator CodeGenerator generator = GeneratorFactory.createGenerator(language, config); // Generate code generator.generate(extractor); } catch (Exception e) { throw new RuntimeException("Code generation failed", e); } } public void generateFromContent(String openApiContent, String language, CodeGenConfig config) { try { // Parse OpenAPI specification from content OpenAPIExtractor extractor = OpenAPIExtractor.fromContent(openApiContent); // Create appropriate generator CodeGenerator generator = GeneratorFactory.createGenerator(language, config); // Generate code generator.generate(extractor); } catch (Exception e) { throw new RuntimeException("Code generation failed", e); } } public String[] getSupportedLanguages() { return GeneratorFactory.getSupportedLanguages(); } public void validateOpenAPISpec(String openApiSpec) { try { OpenAPIExtractor extractor = OpenAPIExtractor.fromFile(openApiSpec); System.out.println("✓ Valid OpenAPI specification: " + extractor.getTitle()); } catch (Exception e) { throw new RuntimeException("Invalid OpenAPI specification: " + e.getMessage()); } } } 9. CLI Interface
package com.openapicodegen.cli; import com.openapicodegen.core.OpenAPICodeGenerator; import com.openapicodegen.model.CodeGenConfig; import picocli.CommandLine; import picocli.CommandLine.Command; import picocli.CommandLine.Option; import picocli.CommandLine.Parameters; import java.io.File; import java.util.concurrent.Callable; @Command(name = "openapi-codegen", mixinStandardHelpOptions = true, version = "OpenAPI Code Generator 1.0", description = "Generates client SDKs and server stubs from OpenAPI specifications") public class CLIRunner implements Callable<Integer> { @Parameters(index = "0", description = "The OpenAPI specification file") private File specFile; @Option(names = {"-l", "--language"}, description = "Target language: ${COMPLETION-CANDIDATES}", required = true) private String language; @Option(names = {"-o", "--output"}, description = "Output directory (default: generated)") private String outputDir = "generated"; @Option(names = {"-p", "--package"}, description = "Base package name for generated code") private String packageName; @Option(names = {"--group-id"}, description = "Maven groupId (for Java)") private String groupId; @Option(names = {"--artifact-id"}, description = "Maven artifactId (for Java)") private String artifactId; @Option(names = {"--artifact-version"}, description = "Maven artifact version (for Java)") private String artifactVersion = "1.0.0"; @Option(names = {"--validate"}, description = "Validate the OpenAPI specification without generating code") private boolean validateOnly = false; @Option(names = {"--list-languages"}, description = "List supported languages") private boolean listLanguages = false; private final OpenAPICodeGenerator generator = new OpenAPICodeGenerator(); @Override public Integer call() throws Exception { if (listLanguages) { System.out.println("Supported languages:"); for (String lang : generator.getSupportedLanguages()) { System.out.println(" - " + lang); } return 0; } if (!specFile.exists()) { System.err.println("Error: Specification file not found: " + specFile); return 1; } if (validateOnly) { try { generator.validateOpenAPISpec(specFile.getAbsolutePath()); System.out.println("✓ Specification is valid"); return 0; } catch (Exception e) { System.err.println("✗ Invalid specification: " + e.getMessage()); return 1; } } try { CodeGenConfig config = createConfig(); generator.generate(specFile.getAbsolutePath(), language, config); System.out.println("✓ Code generation completed successfully!"); return 0; } catch (Exception e) { System.err.println("✗ Code generation failed: " + e.getMessage()); e.printStackTrace(); return 1; } } private CodeGenConfig createConfig() { CodeGenConfig.Builder builder = new CodeGenConfig.Builder(); builder.outputDir(outputDir); if (packageName != null) { builder.packageName(packageName); } if (groupId != null) { builder.groupId(groupId); } if (artifactId != null) { builder.artifactId(artifactId); } builder.artifactVersion(artifactVersion); return builder.build(); } public static void main(String[] args) { int exitCode = new CommandLine(new CLIRunner()).execute(args); System.exit(exitCode); } } 10. FreeMarker Templates
Java Model Template (java/model.ftl):
package ${config.modelPackage}; <#if config.useSwaggerAnnotations> import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; </#if> <#if config.useBeanValidation> import javax.validation.constraints.*; </#if> import java.util.Objects; <#-- Import required types based on properties --> <#list model.properties as property> <#if property.javaType?starts_with("List")> import java.util.List; import java.util.ArrayList; <#break> </#if> </#list> <#if config.useSwaggerAnnotations> @ApiModel(description = "${model.description!}") </#if> public class ${model.className} { <#list model.properties as property> <#if config.useSwaggerAnnotations> @ApiModelProperty(<#if property.description??>value = "${property.description}", </#if><#if property.required>required = true</#if>) </#if> <#if config.useBeanValidation> <#if property.required> @NotNull </#if> </#if> private ${property.javaType} ${property.fieldName}; </#list> public ${model.className}() { } <#list model.properties as property> public ${property.javaType} ${property.getterName}() { return ${property.fieldName}; } public void ${property.setterName}(${property.javaType} ${property.fieldName}) { this.${property.fieldName} = ${property.fieldName}; } </#list> @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ${model.className} that = (${model.className}) o; return <#list model.properties as property>Objects.equals(${property.fieldName}, that.${property.fieldName})<#if property_has_next> && </#if></#list>; } @Override public int hashCode() { return Objects.hash(<#list model.properties as property>${property.fieldName}<#if property_has_next>, </#if></#list>); } @Override public String toString() { return "${model.className}{" + <#list model.properties as property> "${property.fieldName}=" + ${property.fieldName} + <#if property_has_next>", "</#if> </#list> '}'; } } Java API Template (java/api.ftl):
package ${config.apiPackage}; <#if config.useSwaggerAnnotations> import io.swagger.annotations.*; </#if> import ${config.modelPackage}.*; import org.springframework.web.bind.annotation.*; import org.springframework.http.ResponseEntity; <#if config.useSwaggerAnnotations> @Api(value = "${tag}", description = "The ${tag} API") </#if> public interface ${className} { <#list operations as operation> <#if config.useSwaggerAnnotations> @ApiOperation(value = "${operation.summary!}", notes = "${operation.description!}") @ApiResponses({ <#list operation.responses as response> @ApiResponse(code = ${response.code}, message = "${response.description!}")<#if response_has_next>,</#if> </#list> }) </#if> @RequestMapping(value = "${operation.path}", method = RequestMethod.${operation.httpMethod}, produces = "application/json") ResponseEntity<${operation.responses['200'].javaType}> ${operation.methodName}( <#list operation.parameters as param> <#if config.useSwaggerAnnotations> @ApiParam(value = "${param.description!}") </#if> @${param.in?cap_first}Param("${param.name}") ${param.javaType} ${param.parameterName}<#if param_has_next>, </#if> </#list> ); </#list> } 11. Maven 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.openapicodegen</groupId> <artifactId>openapi-code-generator</artifactId> <version>1.0.0</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- OpenAPI Parser --> <dependency> <groupId>io.swagger.parser.v3</groupId> <artifactId>swagger-parser</artifactId> <version>2.1.12</version> </dependency> <!-- OpenAPI Models --> <dependency> <groupId>io.swagger.core.v3</groupId> <artifactId>swagger-models</artifactId> <version>2.2.8</version> </dependency> <!-- Template Engine --> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.32</version> </dependency> <!-- CLI Framework --> <dependency> <groupId>info.picocli</groupId> <artifactId>picocli</artifactId> <version>4.7.4</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> <!-- Testing --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>5.4.0</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <source>17</source> <target>17</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.4.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.openapicodegen.cli.CLIRunner</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
Usage Examples
// Programmatic usage public class ExampleUsage { public static void main(String[] args) { OpenAPICodeGenerator generator = new OpenAPICodeGenerator(); CodeGenConfig config = new CodeGenConfig.Builder() .packageName("com.example.api") .outputDir("generated-code") .groupId("com.example") .artifactId("api-client") .artifactVersion("1.0.0") .useBeanValidation(true) .useOptional(true) .build(); generator.generate("petstore.yaml", "java", config); } } // CLI Usage // java -jar openapi-codegen.jar petstore.yaml -l java -p com.example.api Features
✅ Supported Outputs
- Java client SDKs (RestTemplate, WebClient)
- TypeScript/JavaScript clients
- Python clients
- Spring Boot server stubs
- Node.js Express server stubs
- API documentation
✅ Code Quality
- Proper package structure
- Comprehensive model classes
- Full API interface definitions
- Builder patterns where appropriate
- Validation annotations
- Comprehensive tests
✅ Customization
- Extensible template system
- Custom type mappings
- Support for additional properties
- Plugin system for custom generators
This OpenAPI code generator provides a robust foundation for generating high-quality client and server code from OpenAPI specifications, with extensibility for custom requirements and multiple target languages.