60
votes

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());
} 
5
There are classes RelaxedConversionService and StringToEnumIgnoringCaseConverterFactory in Spring Boot, but they are not public.OrangeDog
Here a solution to use StringToEnumIgnoringCaseConverterFactory stackoverflow.com/questions/55169848/…Mohicane
I haven't tested this but looks like this will work: vianneyfaivre.com/tech/…Colm Bhandal

5 Answers

28
votes

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());
 }
18
votes

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);
}
17
votes

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

14
votes

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.

7
votes

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.