Description
Ruslan Stelmachenko opened SPR-14345 and commented
Using Spring Web MVC.
When new org.springframework.format.Formatter
registered using org.springframework.core.convert.ConversionService.addFormatter
, then when conversion fails because of parse error (in Formatter.parse
method), then catched exception in the end turns into org.springframework.beans.TypeMismatchException
.
When the same formatter registered in controller's @InitBinder
method using org.springframework.web.bind.WebDataBinder.addCustomFormatter
, then when conversion fails because of parse error (in Formatter.parse method
), then catched exception in the end turns into org.springframework.beans.MethodInvocationException
.
This difference leads to new problem: when you use Spring's i18n feature (messages.properties
) to localize error messages, then message code is different depending on how formatter was registered.
In first case it is: typeMismatch.*
.
In second case it is: methodInvocation.*
.
I'm not sure this is the intended behavior
It is very strange and inconvinient, that the same formatter throws different error message depending on the way in which it was registered.
Affects: 4.2.6
Attachments:
- spr-14345.zip (71.26 kB)
Issue Links:
- TypeConverterSupport does not propagate custom exceptions anymore [SPR-14661] #19225 TypeConverterSupport does not propagate custom exceptions anymore
Referenced from: commits 367e663, f9e8924, c6f63bd, d51c22a
Backported to: 4.2.7
Activity
spring-projects-issues commentedon Jun 8, 2016
Juergen Hoeller commented
I can't reproduce this: With either variant, I consistently get a
TypeMismatchException
if the actual formatter invocation failed, whereas aMethodInvocationException
only happens if the target setter is being invoked with a converted value but fails to execute properly.Could you please double-check your scenario there and provide a corresponding unit test?
spring-projects-issues commentedon Jun 8, 2016
Ruslan Stelmachenko commented
I've attached the mini-project to reproduce this.
Run it with
gradlew bootRun
and enterhttp://localhost:8080?ym=123
in the browser.You will see
It is MethodInvocation Error
message.Uncomment one of the lines in
demo.controller.HomeController.initBinder
method to test different ways to registerYearMonthFormatter
formatter.When formatter is registered using
ConversionService
, you will seeIt is TypeMismatch Error
message.Also note, that if you enter just
http://localhost:8080
(withoutym
parameter), then in case when formatter registered usingWebDataBinder
, it causesNullPointerException
! It is no case, when formatter registered usingConversionService
.I thought
Formatter.print
method guarantees, that object will not be null.spring-projects-issues commentedon Jun 9, 2016
Ruslan Stelmachenko commented
I also found #12429 and see now that registering custom formatter using
DataBinder.addCustomFormatter(Formatter formatter)
is implemented using bridge (FormatterPropertyEditorAdapter
) between formatters and propertyEditors... I think it's the core of the problem!It's actualy not register formatter. It's register
PropertyEditor
(using bridge) when I register it using this method!This is the reson why behaviour is different.
For instanse, when
ConversionService
invokes the formatter, it doing it usingConversionUtils.invokeConverter
, which catches all exceptions (catch (Exception ex)
) and throwingConversionFailedException
:The
GenericConversionService
also handles nulls in it'sconvert
method. So converter is not invoked at all if the source is null.But
FormatterPropertyEditorAdapter
in contrast just callingformatter.parse
catching onlyjava.text.ParseException
, which will never happen if formatter'sparse
method uses other parsing tecniques, for examplejava.time
family methods. Also it doesn't handle case when source object is null.Maybe this bridge (
FormatterPropertyEditorAdapter
) should be smarter and mimic ConversionService logic when it registers new formatters?spring-projects-issues commentedon Jun 9, 2016
Ruslan Stelmachenko commented
So I think it should be something like this (simplified):
Or maybe using
ConversionUtils
. You know better. :)spring-projects-issues commentedon Jun 9, 2016
Juergen Hoeller commented
Thanks for the analysis! It's indeed custom
Formatter
implementations throwingRuntimeExceptions
where the mismatch comes in. I'll revise that bridging for 4.3 GA and will also backport it to 4.2.7.spring-projects-issues commentedon Jun 9, 2016
Juergen Hoeller commented
Since
PropertyEditors
are not supposed to throwConversionExceptions
in terms of their API contract, the better option seems to be more defensiveness in ourPropertyEditor
invocation infrastructure: i.e. catchingRuntimeException
(evenThrowable
) instead of justIllegalArgumentException
and consistently turning those intoTypeMismatchException
at that level.Point taken about
null
handling:FormatterPropertyEditorAdapter
has the same checks asFormattingConversionService
there now.This will be available in the upcoming
4.3.0.BUILD-SNAPSHOT
. Please give it a try if you have the chance...spring-projects-issues commentedon Jun 11, 2016
Ruslan Stelmachenko commented
I'm tried
4.3.0.BUILD-SNAPSHOT
and I could say, it works now!The NPE is gone. The error is
typeMismatch
.And I agree with the arguments about the API contract.
Thanks for the fixes!
—
But I should say, this will slightly change the behaviour of real property editors. For example:
After fix, it will cause
typeMismatch
error. While previously it throwsmethodInvocation
.I think this is "bad" property editor, because it violates
setAsText
contract, which says:Maybe it is better if adapter will catch
Throwable
and throwIllegalArgumentException
. This way other propery editors (registered in binder as propery editors, not as formatters) will not change it's behaviour after this fix.Of course it is only if the change of native ProperyEditors behavior is not intended.
spring-projects-issues commentedon Jun 21, 2016
Juergen Hoeller commented
From my perspective, that's ok: Exceptions coming out of
PropertyEditors
should always lead to a type mismatch error. A method invocation error code is meant to indicate an exception coming out of a target setter method, after all.1 remaining item