13

I am trying to set up a REST endpoint that allows querying a user by their email address. The email address is the last portion of the path so Spring is treating foo@example.com as the value foo@example and truncating the extension .com.

I found a similar question here Spring MVC @PathVariable with dot (.) is getting truncated However, I have an annotation based configuration using AbstractAnnotationConfigDispatcherServletInitializer and WebMvcConfigurerAdapter. Since I have no xml configuration, this solution will not work for me:

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
    <property name="useDefaultSuffixPattern" value="false" />
</bean>

I have also tried this solution which uses regex but it has not worked either.

@RequestMapping(value = "user/by-email/{email:.+}")

Does anyone know how to turn off the suffix pattern truncation without xml?

troymass
  • 1,022
  • 3
  • 11
  • 24

4 Answers4

20

The dot in the path variable at the end of the URI causes two unexpected behaviours (unexpected for the majority of users, except those familiar with the huge number of Spring configuration properties).

The first (which can be fixed using the {email:.+} regex) is that the default Spring configuration matches all path extensions. So setting up a mapping for /api/{file} will mean that Spring maps a call to /api/myfile.html to the String argument myfile. This is useful when you want /api/myfile.html, /api/myfile.md, /api/myfile.txt and others to all point to the same resource. However, we can turn this behaviour off globally, without having to resort to a regex hack on every endpoint.

The second problem is related to the first and correctly fixed by @masstroy. When /api/myfile.* points to the myfile resource, Spring assumes the path extension (.html, .txt, etc.) indicates that the resource should be returned with a specific format. This behaviour can also be very useful in some situations. But often, it will mean that the object returned by a method mapping cannot be converted into this format, and Spring will throw a HttpMediaTypeNotAcceptableException.

We can turn both off with the following (assuming Spring Boot):

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

  @Override
  public void configurePathMatch(PathMatchConfigurer configurer) {
    // turn off all suffix pattern matching
    configurer.setUseSuffixPatternMatch(false);
    // OR
    // turn on suffix pattern matching ONLY for suffixes
    // you explicitly register using
    // configureContentNegotiation(...)
    configurer.setUseRegisteredSuffixPatternMatch(true);
  }

  @Override
  public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    configurer.favorPathExtension(false);
  }
}

More about Content Negotiation.

bkjvbx
  • 969
  • 1
  • 10
  • 22
14

You have to add trailing slash at the end of the path variable after name like

 @RequestMapping(value ="/test/{name}/")

The Request like

http://localhost:8080/utooa/service/api/admin/test/Takeoff.Java@gmail.com/

takeoffjava
  • 504
  • 4
  • 15
7

I've found the solution to this using the ContentNegotiationConfigurer bean from this article: http://spring.io/blog/2013/05/11/content-negotiation-using-spring-mvc

I added the following configuration to my WebConfig class:

@EnableWebMvc
@Configuration
@ComponentScan(basePackageClasses = { RestAPIConfig.class })
public class WebConfig extends WebMvcConfigurerAdapter {    
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.favorPathExtension(false);
        configurer.defaultContentType(MediaType.APPLICATION_JSON);
    }
}

By setting .favorPathExtension(false), Spring will no longer use the file extension to override the accepts mediaType of the request. The Javadoc for that method reads Indicate whether the extension of the request path should be used to determine the requested media type with the highest priority.

Then I set up my @RequestMapping using the regex

@RequestMapping(value = "/user/by-email/{email:.+}")
troymass
  • 1,022
  • 3
  • 11
  • 24
  • Note that instead of using the `{email:.+}` hack you can also disable suffix pattern matching by overriding `WebMvcConfigurerAdapter.configurePathMatch(PathMatcherConfigurer configurer)` and setting `configurer.setUseSuffixPatternMatch(false)`. You can also restrict suffix pattern matching to explicitly registered file extensions. – bkjvbx Jan 12 '17 at 13:06
  • {email:.+} worked for me without any additional configuration. That means it didn't truncate a part after dot. But there was another problem. ExceptionHandler didn't work properly with dotted variables. Your solution fixed that problem as well – jasiustasiu Jan 12 '17 at 14:22
  • @bkjvbx can you post your solution as an answer, it looks better than this one and I'm going to mark it as the accepted answer – troymass Jan 13 '17 at 00:25
  • @masstroy Done. – bkjvbx Jan 13 '17 at 08:40
0

For the Java-Config folks:

With Spring 4 you can simply turn this feature off by:

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

  @Override
  public void configurePathMatch(PathMatchConfigurer configurer) {
    configurer.setUseSuffixPatternMatch(false);
  }

}

Then in the whole application dots will treated as dots.

d0x
  • 11,040
  • 17
  • 69
  • 104