spring-addons-starter-openapi
A single-purpose starter for springdoc-openapi: it makes the possible values of enums in the generated OpenAPI spec match what a Spring application actually accepts and emits.
Is it still needed?
Partly. The situation was re-checked against springdoc-openapi 3.1.1 on Spring Boot 4.1 (Jackson 3):
| Where the enum is used | What springdoc alone puts in the spec | What Spring actually does | Still a problem? |
|---|---|---|---|
@RequestBody / @ResponseBody (JSON) | the toString() value, or @JsonValue if present (via swagger-core) | Jackson 3 defaults WRITE_ENUMS_USING_TO_STRING and READ_ENUMS_USING_TO_STRING to true, so it also uses toString() / @JsonValue | No, for the default Jackson configuration. It comes back as soon as the application changes these EnumFeatures, registers @JsonProperty on constants, or uses another message converter: swagger-core never asks the application’s converters. |
@RequestParam, @PathVariable, @RequestHeader, @CookieValue, @MatrixVariable | the same toString() / @JsonValue value | the ConversionService: Enum.valueOf(name()) unless a Converter<String, E> is registered | Yes. For any enum whose toString() (or @JsonValue) differs from name(), a client following the spec gets a 400 on every such parameter. springdoc’s own WebConversionServiceProvider handling explicitly skips enums. |
So the starter is still relevant if your API has enum parameters (or a non-default Jackson enum configuration for bodies). The original report was springdoc-openapi#2494; the reproducer now lives in this module’s tests (src/test/java, EnumsApplication and the *EnumValuesTest classes).
What it does
It registers a Swagger ModelConverter bean (springdoc picks up any such bean) which, for enum types only, replaces swagger-core’s guess with values obtained from the application itself:
- parameters (
@RequestParam,@PathVariable,@RequestHeader,@CookieValue,@MatrixVariable, detected from the annotations springdoc passes along): candidate value sets are tried in this order — what the HTTP message converters write,toString(),name()— and the first one that the application’sConversionServiceconverts back to every constant is used. With Spring defaults that givesname(); with a registeredConverter<String, E>it gives whatever that converter accepts. - bodies (anything else, including DTO properties): each constant is serialized by the JSON
HttpMessageConverters of theRequestMappingHandlerAdapter(servlet) or the JSONEncoders of theServerCodecConfigurer(reactive), i.e. by exactly what Spring uses for@RequestBody/@ResponseBody. If several converters disagree, the spec generation fails with a message naming them (a spec can’t describe both). If no converter is found, the springdocObjectMapperis used as a fallback.
Both servlet and reactive applications are supported (the reactive converter used to be disabled until now).
Usage
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-starter-openapi</artifactId>
<version>${spring-addons.version}</version>
</dependency>
Nothing to configure. The auto-configuration only registers the converter matching the application type (servlet or reactive).
Example
public enum Status {
ACTIVE("active"), CLOSED("closed");
private final String label;
Status(String label) { this.label = label; }
@Override
@JsonValue
public String toString() { return label; }
}
@GetMapping("/accounts")
List<Account> list(@RequestParam Status status) { ... } // Account has a Status property
status query parameter | Account.status property | |
|---|---|---|
| springdoc alone | ["active", "closed"] → 400, Spring expects ACTIVE / CLOSED | ["active", "closed"] |
| with this starter | ["ACTIVE", "CLOSED"] | ["active", "closed"] |
Register a Converter<String, Status> accepting the labels and the parameter values become ["active", "closed"] too: the spec follows the application, not the other way around.