Skip to content

[BUG] [JAVA] [SPRING] Missing declaration of x-setter-extra-annotation at codegen for unique arrays #23520

@drewlakee

Description

@drewlakee

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator?
  • Have you tested with the latest master to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What's the actual output vs expected output?
Description

Hi,

I tried to plug in a custom jackson.JsonDeserialize for a property with type: array and uniqueItems: true in a component schema, but it didn't work as I expected. A few moments later with a debugger I discovered that there's no way at the moment to achieve a custom deserialization in Java for Set collection at codegen, and the reason is that jackson has a JsonDeserialize priority of x-setter-extra-annotation over x-field-extra-annotation.

My custom JsonDeserialize:

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import org.apache.commons.lang3.StringUtils;

public class TrimmingStringSetDeserializer extends StdDeserializer<Set<String>> {

    protected TrimmingStringSetDeserializer() {
        super(Set.class);
    }

    @Override
    public Set<String> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        if (p.getCurrentToken() == JsonToken.START_ARRAY) {
            Set<String> values = new HashSet<>();
            while (p.nextToken() != JsonToken.END_ARRAY) {
                String value = StringUtils.trimToNull(p.getValueAsString());
                if (value != null) {
                    values.add(value);
                }
            }

            return values;
        }
        ctxt.reportWrongTokenException(Set.class, JsonToken.START_ARRAY, "Expected string set");
        throw new RuntimeException("reportWrongTokenException");
    }
}

The way I was expected it to work:

      properties:
        nicknames:
          type: array
          uniqueItems: true
          # x-field-extra-annotation: '@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = org.openapitools.tools.TrimmingStringSetDeserializer.class)'
          x-setter-extra-annotation: '@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = org.openapitools.tools.TrimmingStringSetDeserializer.class)'
          items:
            type: string

The way java codegen works at the moment:

  // always sets as a default
  @JsonDeserialize(as = LinkedHashSet.class)
  @JsonProperty("nicknames")
  public void setNicknames(Set<String> nicknames) {
    this.nicknames = nicknames;
  }

If x-field-extra-annotation is custom, it's always ignored, and for x-setter-extra-annotation it's simply not working because of the default codegen.

openapi-generator version

latest

OpenAPI declaration file content or url
openapi: 3.0.0
info:
  title: API
  version: 1.0.0
paths:
  /pet:
    post:
      summary: Add a new pet to the store
      operationId: addPet
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Pet'
      responses:
        '200':
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
components:
  schemas:
    Pet:
      title: a Pet
      description: A pet for sale in the pet store
      properties:
        nicknames:
          type: array
          uniqueItems: true
          # x-field-extra-annotation: '@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = org.openapitools.tools.TrimmingStringSetDeserializer.class)'
          x-setter-extra-annotation: '@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = org.openapitools.tools.TrimmingStringSetDeserializer.class)'
          items:
            type: string
Generation Details
package org.openapitools.model;

import java.net.URI;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.lang.Nullable;
import org.openapitools.jackson.nullable.JsonNullable;
import java.time.OffsetDateTime;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import io.swagger.v3.oas.annotations.media.Schema;


import java.util.*;
import jakarta.annotation.Generated;

/**
 * A pet for sale in the pet store
 */

@Schema(name = "Pet", description = "A pet for sale in the pet store")
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2026-04-13T10:23:49.058908+03:00[Europe/Moscow]", comments = "Generator version: unset")
public class Pet {

  @Valid
  private Set<String> nicknames = new LinkedHashSet<>();

  public Pet nicknames(Set<String> nicknames) {
    this.nicknames = nicknames;
    return this;
  }

  public Pet addNicknamesItem(String nicknamesItem) {
    if (this.nicknames == null) {
      this.nicknames = new LinkedHashSet<>();
    }
    this.nicknames.add(nicknamesItem);
    return this;
  }

  /**
   * Get nicknames
   * @return nicknames
   */
  
  @Schema(name = "nicknames", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
  @JsonProperty("nicknames")
  public Set<String> getNicknames() {
    return nicknames;
  }

  @JsonDeserialize(as = LinkedHashSet.class)
  @JsonProperty("nicknames")
  public void setNicknames(Set<String> nicknames) {
    this.nicknames = nicknames;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Pet pet = (Pet) o;
    return Objects.equals(this.nicknames, pet.nicknames);
  }

  @Override
  public int hashCode() {
    return Objects.hash(nicknames);
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("class Pet {\n");
    sb.append("    nicknames: ").append(toIndentedString(nicknames)).append("\n");
    sb.append("}");
    return sb.toString();
  }

  /**
   * Convert the given object to string with each line indented by 4 spaces
   * (except the first line).
   */
  private String toIndentedString(@Nullable Object o) {
    return o == null ? "null" : o.toString().replace("\n", "\n    ");
  }
}
Steps to reproduce
  1. Clone the repository
  2. Set up locally openapi schema as I mentioned above
  3. Pass to org.openapitools.codegen.OpenAPIGenerator arguments generate -g spring -i "/Users/<user>/Library/Application Support/JetBrains/IntelliJIdea2025.3/scratches/scratch_22.yml" -o ./out/generated-java-openapi
  4. Look at org.openapitools.model.Pet#setNicknames
Related issues/PRs

Didn't find an appropriate one.

Suggest a fix

[Java] [Spring] Support a custom declaration of x-setter-extra-annotation at codegen for unique arrays#23522

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions