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.