Spring MVC Formatters for BigDecimal and Long

by GarciaPL on Saturday 23 January 2016

In recent times I had issue with web application written in Spring. The problem was that some numbers stored in database as a BigDecimal's and Long's were not being stored/processed properly for different locales. On backend the numbers were processed of course using English locale, but when you have number saved with different locale the calculations might be different.

Saved BigDecimal's and Long's were processed against English locale, but presentation of numbers were based on appropriate locale of present user context.


Parse BigDecimal => 12,333,444
English => 12333444
Indonesian => 12333444

Print BigDecimal => 12345.50
English => 12345.50
Indonesian => 12345,50


Only difference for printed numbers for above locales was how decimal places is presented. For English we use dot. For Indonesian comma is used.


Parse Long => 12,333,444
English => 12333444
Indonesian => 12333444

Print Long => 12333444
English => 12333444
Indonesian => 12333444


For Long's the formatter we are going to just remove grouping commas.

So, below you can find CurrencyLocaleBigDecimalFormatter :

package pl.garciapl.test;

import org.springframework.format.Formatter;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;

public class CurrencyLocaleBigDecimalFormatter implements Formatter<BigDecimal> {

    @Override
    public BigDecimal parse(String text, Locale locale) throws ParseException {
        DecimalFormat format = (DecimalFormat) NumberFormat.getNumberInstance(Locale.ENGLISH);
        format.applyPattern("#,##0.00");
        format.setParseBigDecimal(true);
        format.setGroupingUsed(true);
        return (BigDecimal) format.parse(text);
    }

    @Override
    public String print(BigDecimal object, Locale locale) {
        DecimalFormat format = (DecimalFormat) NumberFormat.getNumberInstance(locale);
        format.applyPattern("#,##0.00");
        format.setParseBigDecimal(true);
        format.setGroupingUsed(false);
        return format.format(object);
    }
}



and CurrencyLocaleLongFormatter :


package pl.garciapl.test;

import org.springframework.format.Formatter;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;

public class CurrencyLocaleLongFormatter implements Formatter<Long> {

    @Override
    public Long parse(String text, Locale locale) throws ParseException {
        DecimalFormat format = (DecimalFormat) NumberFormat.getNumberInstance(Locale.ENGLISH);
        format.applyPattern("#,##0.00");
        format.setParseBigDecimal(false);
        format.setGroupingUsed(true);
        return (Long) format.parse(text);
    }

    @Override
    public String print(Long object, Locale locale) {
        DecimalFormat format = (DecimalFormat) NumberFormat.getNumberInstance(locale);
        format.applyPattern("#,##0.00");
        format.setParseBigDecimal(false);
        format.setGroupingUsed(false);
        return format.format(object);
    }
}


Also CurrencyLocaleFormatterRegistrar is needed because of Spring framework needs to register formatters in registry to use them.

package pl.garciapl.test;

import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistrar;
import org.springframework.format.FormatterRegistry;

import java.util.HashSet;
import java.util.Set;

public class CurrencyLocaleFormatterRegistrar implements FormatterRegistrar {

    private Set<Formatter> formatters = new HashSet<>();

    public CurrencyLocaleFormatterRegistrar(Set<Formatter> formatters) {
        this.formatters = formatters;
    }

    @Override
    public void registerFormatters(FormatterRegistry registry) {
        for (Formatter element : this.formatters) {
            registry.addFormatter(element);
        }
    }
}

And finally whole beans configuration for Spring

    <bean id="currencyLocaleBigDecimalFormatter" class="ie.garciapl.test.CurrencyLocaleBigDecimalFormatter"/>
    <bean id="currencyLocaleLongFormatter" class="ie.garciapl.test.CurrencyLocaleLongFormatter"/>
    <bean id="currencyLocaleRegistrar" class="ie.garciapl.test.CurrencyLocaleFormatterRegistrar">
        <constructor-arg>
            <set>
                <ref bean="currencyLocaleBigDecimalFormatter"/>
                <ref bean="currencyLocaleLongFormatter"/>
            </set>
        </constructor-arg>
    </bean>

Do not forget to plug into your conversionCurrencyService as below :

<mvc:annotation-driven conversion-service="conversionCurrencyService"/>

Reference : [1] Spring Docs - Formatters [2] Javabeat.net - Introduction to Spring Converters and Formatters [3] CurrencyLocaleBigDecimalFormatter [4] CurrencyLocaleLongFormatter [5] CurrencyLocaleFormatterRegistrar [6] CurrencyLocaleBigDecimalFormatterTest [7] CurrencyLocaleLongFormatterTest