Spring 3.0 MVC binding Enums Case Sensitive

JavaSpringSpring Mvc

Java Problem Overview


If I have a RequestMapping in a Spring controller like so...

@RequestMapping(method = RequestMethod.GET, value = "{product}")
public ModelAndView getPage(@PathVariable Product product)

And Product is an enum. eg. Product.Home

When I request the page, mysite.com/home

I get

Unable to convert value "home" from type 'java.lang.String' to type 'domain.model.product.Product'; nested exception is java.lang.IllegalArgumentException: No enum const class domain.model.product.Product.home

Is there a way to have the enum type converter to understand that lower case home is actually Home?

I'd like to keep the url case insensitive and my Java enums with standard capital letters.

Thanks

Solution

public class ProductEnumConverter extends PropertyEditorSupport
{
    @Override public void setAsText(final String text) throws IllegalArgumentException
    {
        setValue(Product.valueOf(WordUtils.capitalizeFully(text.trim())));
    }
}

registering it

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
        <property name="customEditors">
            <map>
                <entry key="domain.model.product.Product" value="domain.infrastructure.ProductEnumConverter"/>
            </map>
        </property>
    </bean>

Add to controllers that need special conversion

@InitBinder
public void initBinder(WebDataBinder binder)
{
    binder.registerCustomEditor(Product.class, new ProductEnumConverter());
} 

Java Solutions


Solution 1 - Java

Broadly speaking, you want to create a new PropertyEditor that does the normalisation for you, and then you register that in your Controller like so:

@InitBinder
 public void initBinder(WebDataBinder binder) {
   
  binder.registerCustomEditor(Product.class,
    new CaseInsensitivePropertyEditor());
 }

Solution 2 - Java

I think you will have to implement a Custom PropertyEditor.

Something like this:

public class ProductEditor extends PropertyEditorSupport{

    @Override
    public void setAsText(final String text){
        setValue(Product.valueOf(text.toUpperCase()));
    }

}

See GaryF's answer on how to bind it

Here's a more tolerant version in case you use lower case in your enum constants (which you probably shouldn't, but still):

@Override
public void setAsText(final String text){
    Product product = null;
    for(final Product candidate : Product.values()){
        if(candidate.name().equalsIgnoreCase(text)){
            product = candidate;
            break;
        }
    }
    setValue(product);
}

Solution 3 - Java

It's also possible to create a generic converter that will work with all Enums like this:

public class CaseInsensitiveConverter<T extends Enum<T>> extends PropertyEditorSupport {

    private final Class<T> typeParameterClass;

    public CaseInsensitiveConverter(Class<T> typeParameterClass) {
        super();
        this.typeParameterClass = typeParameterClass;
    }

    @Override
    public void setAsText(final String text) throws IllegalArgumentException {
        String upper = text.toUpperCase(); // or something more robust
        T value = T.valueOf(typeParameterClass, upper);
        setValue(value);
    }
}

Usage:

@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.registerCustomEditor(MyEnum.class, new CaseInsensitiveConverter<>(MyEnum.class));
}

Or globally as skaffman explains

Solution 4 - Java

In Spring Boot 2 you can use ApplicationConversionService. It provides some useful converters, especially org.springframework.boot.convert.StringToEnumIgnoringCaseConverterFactory - responsible for converting a string value to an enum instance. This is the most generic (we don't need to create separate converter/formatter per enum) and simplest solution I've managed to find.

import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class AppWebMvcConfigurer implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
	    ApplicationConversionService.configure(registry);
    }
}

I know that questions is regarding Spring 3 but this is the first result in google when searching for a spring mvc enums case insensitive phrase.

Solution 5 - Java

To add to @GaryF's answer, and to address your comment to it, you can declare global custom property editors by injecting them into a custom AnnotationMethodHandlerAdapter. Spring MVC normally registers one of these by default, but you can give it a specially-configured one if you choose, e.g.

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
  <property name="webBindingInitializer">
    <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
      <property name="propertyEditorRegistrars">
        <list>
          <bean class="com.xyz.MyPropertyEditorRegistrar"/>
        </list>
      </property>
    </bean>
  </property>
</bean>

MyPropertyEditorRegistrar is an instance of PropertyEditorRegistrar, which in turns registers custom PropertyEditor objects with Spring.

Simply declaring this should be enough.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questiondom farrView Question on Stackoverflow
Solution 1 - JavaGaryFView Answer on Stackoverflow
Solution 2 - JavaSean Patrick FloydView Answer on Stackoverflow
Solution 3 - Javauser404345View Answer on Stackoverflow
Solution 4 - JavatstecView Answer on Stackoverflow
Solution 5 - JavaskaffmanView Answer on Stackoverflow