The Problem

I was working on our damage reporting API, specifically the POST /v1/damage-report endpoint, which creates a new damage report. The DamageReportDTO is a Java record with fields like damageType, employeeCausatorId, and others, validated using Jakarta Validation. Most fields are annotated with @NotNull(groups = OnFinalized.class), meaning they’re only required when submitting or approving a report, not at creation. Only employeeCausatorId has a plain @NotNull, so it should be the only mandatory field for the POST endpoint.

However, when I checked the Swagger UI (/swagger-ui.html), every field in the DamageReportDTO schema had a little star next to it, indicating they were all required. This was misleading—clients would think they need to send everything upfront, which isn’t true. The controller for POST /v1/damage-report doesn’t use @Validated, so those OnFinalized constraints shouldn’t apply here. Something was off with how Swagger was rendering the schema.

<aside> 💡

schema.requiredMode is a newer way of handling required properties within OpenAPI specifications, particularly in OpenAPI 3.0 and later. It replaces the older, simpler required boolean.

</aside>

Initial Code

Here’s what the DamageReportDTO looked like initially:

package com.sensey.mdf.damage_report.dto;

import com.sensey.mdf.damage_report.enums.DamageType;
import com.sensey.mdf.damage_report.validation.OnFinalized;
import jakarta.validation.constraints.NotNull;
import java.util.UUID;

public record DamageReportDTO(
    UUID id,
    @NotNull(groups = OnFinalized.class, message = "Damage type is required when finalizing")
    DamageType damageType,
    @NotNull(message = "Employee causator ID is required")
    UUID employeeCausatorId,
    @NotNull(groups = OnFinalized.class, message = "Employee reporter ID is required when finalizing")
    UUID employeeReporterId
    // ... other fields with similar annotations ...
) {}

And the controller:

@PostMapping
public ResponseEntity<DamageReportDTO> createDamageReport(@RequestBody DamageReportDTO damageDTO) {
    return ResponseEntity.status(201).body(damageReportService.createDamageReport(damageDTO));
}

In Swagger UI, the schema for POST /v1/damage-report showed:

All had stars, even though only employeeCausatorId should.

Step 1: Investigating the Issue

I figured Swagger might be misinterpreting the @NotNull annotations. Since damageType and employeeReporterId use groups = OnFinalized.class, I assumed Swagger wasn’t smart enough to ignore those constraints for this endpoint. But why was id marked required? It didn’t even have @NotNull! Maybe it’s because it’s a record, and all fields are implicitly non-nullable in Java? I wasn’t sure.

I checked the raw OpenAPI spec at /v3/api-docs. The DamageReportDTO schema had: