swagger-api / swagger-api/swagger-core

[Bug]: Explicit @Schema(type = "number"|"integer"|"boolean") is emitted as "string" under OpenAPI 3.1

Open
#5,233 7 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

backlog Bug
Dominant language
Java
Stars
7.5k
Forks
2.3k
Avg merge
18h 1m
Merged PRs (30d)
10

Description

Description of the problem/issue

When generating an OpenAPI 3.1 schema, an explicit @Schema(type = "...") on a property is resolved incorrectly. The scalar type is set correctly on the Schema object, but the types set is populated with ["string"]. Since the 3.1 serializer reads the types set, the property is emitted as type: "string" regardless of the declared type.

  • Goal: produce a correct OpenAPI 3.1 document for DTOs that use explicit @Schema(type = ...).
  • Annotation misbehaving: @Schema — an explicit type = "number" | "integer" | "boolean" is emitted as "string".
  • Correct case: properties relying on type inference (no explicit type in @Schema) serialize correctly. Only an explicitly declared scalar type is affected.
  • Framework: reproduces on plain swagger-core (ModelConverters + Json31); also observed via springdoc-openapi with springdoc.api-docs.version=openapi_3_1.
  • Not examples-related: it reproduces on fields with no example, so it is distinct from #5061 / #5062 (numeric example serialization).
  • Downstream: the resulting spec misreports field types, which breaks OpenAPI-based type/client generation (numbers/booleans generated as string).

Under OpenAPI 3.0 (openapi31 = false) the same DTO serializes correctly, so this is specific to 3.1 generation.

Affected Version

2.2.47

Earliest version the bug appears in (if known):
Not fully bisected. Confirmed present in 2.2.47 and still present in 2.2.52 (latest release at time of filing).

Steps to Reproduce

  1. Add a DTO with explicit @Schema(type = ...) properties and resolve it in OpenAPI 3.1 mode:

    import io.swagger.v3.core.converter.AnnotatedType;
    import io.swagger.v3.core.converter.ModelConverters;
    import io.swagger.v3.core.converter.ResolvedSchema;
    import io.swagger.v3.core.util.Json31;
    import io.swagger.v3.oas.annotations.media.Schema;
    import java.math.BigDecimal;
    
    public class Repro {
      enum Freq { DAY, WEEK, MONTH }
    
      static class Dto {
        @Schema(title = "Inferred") public BigDecimal inferred;         // no explicit type (control)
        @Schema(title = "Amount", type = "number")  public BigDecimal amount;
        @Schema(title = "Count",  type = "integer") public Integer count;
        @Schema(title = "Flag",   type = "boolean") public Boolean flag;
        @Schema(title = "Unit")   public Freq unit;                     // enum (control)
      }
    
      public static void main(String[] args) {
        ModelConverters converters = new ModelConverters(true); // openapi31 = true
        ResolvedSchema resolved =
            converters.resolveAsResolvedSchema(new AnnotatedType(Dto.class));
    
        // root cause: scalar type is correct, but the "types" set is ["string"]
        resolved.schema.getProperties().forEach((name, s) ->
            System.out.println(name + " -> getType()=" + s.getType() + " getTypes()=" + s.getTypes()));
    
        System.out.println(Json31.pretty(resolved.schema));
      }
    }
    
  2. Resolve with new ModelConverters(true) (OpenAPI 3.1 mode) and serialize with Json31.

  3. Observe the serialized output and inspect the resolved Schema objects.

Expected Behavior

The declared type is preserved:

{
  "properties": {
    "inferred": { "type": "number" },
    "amount":   { "type": "number" },
    "count":    { "type": "integer" },
    "flag":     { "type": "boolean" },
    "unit":     { "type": "string", "enum": ["DAY", "WEEK", "MONTH"] }
  }
}

Actual Behavior

Every property with an explicit scalar type collapses to "string":

{
  "properties": {
    "inferred": { "type": "number" },                                  // OK (inferred, control)
    "amount":   { "type": "string" },                                  // WRONG, declared "number"
    "count":    { "type": "string" },                                  // WRONG, declared "integer"
    "flag":     { "type": "string" },                                  // WRONG, declared "boolean"
    "unit":     { "type": "string", "enum": ["DAY", "WEEK", "MONTH"] }  // OK (enum, control)
  }
}

inferred (inferred type) and unit (enum) serialize correctly — the defect is limited to an explicitly declared scalar type.

Inspecting the resolved (pre-serialization) Schema objects shows the root cause — the scalar type is right, the types set is wrong:

amount -> getType()="number"  getTypes()=["string"]
count  -> getType()="integer" getTypes()=["string"]
flag   -> getType()="boolean" getTypes()=["string"]

Logs / Stack Traces

No exception or log output — the schema is generated silently with the wrong type.

Additional Context

  • The same DTO serialized under OpenAPI 3.0 (new ModelConverters(false)) produces correct types, confirming this is a 3.1-generation regression.
  • Likely related to #4679 (@Schema type/implementation ignored under OpenAPI 3.1).
  • Distinct from #5061 / #5062, which concern numeric example serialization; this issue is about the property type and reproduces with no example present.
  • Current workaround: a post-processing pass that re-syncs each schema's types set to its scalar type produces a correct 3.1 document.

Checklist

  • I have searched the existing issues and this is not a duplicate.
  • I have provided sufficient information for maintainers to reproduce the issue.

Repro.java

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 with the attached Repro.java, then trace ModelConverters(true) into the resolved Schema objects and Json31 serialization. Compare getType() and getTypes() for explicit scalar @Schema declarations against inferred and enum controls. Done means OpenAPI 3.1 output preserves number, integer, and boolean, while OpenAPI 3.0 behavior remains correct.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, openapi
Domain
api
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.