OpenAPITools / OpenAPITools/openapi-generator

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

Open
#23,520 0 comments 0 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

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

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 at org.openapitools.codegen.OpenAPIGenerator and the SpringCodegen path that produces Pet#setNicknames, using the supplied schema to reproduce the generated annotation. Compare the generated setter with the requested x-setter-extra-annotation behavior; done means the custom annotation is retained for unique arrays without the conflicting default declaration. A linked pull request already proposes a fix.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring
Domain
tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.