OpenAPITools / OpenAPITools/openapi-generator

[BUG][SPRING] oneOf type: Jackson trying to instantiate interface instead of implementation

Open
#15,082 16 comments 11 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Issue: Bug
Dominant language
Java
Stars
26.8k
Forks
7.7k
PR merge metrics
PR metrics pending

Description

Description

I'm using the oneOf feature to define several possible schemas that can go into a response body of my service. In the generated Java client code, the Java implementations of these schemas implement an interface, but when I send a request through, Jackson is trying to create an instance of the interface, instead of the concrete class.

The same issue was reported and fixed in swagger-codegen project: https://github.com/swagger-api/swagger-codegen/issues/10011

openapi-generator version

6.4.0

OpenAPI declaration file content or url
openapi: 3.0.1
info:
  version: '2.0'
  title: ABC API documentation
  ...
  responses:
    ...
    ...
    CancellationDuration:
      type: object
      oneOf:
        - $ref: '#/components/schemas/DeadlineAbsolute'
        - $ref: '#/components/schemas/DeadlineDurationBeforeArrival'
Generation Details

openapi-generator-maven-plugin is used, full configuration:

      <plugin>
        <groupId>org.openapitools</groupId>
        <artifactId>openapi-generator-maven-plugin</artifactId>
        <version>6.4.0</version>
        <configuration>
          <generateSupportingFiles>true</generateSupportingFiles>
          <generatorName>spring</generatorName>
          <generateApiDocumentation>true</generateApiDocumentation>
          <generateModelDocumentation>true</generateModelDocumentation>
          <configOptions>
            <oas3>true</oas3>
            <useTags>true</useTags>
            <withSeparateModelsAndApi>true</withSeparateModelsAndApi>
            <delegatePattern>true</delegatePattern>
            <withInterfaces>true</withInterfaces>
            <library>spring-boot</library>
            <dateLibrary>java8</dateLibrary>
            <useSpringfox>false</useSpringfox>
            <useSpringController>true</useSpringController>
            <modelPropertyNaming>camelCase</modelPropertyNaming>
          </configOptions>
          <!-- Option to prevent openapi generator to strip off common prefix from enum values -->
          <additionalProperties>removeEnumValuePrefix=false</additionalProperties>
          <!-- <skipIfSpecIsUnchanged>true</skipIfSpecIsUnchanged>-->
        </configuration>
        <executions>
          <execution>
            <id>spring-execution</id>
            <goals>
              <goal>generate</goal>
            </goals>
            <configuration>
              <ignoreFileOverride>${project.basedir}/.openapi-generator-ignore</ignoreFileOverride>
              <inputSpec>${project.basedir}/src/main/resources/yaml/apis/api/Api.yaml</inputSpec>
              <generatorName>spring</generatorName>
              <apiPackage>com.mycompany.obt.master.apis</apiPackage>
              <modelPackage>com.mycompany.obt.master.models</modelPackage>
            </configuration>
          </execution>
          <execution>
            <id>typescript-execution</id>
            <goals>
              <goal>generate</goal>
            </goals>
            <configuration>
              <inputSpec>${project.basedir}/src/main/resources/yaml/apis/api/Api.yaml</inputSpec>
              <generatorName>typescript-axios</generatorName>
              <apiPackage>com.mycompany.obt.master.apis</apiPackage>
              <modelPackage>com.mycompany.obt.master.models</modelPackage>
            </configuration>
          </execution>
        </executions>
      </plugin>

This results in an empty interface

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-03-31T09:08:52.189665+05:30[Asia/Kolkata]")
public interface CancellationDuration {}

And two concrete classes:

@Schema(name = "DeadlineAbsolute", description = "Absolute Date and time before which cancellation policy is applicable")
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-03-31T09:13:38.604952+05:30[Asia/Kolkata]")
public class DeadlineAbsolute implements CancellationDuration { ... }


@Schema(name = "DeadlineDurationBeforeArrival", description = "Time before arrival when cancellation policy is applicable")
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-03-31T09:13:38.604952+05:30[Asia/Kolkata]")
public class DeadlineDurationBeforeArrival implements CancellationDuration { ... }

This error occurs on deserialization:

Caused by: org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class com.mycompany.obt.master.models.CancellationDuration]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.mycompany.obt.master.models.CancellationDuration` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1762, column: 32]
	at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:388)
	at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:343)
	at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:185)
	at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:160)
	at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:133)
	at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)
	at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:179)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the generated Spring models from the provided OpenAPI declaration and Maven plugin configuration, focusing on CancellationDuration, DeadlineAbsolute, and DeadlineDurationBeforeArrival. Then trace how the Spring generator represents oneOf during Jackson deserialization; done means a response containing either schema deserializes to the appropriate concrete class instead of the interface.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, openapi, spring
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
32/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.