This is continuation of question Spring MVC @PathVariable getting truncated
Spring forum states that it has fixed(3.2 version) as part of ContentNegotiationManager.
In addition to Martin Frey's answer, this can also be fixed by adding a trailing slash in the RequestMapping value:
/path/{variable}/
Keep in mind that this fix does not support maintainability. It now requires all URI's to have a trailing slash - something that may not be apparent to API users / new developers. Because it's likely not all parameters may have a .
in them, it may also create intermittent bugs
If you are using Spring 3.2+ then below solution will help. This will handle all urls so definitely better than applying regex pattern in the request URI mapping to allow . like /somepath/{variable:.+}
Define a bean in the xml file
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="useSuffixPatternMatch" value="false"/>
<property name="useRegisteredSuffixPatternMatch" value="true"/>
</bean>
The flags usage can be found on the documentation. I am putting snipped to explain
exlanation of useRegisteredSuffixPatternMatch is said to be resolving the issue. From the java doc in the class
If enabled, a controller method mapped to "/users" also matches to "/users.json" assuming ".json" is a file extension registered with the provided {@link #setContentNegotiationManager(ContentNegotiationManager) contentNegotiationManager}. This can be useful for allowing only specific URL extensions to be used as well as in cases where a "." in the URL path can lead to ambiguous interpretation of path variable content, (e.g. given "/users/{user}" and incoming URLs such as "/users/john.j.joe" and "/users/john.j.joe.json").
For me the
@GetMapping(path = "/a/{variableName:.+}")
does work but only if you also encode the "dot" in your request url as "%2E" then it works. But requires URL's to all be that...which is not a "standard" encoding, though valid. Feels like something of a bug :|
The other work around, similar to the "trailing slash" way is to move the variable that will have the dot "inline" ex:
@GetMapping(path = "/{variableName}/a")
now all dots will be preserved, no modifications needed.
One pretty easy way to work around this issue is to append a trailing slash ...
e.g.:
use :
/somepath/filename.jpg/
instead of:
/somepath/filename.jpg
Spring considers that anything behind the last dot is a file extension such as .json
or .xml
and trucate it to retrieve your parameter.
So if you have /somepath/{variable}
:
/somepath/param
, /somepath/param.json
, /somepath/param.xml
or /somepath/param.anything
will result in a param with value param
/somepath/param.value.json
, /somepath/param.value.xml
or /somepath/param.value.anything
will result in a param with value param.value
if you change your mapping to /somepath/{variable:.+}
as suggested, any dot, including the last one will be consider as part of your parameter :
/somepath/param
will result in a param with value param
/somepath/param.json
will result in a param with value param.json
/somepath/param.xml
will result in a param with value param.xml
/somepath/param.anything
will result in a param with value param.anything
/somepath/param.value.json
will result in a param with value param.value.json
If you don't care of extension recognition, you can disable it by overriding mvc:annotation-driven
automagic :
<bean id="handlerMapping"
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="contentNegotiationManager" ref="contentNegotiationManager"/>
<property name="useSuffixPatternMatch" value="false"/>
</bean>
So, again, if you have /somepath/{variable}
:
/somepath/param
, /somepath/param.json
, /somepath/param.xml
or /somepath/param.anything
will result in a param with value param
/somepath/param.value.json
, /somepath/param.value.xml
or /somepath/param.value.anything
will result in a param with value param.value
note : the difference from the default config is visible only if you have a mapping like somepath/something.{variable}
. see Resthub project issue
if you want to keep extension management, since Spring 3.2 you can also set the useRegisteredSuffixPatternMatch property of RequestMappingHandlerMapping bean in order to keep suffixPattern recognition activated but limited to registered extension.
Here you define only json and xml extensions :
<bean id="handlerMapping"
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="contentNegotiationManager" ref="contentNegotiationManager"/>
<property name="useRegisteredSuffixPatternMatch" value="true"/>
</bean>
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="false"/>
<property name="favorParameter" value="true"/>
<property name="mediaTypes">
<value>
json=application/json
xml=application/xml
</value>
</property>
</bean>
Note that mvc:annotation-driven accepts now a contentNegotiation option to provide a custom bean but the property of RequestMappingHandlerMapping has to be changed to true (default false) (cf. https://jira.springsource.org/browse/SPR-7632).
For that reason, you still have to override the all mvc:annotation-driven configuration. I opened a ticket to Spring to ask for a custom RequestMappingHandlerMapping : https://jira.springsource.org/browse/SPR-11253. Please vote if you are intereted in.
While overriding, be carreful to consider also custom Execution management overriding. Otherwise, all your custom Exception mappings will fail. You will have to reuse messageCoverters with a list bean :
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean" />
<util:list id="messageConverters">
<bean class="your.custom.message.converter.IfAny"></bean>
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"></bean>
<bean class="org.springframework.http.converter.StringHttpMessageConverter"></bean>
<bean class="org.springframework.http.converter.ResourceHttpMessageConverter"></bean>
<bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"></bean>
<bean class="org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter"></bean>
<bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"></bean>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean>
</util:list>
<bean name="exceptionHandlerExceptionResolver"
class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver">
<property name="order" value="0"/>
<property name="messageConverters" ref="messageConverters"/>
</bean>
<bean name="handlerAdapter"
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="webBindingInitializer">
<bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
<property name="conversionService" ref="conversionService" />
<property name="validator" ref="validator" />
</bean>
</property>
<property name="messageConverters" ref="messageConverters"/>
</bean>
<bean id="handlerMapping"
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
</bean>
I implemented, in the open source project Resthub that I am part of, a set of tests on these subjects : see https://github.com/resthub/resthub-spring-stack/pull/219/files & https://github.com/resthub/resthub-spring-stack/issues/217
If you are using Spring 3.2.x and <mvc:annotation-driven />
, create this little BeanPostProcessor
:
package spring;
public final class DoNotTruncateMyUrls implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof RequestMappingHandlerMapping) {
((RequestMappingHandlerMapping)bean).setUseSuffixPatternMatch(false);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
Then put this in your MVC config xml:
<bean class="spring.DoNotTruncateMyUrls" />