OpenAPITools / OpenAPITools/openapi-generator
[BUG][Java][Spring][SpringBoot] optional array with 'minItems' set, fails validation
Nobody has claimed this yet.
- 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?
- [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
https://github.com/OpenAPITools/openapi-generator/issues/18735
https://github.com/OpenAPITools/openapi-generator/pull/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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running the documented generation command with optional_array-openapi.yaml and inspect the generated NewCustomerRequest model, especially its optional documents field. Use NewCustomerRequestTest as the reproduction; done means omitting documents produces no validation violations while minItems still applies when the array is provided.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, spring, spring-boot
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100