5

I wrote a custom Spring formatter (which implements the org.springframework.format.Formatter interface) for converting form input values to BigDecimal values for dollar inputs. The formatter does not accept values with more than two digits after the decimal place. In that case, a ParseException is thrown by the formatter's parse() method.

public class InputDollarBigDecimalFormatter
    implements Formatter<BigDecimal>
{
    @Override
    public BigDecimal parse(String text, Locale locale)
        throws ParseException
    {
        // ...
    }

    @Override
    public String print(BigDecimal amount, Locale locale)
    {
        // ...
    }
}

The formatter is registered so it can be applied using an annotation named @InputDollarBigDecimalFormat.

A field in my form-backing object, to which the formatter is applied, looks like this:

@InputDollarBigDecimalFormat
private BigDecimal price;

The formatter is working fine.

The problem is that when the ParseException is thrown, Spring still attempts to convert the input value to a BigDecimal using the default conversion. This means an input value of 100.123 is still successfully converted to a BigDecimal, even though my custom formatter throws a ParseException.

How can I prevent Spring from converting the input value to a BigDecimal when my custom Formatter has rejected the value by throwing a ParseException?

I'm using Spring 3.1.0.

Spring formatters are documented here.


UPDATE:

After stepping through the code with a debugger, I see that the logic for this is in the org.springframework.beans.TypeConverterDelegate class. Spring is clearly doing this on purpose, so I can see only the following possible solutions:

(1) If possible, un-register the default PropertyEditor for BigDecimal values. It appears Spring registers a org.springframework.beans.propertyeditors.CustomNumberEditor for converting a string to a BigDecimal. Of course, this solution has the downside that the default PropertyEditor would not be available for other BigDecimal fields (that do not use the custom formatter).

(2) Create a Dollar class that wraps a BigDecimal and change the custom formatter to work with the Dollar class. The type of field would also have to change to Dollar. This would be a bit of a nuisance when working with the values.

(3) Perhaps Spring has realized it is incorrect to fall back to the default PropertyEditor when a custom formatter has rejected a value and so this has been changed in a more recent version of Spring. I'm doubtful, but if anyone knows either way, your help would be appreciated.

(4) But perhaps the correct solution is to view the custom formatter as just helping out the default PropertyEditor by allowing more formats. In addtion to limiting the value to two decimal places, my custom formatter also allows for the input value to include a dollar sign and commas. I could leave that logic, but remove the decimal place restriction. Then I could add a custom bean validation (JSR 303) constraint that rejects the value if it does not have exactly two digits after the decimal place (unless it has none). I would just have to annotate my field with the constraint:

@InputDollarBigDecimalFormat
@WholeDollarOrCentsConstraint
private BigDecimal price;

Of course, I would also have to add an error message for the constraint, in addition to the one for the formatter.

If any of these seems like the correct solution to you, feel free to add an answer with your reasoning.

John S
  • 21,212
  • 8
  • 46
  • 56
  • just for kicks, have you tried throwing `IllegalArgumentException` instead? – leeor Sep 25 '15 at 20:50
  • @leeor - I had the same thought. Unfortunately, I just tried that and the result is the same. After stepping through the code, I can see that the result would be the same for any RuntimeException thrown. – John S Sep 25 '15 at 21:01

1 Answers1

0

Here's what worked for me. If you have a look in TypeConverterDelegate, the very first thing it does (and also later along as part of the 'fallback conversion' bit) is that it checks if there is a custom PropertyEditor registered for the given type. If there is, it completely bypasses the conversionService part (which is what swallows your exception) and uses the editor instead.

So instead of your suggestion (1), you could try creating your own class that extends PropertyEditorSupport and register it for BigDecimal (see here for an example of how to do that). You could then throw an IllegalArgumentException in your setAsText method.