6

The springdoc-openapi library automatically marks certain properties as required in the generated OpenAPI documentation. For instance, properties annotated as @NotNull will be included in the list of required properties in the generated YAML file.

One thing the library does not do is mark optional properties as nullable: true. However, by default a Spring Boot application will accept null in requests and return null in responses for optional properties. This means that there is a discrepancy between the OpenAPI documentation and the behavior of the endpoint.

It is trivial to manually mark any individual property as nullable: just add @Schema(nullable = true) to the field or accessor. However, in a large model with multiple properties, I would rather this be automatically determined in the same manner as the required property. Namely, if the property is not required, I would want it to be nullable, and vice versa.

How can I get my optional properties marked as nullable: true in OpenAPI documentation generated by springdoc-openapi?

Example

import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.NotNull;

public class RequiredExample {
    @NotNull
    private String key;

    private String value;

    public String getKey() { return key; }
    public void setKey(String key) { this.key = key; }
    public String getValue() { return value; }
    public void setValue(String value) { this.value = value; }
}

Generated OpenAPI documentation:

"components": {
  "schemas": {
    "RequiredExample": {
      "required": [
        "key"
      ],
      "type": "object",
      "properties": {
        "key": {
          "type": "string"
        },
        "value": {
          "type": "string"
        }
      }
    }
  }
}

Desired OpenAPI documentation:

"components": {
  "schemas": {
    "RequiredExample": {
      "required": [
        "key"
      ],
      "type": "object",
      "properties": {
        "key": {
          "type": "string"
        },
        "value": {
          "type": "string"
          "nullable": true
        }
      }
    }
  }
}
M. Justin
  • 14,487
  • 7
  • 91
  • 130
  • I have the same question. Did you find a solution? Also do you know *why* `"nullable": true` hasn't been made the default for optional properties? – Snackoverflow Mar 01 '22 at 10:21
  • @Snackoverflow I haven't found anything built-in, so I've been using the [`OpenApiCustomizer`](https://stackoverflow.com/a/69310599/1108305) approach. And I don't know why they made the decision to make things nullable by default. – M. Justin Mar 01 '22 at 16:05
  • This is especially interesting for Kotlin, because there you have explicit nullable or non-nullable types. Turns out if you have a nullable property, then springdoc-openapi makes it optional (not required) but still not nullable. So according to the schema you can omit the property from JSON, but you may not have its value `null`. As of now, I am still not sure whether this is intended or a bug. Haven't really found any explanation online. – Snackoverflow Mar 02 '22 at 06:33

2 Answers2

1

You could directly annotate your attribute with @Schema. The key here is that both nullable and requiredMode will have to bet set.

import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.NotNull;

public class RequiredExample {
    @NotNull
    private String key;

    @Schema(nullable = true, requiredMode = Schema.RequiredMode.REQUIRED)
    private String value;

    public String getKey() { return key; }
    public void setKey(String key) { this.key = key; }
    public String getValue() { return value; }
    public void setValue(String value) { this.value = value; }
}

For some context, this was my exact use case.

usersina
  • 1,063
  • 11
  • 28
  • Yep, that's something I mentioned in my question, and something I didn't want to have to do for every single property: "It is trivial to manually mark any individual property as nullable: just add `@Schema(nullable = true)` to the field or accessor. However, in a large model with multiple properties, I would rather this be automatically determined in the same manner as the `required` property." – M. Justin May 22 '23 at 16:05
  • Makes sense. In that case, I would consider creating an own custom class annotation that will apply the nullable part to all fields, if they don't have a `@Schema` annotation already. Not sure how much work that would be though. – usersina May 22 '23 at 16:17
0

One solution is to create a springdoc-openapi OpenApiCustomiser Spring bean which sets all properties to nullable unless they are in the list of required properties. This approach benefits from the built-in springdoc-openapi support for @NotNull and other such annotations, since the required property will have been calculated in the standard manner based on the presence of such properties.

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.Schema;
import org.springdoc.core.customizers.OpenApiCustomiser;
import org.springframework.stereotype.Component;
import java.util.Map;

@Component
public class NullableIfNotRequiredOpenApiCustomizer implements OpenApiCustomiser {
    @Override
    @SuppressWarnings({"rawtypes", "unchecked"})
    public void customise(OpenAPI openApi) {
        for (Schema schema : openApi.getComponents().getSchemas().values()) {
            if (schema.getProperties() == null) {
                continue;
            }

            ((Map<String, Schema>) schema.getProperties()).forEach((String name, Schema value) -> {
                if (schema.getRequired() == null || !schema.getRequired().contains(name)) {
                    value.setNullable(true);
                }
            });
        }
    }
}
M. Justin
  • 14,487
  • 7
  • 91
  • 130
  • 1
    Note that you should handle `ComposedSchema` types differently. For example, if your schema has an `allOf` attribute with an object inside, the properties of that nested object should also be processed. The above code does not do so and just skips them because their `getProperties()` will return `null`. – Snackoverflow Mar 01 '22 at 10:18
  • @Snackoverflow That is likely true, and something I'll have to play around with when I get the opportunity. My code doesn't use `ComposedSchema` currently, so I haven't run into this myself. – M. Justin Mar 01 '22 at 16:06