Skip to content

[BUG][Java][Spring][SpringBoot] optional array with 'minItems' set, fails validation #22784

@kingofdisasterr

Description

@kingofdisasterr

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?
  • [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description

given an optioal array with 'minItems: 1', fails validation if this array isn't provided by the client.

All arrays defined in OpenAPI are, by default, initialized as empty lists in Java. If an array is required to have at least one entry when it is provided, validation will fail if the client omits the array.

openapi-generator version

v7.18.0, v7.19.0, master

OpenAPI declaration file content or url

OpenApi declaration:

openapi: 3.1.1
info:
    title: Optional Array OpenAPI Example
    version: 1.0.0
paths:
  /v1/new-customer:
    put:
      operationId: setNewCustomer
      requestBody:
        description: the new customer to be created
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewCustomerRequest'
      responses:
        204:
          description: The new customer was set successfully.

components:
  schemas:
    NewCustomerRequest:
      type: object
      properties:
        newCustomer:
          properties:
            name:
              type: string
          required:
            - name
        documents:
          description: |
            the documents to be attached to identify the new customer.
            This array is optional. If provided, it must contain between 1 and 3 items
          type: array
          items:
            $ref: '#/components/schemas/Document'
          minItems: 1
          maxItems: 3
      required:
        - newCustomer
    Document:
      type: object
      properties:
        documentId:
          description: |
            The document id (uuid)
          type: string
          format: uuid
        documentType:
          $ref: '#/components/schemas/DocumentType'
        documentNo:
          type: string
      required:
        - documentId
        - documentType
        - documentNo
    DocumentType:
      type: string
      enum:
        - ID_CARD
        - PASSPORT
Generation Details

java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate
-i optional_array-openapi.yaml
-g spring
-o tmp/spring-api

generates this model:

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 java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.openapitools.model.Document;
import org.openapitools.model.NewCustomerRequestNewCustomer;
import org.springframework.lang.Nullable;
import org.openapitools.jackson.nullable.JsonNullable;
import java.time.OffsetDateTime;
import javax.validation.Valid;
import javax.validation.constraints.*;
import io.swagger.v3.oas.annotations.media.Schema;


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

/**
 * NewCustomerRequest
 */

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2026-01-23T09:37:18.269659+01:00[Europe/Zurich]", comments = "Generator version: 7.20.0-SNAPSHOT")
public class NewCustomerRequest {

  private NewCustomerRequestNewCustomer newCustomer;

  @Valid
  private List<@Valid Document> documents = new ArrayList<>();

  public NewCustomerRequest() {
    super();
  }

  /**
   * Constructor with only required parameters
   */
  public NewCustomerRequest(NewCustomerRequestNewCustomer newCustomer) {
    this.newCustomer = newCustomer;
  }

  public NewCustomerRequest newCustomer(NewCustomerRequestNewCustomer newCustomer) {
    this.newCustomer = newCustomer;
    return this;
  }

  /**
   * Get newCustomer
   * @return newCustomer
   */
  @NotNull @Valid 
  @Schema(name = "newCustomer", requiredMode = Schema.RequiredMode.REQUIRED)
  @JsonProperty("newCustomer")
  public NewCustomerRequestNewCustomer getNewCustomer() {
    return newCustomer;
  }

  public void setNewCustomer(NewCustomerRequestNewCustomer newCustomer) {
    this.newCustomer = newCustomer;
  }

  public NewCustomerRequest documents(List<@Valid Document> documents) {
    this.documents = documents;
    return this;
  }

  public NewCustomerRequest addDocumentsItem(Document documentsItem) {
    if (this.documents == null) {
      this.documents = new ArrayList<>();
    }
    this.documents.add(documentsItem);
    return this;
  }

  /**
   * the documents to be attached to identify the new customer. This array is optional. If provided, it must contain between 1 and 3 items 
   * @return documents
   */
  @Valid @Size(min = 1, max = 3) 
  @Schema(name = "documents", description = "the documents to be attached to identify the new customer. This array is optional. If provided, it must contain between 1 and 3 items ", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
  @JsonProperty("documents")
  public List<@Valid Document> getDocuments() {
    return documents;
  }

  public void setDocuments(List<@Valid Document> documents) {
    this.documents = documents;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    NewCustomerRequest newCustomerRequest = (NewCustomerRequest) o;
    return Objects.equals(this.newCustomer, newCustomerRequest.newCustomer) &&
        Objects.equals(this.documents, newCustomerRequest.documents);
  }

  @Override
  public int hashCode() {
    return Objects.hash(newCustomer, documents);
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("class NewCustomerRequest {\n");
    sb.append("    newCustomer: ").append(toIndentedString(newCustomer)).append("\n");
    sb.append("    documents: ").append(toIndentedString(documents)).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) {
    if (o == null) {
      return "null";
    }
    return o.toString().replace("\n", "\n    ");
  }
}
Steps to reproduce

given the declaration above, generate the java code and execute this test:

package org.openapitools.model;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;

public class NewCustomerRequestTest {

	private final Validator validator;

	public NewCustomerRequestTest() {
		try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) {
			this.validator = factory.getValidator();
		}
	}

    @Test
    void testNewCustomerReadFromJsonAndValidate()
            throws Exception {
        final String newCustomerJsonString = "{\"newCustomer\":{\"name\":\"test customer name\"}}";
        final NewCustomerRequest newCustomer = new ObjectMapper().readValue(newCustomerJsonString, NewCustomerRequest.class);
        final Set<ConstraintViolation<NewCustomerRequest>> newCustomerValidations = validator.validate(newCustomer);
        assertThat(newCustomerValidations).isEmpty();
    }

}
Related issues/PRs

#18735
#21269

Suggest a fix

The generator must check whether an array has 'minItems' defined and whether it is optional. If so, the array must not be initialized by default in Java.

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