Proper Enum Handling Across Query Params, Request Body, and Database in Spring Boot
The article demonstrates how Spring Boot can automatically convert strings to enums for query and path parameters, introduces a unified StringToEnumConverterFactory, configures Jackson for case‑insensitive enum deserialization in @RequestBody, and uses a JPA AttributeConverter to persist enums, removing repetitive manual conversion code.
Many Spring Boot projects manually convert request strings to enums using Enum.valueOf and try‑catch blocks, leading to inconsistent case handling, duplicated code, and error‑prone updates. Spring can automate this conversion, but developers need to apply the right mechanisms at each layer.
1. Query and Path Parameters as Enums
Instead of receiving a String and converting it yourself, declare the parameter type as the enum. Spring MVC automatically maps the incoming string (case‑insensitive) to the enum instance.
@GetMapping("/users")
public Result listUsers(@RequestParam UserStatusEnum status) {
// status is already an enum
return Result.success(userService.listByStatus(status));
}
@GetMapping("/users/{status}")
public Result getUserByStatus(@PathVariable UserStatusEnum status) {
return Result.success(userService.listByStatus(status));
}Use type constraints directly; avoid degrading to String and fixing it later.
2. Unified String‑to‑Enum ConverterFactory
To keep conversion rules consistent across the whole application, implement a ConverterFactory<String, Enum<?>> that performs case‑insensitive matching and throws a uniform business exception for illegal values.
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.stereotype.Component;
import com.example.common.exception.BusinessException;
import java.util.Arrays;
@Component
public class StringToEnumConverterFactory implements ConverterFactory<String, Enum<?>> {
@Override
public <T extends Enum<?>> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnumConverter<>(targetType);
}
private static class StringToEnumConverter<T extends Enum<?>> implements Converter<String, T> {
private final Class<T> enumType;
public StringToEnumConverter(Class<T> enumType) { this.enumType = enumType; }
@Override
public T convert(String source) {
if (source == null || source.isEmpty()) return null;
return Arrays.stream(enumType.getEnumConstants())
.filter(e -> e.name().equalsIgnoreCase(source))
.findFirst()
.orElseThrow(() -> new BusinessException("ENUM_CONVERSION_ERROR",
"Invalid value for enum " + enumType.getSimpleName() + ": " + source));
}
}
}Register the factory in the MVC conversion chain:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private StringToEnumConverterFactory stringToEnumConverterFactory;
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(stringToEnumConverterFactory);
}
}After registration, all @RequestParam and @PathVariable enum parameters follow the same case‑insensitive rule and produce a consistent exception for invalid values.
3. @RequestBody and Jackson Configuration
Request bodies are deserialized by Jackson, which uses a different mechanism from MVC parameter binding. Enable case‑insensitive enum handling for JSON payloads by customizing the ObjectMapper:
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer enumCustomizer() {
return builder -> builder.featuresToEnable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
}
}Now a client can send {"status":"active"} and the controller receives UserStatusEnum.ACTIVE directly:
@PostMapping("/users")
public Result createUser(@RequestBody UserUpdateDTO dto) {
// dto.status is already an enum
...
}The conversion for @RequestBody is separate from MVC conversion; both need explicit configuration.
4. Practical Scenarios
Scenario 1 – List Filtering
@GetMapping("/orders")
public Result listOrders(@RequestParam(required = false) OrderStatus status) {
return Result.success(orderService.listByStatus(status));
}Clients call /orders?status=PAID; Spring converts the string to OrderStatus.PAID automatically.
Scenario 2 – Path‑Based Operations
@PostMapping("/orders/{orderId}/status/{status}")
public Result updateOrderStatus(@PathVariable Long orderId,
@PathVariable OrderStatus status) {
orderService.updateStatus(orderId, status);
return Result.success();
}Scenario 3 – Persisting Enums with JPA
@Entity
public class Order {
@Convert(converter = OrderStatusConverter.class)
private OrderStatus status;
}
@Converter(autoApply = true)
public class OrderStatusConverter implements AttributeConverter<OrderStatus, Integer> {
@Override
public Integer convertToDatabaseColumn(OrderStatus status) {
return status == null ? null : status.getCode();
}
@Override
public OrderStatus convertToEntityAttribute(Integer code) {
return Arrays.stream(OrderStatus.values())
.filter(s -> s.getCode().equals(code))
.findFirst()
.orElse(null);
}
}Conclusion
Declare query and path parameters as enums; Spring handles conversion.
Use a single StringToEnumConverterFactory for consistent, case‑insensitive conversion and uniform error handling.
Configure Jackson with MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS for @RequestBody deserialization.
Persist enums via JPA @Convert and an AttributeConverter to store integer codes.
The entire request‑response chain now uses the same business semantics: the front end sends strings, the back end works with enums.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
