Search code examples
spring-bootrestvalidationenums

SpringBoot rest validation does not fail on wrong enum input


I have a SpringBoot rest POST endpoint where in body I POST an enum value. This call does not fail on wrong value input. I would like the rest call to fail instead of returning null for a value which can not be deserialised.

I have tried with the following custom ObjectMapper configuration, but any wrong input i put as enum deserialises to null.

@Bean
@Primary
public ObjectMapper customJsonObjectMapper() {
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
    ObjectMapper objectMapper = builder.build();
    objectMapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, false);
    SimpleModule module = new SimpleModule();
    objectMapper.registerModule(module);
    return objectMapper;
}

For example if i have the enum:

public enum CouponOddType {
  BACK("back"),
    LAY("lay");

  private String value;

  CouponOddType(String value) {
    this.value = value;
  }

  @Override
  @JsonValue
  public String toString() {
    return String.valueOf(value);
  }

  @JsonCreator
  public static CouponOddType fromValue(String text) {
    for (CouponOddType b : CouponOddType.values()) {
      if (String.valueOf(b.value).equals(text)) {
        return b;
      }
    }
    return null;
  }
}

the dto where the request is mapped to:

    @ApiModel(description = "Filter used to query coupons. Filter properties are combined with AND operator")
@Validated
@javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2020-07-07T13:12:58.487+02:00[Europe/Ljubljana]")
public class CouponQueryFilter   {
  
  @JsonProperty("statuses")
  @Valid
  private List<CouponStatus> statuses = null;

  @JsonProperty("oddTypes")
  @Valid
  private List<CouponOddType> oddTypes = null;

  public CouponQueryFilter statuses(List<CouponStatus> statuses) {
    this.statuses = statuses;
    return this;
  }

  public CouponQueryFilter addStatusesItem(CouponStatus statusesItem) {
    if (this.statuses == null) {
      this.statuses = new ArrayList<>();
    }
    this.statuses.add(statusesItem);
    return this;
  }

  /**
   * Get statuses
   * @return statuses
  **/
  @ApiModelProperty(value = "")
      @Valid
    public List<CouponStatus> getStatuses() {
    return statuses;
  }

  public void setStatuses(List<CouponStatus> statuses) {
    this.statuses = statuses;
  }

  public CouponQueryFilter oddTypes(List<CouponOddType> oddTypes) {
    this.oddTypes = oddTypes;
    return this;
  }

  public CouponQueryFilter addOddTypesItem(CouponOddType oddTypesItem) {
    if (this.oddTypes == null) {
      this.oddTypes = new ArrayList<>();
    }
    this.oddTypes.add(oddTypesItem);
    return this;
  }

  /**
   * Get oddTypes
   * @return oddTypes
  **/
  @ApiModelProperty(value = "")
      @Valid
    public List<CouponOddType> getOddTypes() {
    return oddTypes;
  }

  public void setOddTypes(List<CouponOddType> oddTypes) {
    this.oddTypes = oddTypes;
  }

}

and in the POST request i put the enum value in json array:

{
 
    "statuses": [
       "wrong value"
    ],
    "oddTypes": [
       "wrong value"
    ]
}

I would like that this type of request results in an HTTP 404 error, instead of deserialising into null.


Solution

  • In this case, Jackson is actually behaving as intended and there is an issue in your deserialization logic. Ultimately, you want bad enum values to throw an error and return that error to the user. This is infact the default behaviour of spring and Jackson, and will result in a HTTP 400 BAD REQUEST error. IMO This is the appropriate error to return (not 404) since the user has supplied bad input.

    Unless there is a specific reason for you to implement a custom @JsonCreator in your enum class, I would get rid of it. What is happening here is that Jackson is being told to use this method for converting a string into an enum value instead from the defualt method. When a text is passed that is not a valid value of your enum, you are returning null which results into that values deserializing to null.

    A quick fix, would be to delete the JsonCreator and allow jackson to use its default behaviour for handling enums. The extra properties methods you have added are unnecessary in most cases

    public enum CouponOddType {
      BACK("back"),
        LAY("lay");
    
      private String value;
    
      CouponOddType(String value) {
        this.value = value;
      }
    }
    

    If you need to preserve the creator for some other reason, then you will need to add business logic to determine if any of the enum values in the arrays evaluated to null.

    private Response someSpringRestEndpoint(@RequestBody CouponQueryFilter filter){
      if (filter.getOddTypes() != null && filter.getOddTypes().contains(null){
        throw new CustomException()
      }
    
      if (filter.getStatuses() != null && filter.getStatuses().contains(null){
        throw new CustomException()
      }
    
      //... other business logic
    }