Skip to content

Commit fd351f8

Browse files
committed
fix(java): place bean-validation @Valid on the type argument instead of the container (HV000271)
Hibernate Validator 9.1+ (bundled with Spring Boot 4) logs HV000271 ("Using @Valid on a container is deprecated. You should apply the annotation on the type argument(s)") for every @Valid-annotated List/Set/Map — on both model properties and API parameters. This relocates @Valid from the container to its type argument — the Bean-Validation-2.0 form (List<@Valid T>, Map<String, @Valid V>) — which cascades identically but is not deprecated. Backward-compatible: element validation is preserved, never dropped; single-object @Valid is untouched. Verified on Spring Boot 3.3 (Spring 6.1) and 4.1 (Spring 7): a container parameter carrying only the type-argument @Valid still triggers element validation via method validation, with no HV000271. Scope (Java family): spring, java client, JAX-RS (jersey, resteasy(+eap), cxf(+extended/cdi), spec), java-camel, java-msf4j. Models (property/getter): the container @Valid is moved to the type argument. Map values are gated behind a new AbstractJavaCodegen#useBeanValidationOnMapValueType() (default false; overridden true in Spring/JavaClient/JAX-RS) so untouched generators don't silently gain map-value validation. Arrays/sets already injected the type-argument form. Parameters (@RequestBody/@RequestParam/@RequestPart): the redundant container-level @Valid is dropped for container parameters; element validation is driven by the type-argument @Valid. Single-object parameters keep @Valid. Reactive Mono/Flux bodies are intentionally untouched (not Jakarta containers, so no HV000271). Regenerated all affected samples and added/updated codegen tests in SpringCodegenTest, JavaClientCodegenTest, JavaJAXRSSpecServerCodegenTest and JavaValidationArrayPrimitivesTest.
1 parent 0f12a09 commit fd351f8

581 files changed

Lines changed: 1388 additions & 1354 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,7 +1097,7 @@ public String getTypeDeclaration(Schema p) {
10971097
Schema<?> target = ModelUtils.isGenerateAliasAsModel() ? p : schema;
10981098
if (ModelUtils.isArraySchema(target)) {
10991099
Schema<?> items = getSchemaItems(schema);
1100-
String typeDeclaration = getTypeDeclarationForArray(items);
1100+
String typeDeclaration = getTypeDeclarationWithBeanValidation(items);
11011101
return getSchemaType(target) + "<" + typeDeclaration + ">";
11021102
} else if (ModelUtils.isMapSchema(target)) {
11031103
// Note: ModelUtils.isMapSchema(p) returns true when p is a composed schema that also defines
@@ -1108,12 +1108,43 @@ public String getTypeDeclaration(Schema p) {
11081108
inner = new StringSchema().description("TODO default missing map inner type to string");
11091109
p.setAdditionalProperties(inner);
11101110
}
1111-
return getSchemaType(target) + "<String, " + getTypeDeclaration(inner) + ">";
1111+
// Unlike arrays/sets, map values never received a type-argument bean
1112+
// validation before, so this is gated: only generators that have dropped the
1113+
// deprecated container-level @Valid (HV000271) opt in, to avoid silently adding
1114+
// new validation to generators that still cascade via the container.
1115+
String valueDeclaration = useBeanValidationOnMapValueType()
1116+
? getTypeDeclarationWithBeanValidation(inner)
1117+
: getTypeDeclaration(inner);
1118+
return getSchemaType(target) + "<String, " + valueDeclaration + ">";
11121119
}
11131120
return super.getTypeDeclaration(target);
11141121
}
11151122

1116-
private String getTypeDeclarationForArray(Schema<?> items) {
1123+
/**
1124+
* Whether bean validation of map values is expressed on the value type argument
1125+
* ({@code Map<String, @Valid V>}) instead of on the map itself. Generators that have migrated
1126+
* off the deprecated container-level {@code @Valid} (Hibernate Validator HV000271)
1127+
* override this to return {@code true}. Arrays/sets always place bean validation on the
1128+
* type argument, so they are not gated by this method.
1129+
*
1130+
* @return {@code true} to emit map-value bean validation on the type argument;
1131+
* {@code false} by default
1132+
*/
1133+
protected boolean useBeanValidationOnMapValueType() {
1134+
return false;
1135+
}
1136+
1137+
/**
1138+
* Renders the type declaration of a container element (array/set item or map value) with its
1139+
* bean validation applied to the type argument, e.g. {@code @Valid Pet} or
1140+
* {@code @Size(max = 3) String}. Hibernate Validator 9.1+ expects cascade/constraints on the
1141+
* type argument; a container-level {@code @Valid} is deprecated and logs HV000271, so the
1142+
* annotation is placed here instead of on the container.
1143+
*
1144+
* @param items the array/set item or map value schema
1145+
* @return the element type declaration prefixed with its bean validation (no prefix if none)
1146+
*/
1147+
private String getTypeDeclarationWithBeanValidation(Schema<?> items) {
11171148
String typeDeclaration = getTypeDeclaration(items);
11181149

11191150
String beanValidation = getBeanValidation(items);
@@ -1137,11 +1168,12 @@ private String getTypeDeclarationForArray(Schema<?> items) {
11371168
}
11381169

11391170
/**
1140-
* This method stand for resolve bean validation for container(array, set).
1171+
* This method stand for resolve bean validation for a container element
1172+
* (array/set item or map value).
11411173
* Return empty if there's no bean validation for requested type or prop useBeanValidation false or missed.
11421174
*
11431175
* @param items type
1144-
* @return BeanValidation for declared type in container(array, set)
1176+
* @return BeanValidation for declared element type of a container (array, set, map value)
11451177
*/
11461178
private String getBeanValidation(Schema<?> items) {
11471179
if (!isUseBeanValidation()) {

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaJAXRSServerCodegen.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,14 @@ public CodegenType getTag() {
132132
return CodegenType.SERVER;
133133
}
134134

135+
@Override
136+
protected boolean useBeanValidationOnMapValueType() {
137+
// The JAX-RS templates place container element validation on the type argument
138+
// (List<@Valid T>, Map<String, @Valid V>) rather than the deprecated
139+
// container-level @Valid (HV000271).
140+
return true;
141+
}
142+
135143
@Override
136144
public void processOpts() {
137145
super.processOpts();

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaClientCodegen.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,14 @@ public DocumentationProvider defaultDocumentationProvider() {
201201
return DocumentationProvider.SOURCE;
202202
}
203203

204+
@Override
205+
protected boolean useBeanValidationOnMapValueType() {
206+
// The Java templates place container element validation on the type argument
207+
// (List<@Valid T>, Map<String, @Valid V>) rather than the deprecated
208+
// container-level @Valid (HV000271).
209+
return true;
210+
}
211+
204212
@Override
205213
public List<DocumentationProvider> supportedDocumentationProvider() {
206214
List<DocumentationProvider> documentationProviders = new ArrayList<>();

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,6 +1555,14 @@ public List<VendorExtension> getSupportedVendorExtensions() {
15551555
return extensions;
15561556
}
15571557

1558+
@Override
1559+
protected boolean useBeanValidationOnMapValueType() {
1560+
// The Spring templates place container element validation on the type argument
1561+
// (List<@Valid T>, Map<String, @Valid V>) rather than the deprecated
1562+
// container-level @Valid (HV000271).
1563+
return true;
1564+
}
1565+
15581566
protected boolean isSpringCodegen() {
15591567
return getName().contains("spring");
15601568
}

modules/openapi-generator/src/main/resources/Java/beanValidation.mustache

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,6 @@
33
@NotNull
44
{{/isReadOnly}}
55
{{/required}}
6-
{{#isContainer}}
7-
{{^isPrimitiveType}}
8-
{{^isEnum}}
9-
@Valid
10-
{{/isEnum}}
11-
{{/isPrimitiveType}}
12-
{{/isContainer}}
136
{{^isContainer}}
147
{{^isPrimitiveType}}
158
@Valid
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{{#isBodyParam}}{{#useBeanValidation}}@Valid {{/useBeanValidation}}{{{dataType}}} {{paramName}}{{/isBodyParam}}
1+
{{#isBodyParam}}{{#useBeanValidation}}{{^isContainer}}@Valid {{/isContainer}}{{/useBeanValidation}}{{{dataType}}} {{paramName}}{{/isBodyParam}}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{{#required}}{{^isReadOnly}}@NotNull {{/isReadOnly}}{{/required}}{{#isContainer}}{{^items.isPrimitiveType}}{{^items.isDate}}{{^items.isDateTime}}{{^items.isString}}{{^items.isFile}}{{^items.isEnumOrRef}}@Valid {{/items.isEnumOrRef}}{{/items.isFile}}{{/items.isString}}{{/items.isDateTime}}{{/items.isDate}}{{/items.isPrimitiveType}}{{/isContainer}}{{^isContainer}}{{^isPrimitiveType}}{{^isDate}}{{^isDateTime}}{{^isString}}{{^isFile}}{{^isEnumOrRef}}@Valid {{/isEnumOrRef}}{{/isFile}}{{/isString}}{{/isDateTime}}{{/isDate}}{{/isPrimitiveType}}{{/isContainer}}{{>beanValidationCore}}
1+
{{#required}}{{^isReadOnly}}@NotNull {{/isReadOnly}}{{/required}}{{^isContainer}}{{^isPrimitiveType}}{{^isDate}}{{^isDateTime}}{{^isString}}{{^isFile}}{{^isEnumOrRef}}@Valid {{/isEnumOrRef}}{{/isFile}}{{/isString}}{{/isDateTime}}{{/isDate}}{{/isPrimitiveType}}{{/isContainer}}{{>beanValidationCore}}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{{#isBodyParam}}{{#useBeanValidation}}@Valid {{/useBeanValidation}}{{{dataType}}} {{paramName}}{{/isBodyParam}}
1+
{{#isBodyParam}}{{#useBeanValidation}}{{^isContainer}}@Valid {{/isContainer}}{{/useBeanValidation}}{{{dataType}}} {{paramName}}{{/isBodyParam}}

modules/openapi-generator/src/main/resources/JavaJaxRS/cxf-ext/pojo.mustache

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}}{{#vendorExtensi
4040
{{#withXml}}
4141
@XmlElement(name="{{baseName}}"{{#required}}, required = {{required}}{{/required}})
4242
{{/withXml}}
43-
@ApiModelProperty({{#example}}example = "{{{.}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}value = "{{{description}}}"){{^isPrimitiveType}}{{^isDate}}{{^isDateTime}}{{^isString}}{{^isFile}}{{#useBeanValidation}}
44-
@Valid{{/useBeanValidation}}{{/isFile}}{{/isString}}{{/isDateTime}}{{/isDate}}{{/isPrimitiveType}}
43+
@ApiModelProperty({{#example}}example = "{{{.}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}value = "{{{description}}}"){{^isContainer}}{{^isPrimitiveType}}{{^isDate}}{{^isDateTime}}{{^isString}}{{^isFile}}{{#useBeanValidation}}
44+
@Valid{{/useBeanValidation}}{{/isFile}}{{/isString}}{{/isDateTime}}{{/isDate}}{{/isPrimitiveType}}{{/isContainer}}
4545
{{#isDate}}
4646
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
4747
{{/isDate}}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{{#isBodyParam}}{{#useBeanValidation}}@Valid {{#required}}{{^isNullable}}@NotNull {{/isNullable}}{{/required}}{{/useBeanValidation}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection<InputStream>{{/collectionFormat}}{{^collectionFormat}}InputStream{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{{dataType}}}{{/isFile}} {{paramName}}{{/isBodyParam}}
1+
{{#isBodyParam}}{{#useBeanValidation}}{{^isContainer}}@Valid {{/isContainer}}{{#required}}{{^isNullable}}@NotNull {{/isNullable}}{{/required}}{{/useBeanValidation}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection<InputStream>{{/collectionFormat}}{{^collectionFormat}}InputStream{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{{dataType}}}{{/isFile}} {{paramName}}{{/isBodyParam}}

0 commit comments

Comments
 (0)