OpenAPI
oneOf
openapi-generator-maven-plugin
Spring code generation
API documentation

How to use OpenAPI oneOf property with openapi-generator-maven-plugin when generating Spring code

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

oneOf is the OpenAPI tool for describing payloads that may take one of several distinct shapes. When you generate Spring code from such a schema, the generator can only do a good job if the models are clearly separated and the API description tells the deserializer how to choose the correct subtype.

Model oneOf as explicit component schemas

The most reliable approach is to define each variant as its own named schema under components/schemas, then reference those schemas from a parent object with oneOf. In practice, this is much easier for openapi-generator to handle than embedding anonymous schemas inline.

The key point is that the payload must validate against exactly one schema. If two variants overlap too much, generated code becomes awkward and runtime deserialization may fail.

yaml
1openapi: 3.0.3
2info:
3  title: Payment API
4  version: 1.0.0
5paths:
6  /payments:
7    post:
8      operationId: createPayment
9      requestBody:
10        required: true
11        content:
12          application/json:
13            schema:
14              $ref: '#/components/schemas/PaymentMethod'
15      responses:
16        '200':
17          description: Accepted
18components:
19  schemas:
20    PaymentMethod:
21      oneOf:
22        - $ref: '#/components/schemas/CardPayment'
23        - $ref: '#/components/schemas/BankTransfer'
24      discriminator:
25        propertyName: type
26        mapping:
27          card: '#/components/schemas/CardPayment'
28          bank_transfer: '#/components/schemas/BankTransfer'
29    CardPayment:
30      type: object
31      required:
32        - type
33        - cardNumber
34      properties:
35        type:
36          type: string
37          enum: [card]
38        cardNumber:
39          type: string
40        holderName:
41          type: string
42    BankTransfer:
43      type: object
44      required:
45        - type
46        - iban
47      properties:
48        type:
49          type: string
50          enum: [bank_transfer]
51        iban:
52          type: string
53        accountName:
54          type: string

In this example, the type field acts as a discriminator. That gives the generator and Jackson a concrete hint about which Java type should be used.

Configure the Maven plugin for Spring generation

The Maven plugin only reads what is in the OpenAPI document, so the spec matters more than plugin magic. Still, the generator needs a sensible Spring configuration so it emits interfaces, models, and Jackson annotations that fit a typical Spring Boot application.

xml
1<plugin>
2  <groupId>org.openapitools</groupId>
3  <artifactId>openapi-generator-maven-plugin</artifactId>
4  <version>7.8.0</version>
5  <executions>
6    <execution>
7      <goals>
8        <goal>generate</goal>
9      </goals>
10      <configuration>
11        <inputSpec>${project.basedir}/src/main/resources/openapi.yaml</inputSpec>
12        <generatorName>spring</generatorName>
13        <apiPackage>com.example.api</apiPackage>
14        <modelPackage>com.example.model</modelPackage>
15        <configOptions>
16          <interfaceOnly>true</interfaceOnly>
17          <useSpringBoot3>true</useSpringBoot3>
18          <useJakartaEe>true</useJakartaEe>
19          <dateLibrary>java8</dateLibrary>
20        </configOptions>
21      </configuration>
22    </execution>
23  </executions>
24</plugin>

You do not normally need a special flag just to "turn on" oneOf. The feature comes from the schema. If the output is poor, the root cause is usually an ambiguous OpenAPI definition rather than a missing plugin option.

What the generated Spring models look like

With a discriminator-based oneOf, the generated code usually creates a parent model or interface and subtype models for each branch. Jackson annotations are then used to deserialize incoming JSON into the correct subtype.

For example, a controller implementation might receive a generated base type and branch safely:

java
1@RestController
2class PaymentsController implements PaymentsApi {
3
4    @Override
5    public ResponseEntity<Void> createPayment(PaymentMethod paymentMethod) {
6        if (paymentMethod instanceof CardPayment card) {
7            System.out.println("Charging card " + card.getCardNumber());
8        } else if (paymentMethod instanceof BankTransfer transfer) {
9            System.out.println("Processing transfer " + transfer.getIban());
10        } else {
11            throw new IllegalArgumentException("Unsupported payment type");
12        }
13
14        return ResponseEntity.ok().build();
15    }
16}

That works cleanly because the variants are structurally distinct and the JSON contains a discriminator value. Without that discriminator, the generated deserializer may have to guess based on fields, which is brittle.

Test the contract, not just the generated code

After generation, test the serialized JSON that your API accepts and returns. A valid schema can still behave badly at runtime if two branches are hard to distinguish or if your Spring configuration overrides Jackson defaults.

An integration test keeps the contract honest:

java
1@Test
2void deserializesCardPayment() throws Exception {
3    ObjectMapper mapper = new ObjectMapper();
4
5    String json = """
6        {
7          "type": "card",
8          "cardNumber": "4111111111111111",
9          "holderName": "Ada Lovelace"
10        }
11        """;
12
13    PaymentMethod method = mapper.readValue(json, PaymentMethod.class);
14
15    assertTrue(method instanceof CardPayment);
16}

This kind of test is especially useful after changing the spec or upgrading the generator version.

Common Pitfalls

The most common mistake is using oneOf for schemas that are not truly exclusive. If one payload can satisfy two branches, validation and deserialization become unpredictable. Make the variants distinct by required fields or a discriminator.

Another issue is defining branch schemas inline instead of as named components. Code generation is usually clearer when each subtype has its own reusable schema.

Teams also forget that generator output depends on runtime libraries. If Jackson cannot resolve polymorphic types in your application, inspect the generated annotations and verify that your JSON actually includes the discriminator property.

Finally, do not assume every generator version behaves identically. If you upgrade the plugin, regenerate code and rerun contract tests before merging.

Summary

  • Use oneOf only when the payload must match exactly one schema.
  • Prefer named component schemas instead of inline anonymous branches.
  • Add a discriminator property so generated Spring models deserialize predictably.
  • Treat the OpenAPI document as the primary fix when generated code looks wrong.
  • Verify generated polymorphic models with integration tests against real JSON.

Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design