OpenAPI Generator: Geração de Código Contract-First com o Plugin Maven
Objective
O plugin Maven do OpenAPI Generator transforma uma especificação OpenAPI (YAML/JSON) em código-fonte Java — interfaces de controller, DTOs e stubs de client — durante o build do Maven, para que o contrato seja a única fonte de verdade em vez de DTOs escritos à mão que vão se desalinhando da API documentada.
Use Cases
- Desenvolvimento API-first: a especificação OpenAPI é escrita (ou desenhada colaborativamente) antes de qualquer código Java, e tanto o formato do controller quanto os modelos de request/response são gerados a partir dela.
- Manter as classes de request/response de uma API pública perfeitamente sincronizadas com sua documentação publicada — os DTOs são a especificação, não um espelho manual dela.
- Gerar um client Java tipado para uma API de terceiros ou interna a partir do seu documento OpenAPI publicado, em vez de escrever chamadas
RestTemplate/WebCliente DTOs na mão. - Times grandes onde backend e frontend/consumidores concordam num contrato primeiro, e depois geram seus respectivos códigos de client/server independentemente a partir do mesmo arquivo.
Deep Dive
Ligando o plugin ao build
O plugin se liga por padrão à fase generate-sources do Maven, para que o código-fonte gerado exista antes de o compile rodar:
plaintext<plugin> <groupId>org.openapitools</groupId> <artifactId>openapi-generator-maven-plugin</artifactId> <version>7.24.0</version> <executions> <execution> <goals><goal>generate</goal></goals> <configuration> <inputSpec>${project.basedir}/src/main/resources/api.yaml</inputSpec> <generatorName>spring</generatorName> <configOptions> <sourceFolder>src/gen/java/main</sourceFolder> <interfaceOnly>true</interfaceOnly> </configOptions> </configuration> </execution> </executions> </plugin>
Escolhendo o sabor Spring: library
generatorName=spring gera "uma aplicação server Spring Boot em Java usando a integração com SpringDoc", mas três valores de library mudam a cara desse código de servidor:
spring-boot(default) — um servidor Spring Boot padrão: classes anotadas com@RestControllergeradas (ou interfaces, veja abaixo).spring-cloud— um client Feign com auto-configuração do Spring Cloud, para chamar a API documentada de outro serviço.spring-http-interface— as interfaces HTTP declarativas do Spring 6 (requer Spring Boot 3+), uma alternativa mais leve a um client Feign completo.
interfaceOnly e o padrão delegate
Por padrão o gerador produz uma implementação completa de controller. Definir interfaceOnly=true gera só a interface da API (por exemplo PetApi, com métodos abstratos correspondendo a cada operação) e deixa a implementação por sua conta:
java// gerado: PetApi.java (interface, apenas assinaturas de método)
public interface PetApi {
@Operation(summary = "Find pet by ID")
@GetMapping("/pet/{petId}")
ResponseEntity<Pet> getPetById(@PathVariable Long petId);
}java// escrito à mão: implementa o contrato gerado no seu próprio bean
@RestController
public class PetController implements PetApi {
@Override
public ResponseEntity<Pet> getPetById(Long petId) {
return ResponseEntity.ok(petService.find(petId));
}
}Esse é o padrão que mantém a lógica de negócio escrita à mão totalmente fora do código gerado — regenerar a partir da especificação nunca corre o risco de sobrescrever um método de controller que você editou manualmente. delegatePattern=true vai um passo além, gerando uma interface delegate intermediária entre o controller e seu service, útil quando várias implementações da mesma API precisam ser trocadas entre si.
Ajustes de naming e versão
useTags=truederiva os nomes de classe/método gerados a partir das tags do OpenAPI em vez dooperationId— importa para como as interfaces geradas são organizadas quando uma especificação agrupa operações por tag.useSpringBoot3=true(o default atual) gera importsjakarta.*em vez dejavax.*;useSpringBoot4existe para o Spring Boot 4.x.dateLibrary=java8(default) mapeiadate/date-timedo OpenAPI parajava.time.LocalDate/OffsetDateTime(JSR-310) em vez de Joda-Time ou o antigojava.util.Date.
Trade-offs
- Código gerado é o contrato, não uma sugestão — editar um
PetApi.javagerado diretamente é uma armadilha: o próximomvn generate-sourcessobrescreve silenciosamente.interfaceOnly=truemais implementar a interface você mesmo é a única forma de adicionar lógica que sobrevive à regeneração. - Anotações Swagger v2 e v3 não são binariamente compatíveis — misturar uma dependência
swagger-annotations(v2) com código gerado esperandoswagger-core/swagger-parser(v3) falha em runtime ou em tempo de compilação dependendo do descompasso; fixe uma versão major só, para o projeto inteiro. - Um client gerado acopla seu código à versão da especificação de outra pessoa — regenerar contra um documento OpenAPI upstream mais novo pode mudar silenciosamente assinaturas de método (um campo vira obrigatório, um enum ganha um valor), o que é uma quebra de compilação, não uma surpresa em runtime — em tese esse é o ponto, mas significa que o CI precisa regenerar e rebuildar a cada mudança de especificação upstream para pegar isso cedo.
library=spring-cloudtraz o Feign e todo o maquinário de auto-configuração dele — conveniente se o projeto já usa Spring Cloud, mas exagero para uma única chamada de client num projeto que não tem mais nada a ver com Spring Cloud;spring-http-interfaceé a escolha mais leve no Spring Boot 3+.