As discussed in the previous section, core.convert is a general-purpose type conversion system. It provides a unified ConversionService API as well as a strongly typed SPI Converter interface for implementing conversion logic from one type to another. The Spring container uses this system to bind bean property values. Additionally, both Spring Expression Language (SpEL) and DataBinder use this system to bind field values. For example, if SpEL needs to convert Short to Long to complete the expression.setValue(Object bean, Object value) attempt, the core.convert performs this conversion.

Now consider the type conversion requirements in a typical client environment, such as a web or desktop application. In such environments, conversion from String is typically done to support the client-side postback process, and also back to String to support the view rendering process. In addition, localization of String values is often required. The more general SPI interface core.convert Converter does not address such formatting requirements directly. To address these directly, Spring 3 introduced a convenient SPI interface, Formatter, which provides a simple and robust alternative to implementing PropertyEditor for client environments.

In general, You can use the Converter SPI interface if you need to implement general purpose type conversion logic - for example, to convert between java.util.Date and Long. You can use the SPI Formatter interface if you are working in a client environment (such as a web application) and need to parse and output localized field values. ConversionService provides a single type conversion API for both SPI interfaces.

SPI Interface Formatter

SPI Interface Formatter for implementing field formatting logic is simple and strongly typed. The following listing shows the Formatter interface definition:


package org.springframework.format;
public interface Formatter<T> extends Printer<T>, Parser<T> {
}

Formatter extends from the Printer and Parser building block interfaces. The following listing shows the definitions of these two interfaces:

 
public interface Printer<T> {
    String print(T fieldValue, Locale locale);
}

import java.text.ParseException;
public interface Parser<T> {
    T parse(String clientValue, Locale locale) throws ParseException;
}

To create your own Formatter, implement the Formatter interface shown earlier. Parameterize T as the type of object you want to format - for example, java.util.Date. Implement the print() operation to print an instance of T for display in the client locale. Implement a parse() operation to parse an instance of T from the formatted representation returned from the client locale. Your Formatter should throw ParseException or IllegalArgumentException exceptions when a parse attempt fails. Make sure your Formatter implementation is thread-safe.

The format subpackages contain multiple Formatter implementations for convenience. The Number package contains NumberStyleFormatter, CurrencyStyleFormatter, and PercentStyleFormatter for formatting Number objects that use java.text.NumberFormat. The datetime package contains a DateFormatter for formatting java.util.Date objects with java.text.DateFormat.

The following DateFormatter is an example implementation of Formatter:

Java

package org.springframework.format.datetime;
public final class DateFormatter implements Formatter<Date> {
    private String pattern;
    public DateFormatter(String pattern) {
        this.pattern = pattern;
    }
    public String print(Date date, Locale locale) {
        if (date == null) {
            return "";
        }
        return getDateFormat(locale).format(date);
    }
    public Date parse(String formatted, Locale locale) throws ParseException {
        if (formatted.length() == 0) {
            return null;
        }
        return getDateFormat(locale).parse(formatted);
    }
    protected DateFormat getDateFormat(Locale locale) {
        DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
        dateFormat.setLenient(false);
        return dateFormat;
    }
}
Kotlin

class DateFormatter(private val pattern: String) : Formatter<Date> {
    override fun print(date: Date, locale: Locale)
            = getDateFormat(locale).format(date)
    @Throws(ParseException::class)
    override fun parse(formatted: String, locale: Locale)
            = getDateFormat(locale).parse(formatted)
    protected fun getDateFormat(locale: Locale): DateFormat {
        val dateFormat = SimpleDateFormat(this.pattern, locale)
        dateFormat.isLenient = false
        return dateFormat
    }
}

The Spring team welcomes community participation in the development of Formatter. To make your suggestions, see "GitHub Issues".

Annotation Driven Formatting

Field formatting can be configured by field or annotation type. To bind an annotation to Formatter, implement AnnotationFormatterFactory. The following listing shows the definition of the AnnotationFormatterFactory interface:


package org.springframework.format;
public interface AnnotationFormatterFactory<A extends Annotation> {
    Set<Class<?>> getFieldTypes();
    Printer<?> getPrinter(A annotation, Class<?> fieldType);
    Parser<?> getParser(A annotation, Class<?> fieldType);
}

To create an implementation:

  1. Parameterize A as annotationType fields, with which you want to associate formatting logic - for example, org.springframework.format.annotation.DateTimeFormat.

  2. Let getFieldTypes() returns the field types for which the annotation can be used.

  3. Let getPrinter() return Printer to print the value the annotated field.

  4. Let getParser() return a Parser to parse the clientValue for the annotated field fields.

The following example implementation of AnnotationFormatterFactory binds the @NumberFormat annotation to the formatter so that you can specify a numbering style or pattern :

Java

public final class NumberFormatAnnotationFormatterFactory
        implements AnnotationFormatterFactory<NumberFormat> {
    public Set<Class<?>> getFieldTypes() {
        return new HashSet<Class<?>>(asList(new Class<?>[] {
            Short.class, Integer.class, Long.class, Float.class,
            Double.class, BigDecimal.class, BigInteger.class }));
    }
    public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }
    public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }
    private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) {
        if (!annotation.pattern().isEmpty()) {
            return new NumberStyleFormatter(annotation.pattern());
        } else {
            Style style = annotation.style();
            if (style == Style.PERCENT) {
                return new PercentStyleFormatter();
            } else if (style == Style.CURRENCY) {
                return new CurrencyStyleFormatter();
            } else {
                return new NumberStyleFormatter();
            }
        }
    }
}
Kotlin

class NumberFormatAnnotationFormatterFactory : AnnotationFormatterFactory<NumberFormat> {
    override fun getFieldTypes(): Set<Class<*>> {
        return setOf(Short::class.java, Int::class.java, Long::class.java, Float::class.java, Double::class.java, BigDecimal::class.java, BigInteger::class.java)
    }
    override fun getPrinter(annotation: NumberFormat, fieldType: Class<*>): Printer<Number> {
        return configureFormatterFrom(annotation, fieldType)
    }
    override fun getParser(annotation: NumberFormat, fieldType: Class<*>): Parser<Number> {
        return configureFormatterFrom(annotation, fieldType)
    }
    private fun configureFormatterFrom(annotation: NumberFormat, fieldType: Class<*>): Formatter<Number> {
        return if (annotation.pattern.isNotEmpty()) {
            NumberStyleFormatter(annotation.pattern)
        } else {
            val style = annotation.style
            when {
                style === NumberFormat.Style.PERCENT -> PercentStyleFormatter()
                style === NumberFormat.Style.CURRENCY -> CurrencyStyleFormatter()
                else -> NumberStyleFormatter()
            }
        }
    }
}

To force formatting, you can annotate fields with @NumberFormat, as shown in the following example:

Java

public class MyModel {
    @NumberFormat(style=Style.CURRENCY)
    private BigDecimal decimal;
}
Kotlin

class MyModel(
    @field:NumberFormat(style = Style.CURRENCY) private val decimal: BigDecimal
)

Format Annotation API

In package org.springframework.format.annotation there is Portable Formats Annotation API. You can use @NumberFormat to format Number fields such as Double and Long, and @DateTimeFormat to format java.util.Date, java.util.Calendar, Long (for millisecond timestamps), and java.time from JSR-310.

The following example formats java.util.Date as an ISO date (yyyy-MM-dd) @DateTimeFormat is used:

Java

public class MyModel {
    @DateTimeFormat(iso=ISO.DATE)
    private Date date;
}
Kotlin

class MyModel(
    @DateTimeFormat(iso=ISO.DATE) private val date: Date
) 

SPI interface FormatterRegistry

FormatterRegistry is an SPI interface to register formatters and converters. FormattingConversionService is an implementation of FormatterRegistry suitable for most environments. You can programmatically or declaratively configure this option as a Spring bean, for example using FormattingConversionServiceFactoryBean. Because this implementation also implements ConversionService, you can directly configure it for use with Spring's DataBinder and Spring Expression Language (SpEL).

As follows: The listing shows the SPI interface FormatterRegistry:


package org.springframework.format;
public interface FormatterRegistry extends ConverterRegistry {
    void addPrinter(Printer printer);
    void addParser(Parser parser);
    void addFormatter(Formatter formatter);
    void addFormatterForFieldType(Class fieldType, Formatter formatter);
    void addFormatterForFieldType(Class fieldType, Printer printer, Parser parser);
    void addFormatterForFieldAnnotation(AnnotationFormatterFactory annotationFormatterFactory);
}

As follows from the previous listing, you can register formatters by field type or by annotation.

SPI interface FormatterRegistry allows you to configure formatting rules centrally, instead of duplicating such configuration for all controllers. For example, you can force all date fields to be formatted in a certain way, or that fields with a certain annotation be formatted in a certain way. With a generic FormatterRegistry you define these rules once, and they are applied whenever formatting is required.

SPI interface FormatterRegistrar

FormatterRegistrar is an SPI interface for registering formatters and converters via FormatterRegistry. The following listing shows its interface definition:


package org.springframework.format;
public interface FormatterRegistrar {
    void registerFormatters(FormatterRegistry registry);
}

FormatterRegistrar is useful when registering multiple related converters and formatters for a specific category of formatting, such as date formatting. It can also be useful if declarative registration is not sufficient - for example, if the formatter needs to be indexed under a specific field type other than its own <T>, or if a Printer pair is registered /Parser. The next section provides more information about registering converters and formatters.

Configuring formatting in Spring MVC

Configuring the global date and time format

Default date and time fields , not annotated with @DateTimeFormat, are converted from strings using the DateFormat.SHORT style. If you wish, you can change this by defining your own global format.

To do this, make sure that Spring does not register default formatters. Instead, register formatters manually with:

  • org.springframework.format.datetime.standard.DateTimeFormatterRegistrar

  • org.springframework.format.datetime.DateFormatterRegistrar

For example, the following Java configuration registers a global format of the form yyyyMMdd:

Java

@Configuration
public class AppConfig {
    @Bean
    public FormattingConversionService conversionService() {
        // Use DefaultFormattingConversionService, but don't register default values
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
        // Make sure @NumberFormat is still supported
        conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
        // Register the JSR-310 date conversion in a specific global format
        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
        registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
        registrar.registerFormatters(conversionService);
        // Register the date conversion in a specific global format
        DateFormatterRegistrar registrar = new DateFormatterRegistrar();
        registrar.setFormatter(new DateFormatter("yyyyMMdd"));
        registrar.registerFormatters(conversionService);
        return conversionService;
    }
}
Kotlin

@Configuration
class AppConfig {
    @Bean
    fun conversionService(): FormattingConversionService {
        // Use DefaultFormattingConversionService, but don't register default values
        return DefaultFormattingConversionService(false).apply {
            // Make sure @NumberFormat is still supported
            addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory())
            // Register JSR-310 date conversion in a specific global format
            val registrar = DateTimeFormatterRegistrar ()
            registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
            registrar.registerFormatters(this)
            // Register the date conversion in a specific global format
            val registrar = DateFormatterRegistrar()
            registrar.setFormatter(DateFormatter("yyyyMMdd"))
            registrar.registerFormatters (this)
        }
    }
}

If you prefer XML-based configuration, you can use FormattingConversionServiceFactoryBean. The following example shows , how to do this:



            <beans xmlns="http://www.springframework.org/schema/beans"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd>
    <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="registerDefaultFormatters" value="false" />
        <property name="formatters">
            <set>
                <bean class="org.springframework.format.number.NumberFormatAnnotationFormatterFactory" />
            </set>
        </property>
        <property name="formatterRegistrars">
            <set>
                <bean class="org.springframework.format.datetime.standard.DateTimeFormatterRegistrar">
                    <property name="dateFormatter">
                        <bean class="org.springframework.format.datetime.standard.DateTimeFormatterFactoryBean">
                            <property name="pattern" value="yyyyMMdd"/>
                        </bean>
                    </property>
                </bean>
            </set>
        </property>
            </bean>
            </beans>

Please note that there are additional factors to consider when configuring date and time formats in web applications.