Close

Spring MVC - Customizing existing formatters using @InitBinder

[Last Updated: Apr 27, 2026]

Starting Spring 3 a new formatter approach can be used as an alternative to PropertyEditors.

Spring 3 introduces a convenient Formatter SPI that provides a simple and robust alternative to PropertyEditors for client environments.
In general, use the Converter SPI when you need to implement general-purpose type conversion logic; for example, for converting between a java.util.Date and java.lang.Long. Use the Formatter SPI when you're working in a client environment, such as a web application, and need to parse and print localized field values. The ConversionService provides a unified type conversion API for both SPIs.

Formatters specify the data format to be rendered in user interface. They also provide a way to convert string input from user interface to Java data type.

Converter are used to convert one type to another.

In this example we are going to hook up Spring built-in Formatters: DateFormatter, NumberStyleFormatter and CurrencyStyleFormatter.

We will use @InitBinder approach.



Creating the Controller

This controller handles trade requests based on trade id : /trades/23

@Controller
@RequestMapping("trades")
public class TradeController {

    @Autowired
    private TradeService tradeService;

    @InitBinder
    private void customizeBinding (@PathVariable("tradeId") long tradeId,
                                   WebDataBinder binder) {
        Trade trade = tradeService.getTradeById(tradeId);
        if (trade == null) {
            return;
        }

        DateFormatter dateFormatter = new DateFormatter();
        dateFormatter.setPattern("MM-dd-yyyy");
        binder.addCustomFormatter(dateFormatter, "tradeDate");

        NumberStyleFormatter numberFormatter = new NumberStyleFormatter();
        numberFormatter.setPattern("#,###,###,###.##");
        binder.addCustomFormatter(numberFormatter, "amount");


        CurrencyStyleFormatter currencyFormatter = new CurrencyStyleFormatter();
        currencyFormatter.setCurrency("Buy".equals(trade.getBuySell()) ?
                               trade.getBuyCurrency() : trade.getSellCurrency());
        binder.addCustomFormatter(currencyFormatter, "amount");
    }

    @RequestMapping("/{tradeId:\\d+}")
    public String handleTradeRequest (@PathVariable("tradeId") long tradeId,
                                                               Model model) {
      ........
    }
}



Backing Object

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Date;

public class Trade {

    private long tradeId;
    private String buySell;
    private Currency buyCurrency;
    private Currency sellCurrency;
    private BigDecimal amount;
    private Date tradeDate;

   // getters and setters

}



The JSP page

We have to use spring bind tag for formatting to work.

<%@ page language="java"
    contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<html>
<body>
   <h3> Showing Trade with Id ${trade.tradeId} <h3>
   <p>BuySell: ${trade.buySell}</p>
   <p>Buy Currency: ${trade.buyCurrency}</p>
   <p>Sell Currency: ${trade.sellCurrency}</p>
   <p>Amount :
   <spring:bind path="trade.amount">${status.value}</spring:bind>
   </p>
   <p>Trade Date :
      <spring:bind path="trade.tradeDate">${status.value}</spring:bind>
      </p>
</body>
</html>


Running Example

To try examples, run embedded Jetty (configured in pom.xml of example project below):

mvn jetty:run

With customized formatting:

$ curl -s http://localhost:8080/spring-formatters/trades/1


<html>
<body>
<h3> Showing Trade with Id 1 <h3>
<p>BuySell: Buy</p>
<p>Buy Currency: USD</p>
<p>Sell Currency: EUR</p>
<p>Amount :
$23,030.70
</p>
<p>Trade Date :
08-25-2008
</p>
</body>
</html>


Without formatting:



Registering Formatter Globally

In above example we used @InitBinder to customize formatters. Alternatively we can register our custom formatter globally in @Configuration class:

@EnableWebMvc
@Configuration
public class TradeConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addFormatters (FormatterRegistry registry) {
    //add formatter and converter here
        registry.addFormatter(....);
    }
}

We used similar configuration in our custom Converter Example.

Global registration of a Formatter should be used if it's on field data type rather than on field name.

In @InitBinding approach, we configure formatter and property editors on per request basis and we have an option to specify field names. Also we can access more dynamic information on @InitBinding level. For example in our above example we are getting the Trade instance based on path variable tradeId. We are doing so to decide on the CurrencyStyleFormatter currency option.



Integration Tests

package com.logicbig.example;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import java.text.SimpleDateFormat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringJUnitWebConfig(TradeConfig.class)
public class TradeControllerTest {

  @Autowired private WebApplicationContext wac;

  private MockMvc mockMvc;

  @BeforeEach
  public void setup() {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
  }

  @Test
  public void testTrade2WithFormattedValues() throws Exception {
    MvcResult result =
        mockMvc
            .perform(get("/trades/2"))
            .andExpect(status().isOk())
            .andExpect(view().name("trade-page"))
            .andExpect(model().attributeExists("trade"))
            .andReturn();

    Trade trade = (Trade) result.getModelAndView().getModel().get("trade");

    assertEquals(2L, trade.getTradeId());
    assertEquals("Sell", trade.getBuySell());
    assertEquals("GBP", trade.getBuyCurrency().getCurrencyCode());
    assertEquals("JPY", trade.getSellCurrency().getCurrencyCode());
    assertEquals("1234567.89", trade.getAmount().toString());

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    assertEquals("2024-02-28", sdf.format(trade.getTradeDate()));
  }
}
mvn clean test -Dtest="TradeControllerTest"

Output

$ mvn clean test -Dtest="TradeControllerTest"
[INFO] Scanning for projects...
[WARNING]
[WARNING] Some problems were encountered while building the effective model for com.logicbig.example:spring-formatters:war:1.0-SNAPSHOT
[WARNING] 'build.plugins.plugin.version' for org.apache.maven.plugins:maven-war-plugin is missing. @ line 37, column 21
[WARNING]
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
[WARNING]
[WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
[WARNING]
[INFO]
[INFO] ---------------< com.logicbig.example:spring-formatters >---------------
[INFO] Building spring-formatters 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ war ]---------------------------------
[INFO]
[INFO] --- clean:3.2.0:clean (default-clean) @ spring-formatters ---
[INFO] Deleting D:\example-projects\spring-mvc\spring-formatters\target
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ spring-formatters ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\spring-formatters\src\main\resources
[INFO]
[INFO] --- compiler:3.5.1:compile (default-compile) @ spring-formatters ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 5 source files to D:\example-projects\spring-mvc\spring-formatters\target\classes
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ spring-formatters ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\spring-formatters\src\test\resources
[INFO]
[INFO] --- compiler:3.5.1:testCompile (default-testCompile) @ spring-formatters ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\example-projects\spring-mvc\spring-formatters\target\test-classes
[INFO]
[INFO] --- surefire:3.2.5:test (default-test) @ spring-formatters ---
[INFO] Using auto detected provider org.apache.maven.surefire.junit4.JUnit4Provider
[WARNING] file.encoding cannot be set as system property, use <argLine>-Dfile.encoding=...</argLine> instead
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.logicbig.example.TradeControllerTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.649 s -- in com.logicbig.example.TradeControllerTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.182 s
[INFO] Finished at: 2026-04-27T11:11:46+08:00
[INFO] ------------------------------------------------------------------------

Example Project

Dependencies and Technologies Used:

  • spring-webmvc 7.0.6 (Spring Web MVC)
     Version Compatibility: 4.2.0.RELEASE - 7.0.6Version List
    ×

    Version compatibilities of spring-webmvc with this example:

      javax.servlet-api:3.x
    • 4.2.0.RELEASE
    • 4.2.1.RELEASE
    • 4.2.2.RELEASE
    • 4.2.3.RELEASE
    • 4.2.4.RELEASE
    • 4.2.5.RELEASE
    • 4.2.6.RELEASE
    • 4.2.7.RELEASE
    • 4.2.8.RELEASE
    • 4.2.9.RELEASE
    • 4.3.0.RELEASE
    • 4.3.1.RELEASE
    • 4.3.2.RELEASE
    • 4.3.3.RELEASE
    • 4.3.4.RELEASE
    • 4.3.5.RELEASE
    • 4.3.6.RELEASE
    • 4.3.7.RELEASE
    • 4.3.8.RELEASE
    • 4.3.9.RELEASE
    • 4.3.10.RELEASE
    • 4.3.11.RELEASE
    • 4.3.12.RELEASE
    • 4.3.13.RELEASE
    • 4.3.14.RELEASE
    • 4.3.15.RELEASE
    • 4.3.16.RELEASE
    • 4.3.17.RELEASE
    • 4.3.18.RELEASE
    • 4.3.19.RELEASE
    • 4.3.20.RELEASE
    • 4.3.21.RELEASE
    • 4.3.22.RELEASE
    • 4.3.23.RELEASE
    • 4.3.24.RELEASE
    • 4.3.25.RELEASE
    • 4.3.26.RELEASE
    • 4.3.27.RELEASE
    • 4.3.28.RELEASE
    • 4.3.29.RELEASE
    • 4.3.30.RELEASE
    • 5.0.0.RELEASE
    • 5.0.1.RELEASE
    • 5.0.2.RELEASE
    • 5.0.3.RELEASE
    • 5.0.4.RELEASE
    • 5.0.5.RELEASE
    • 5.0.6.RELEASE
    • 5.0.7.RELEASE
    • 5.0.8.RELEASE
    • 5.0.9.RELEASE
    • 5.0.10.RELEASE
    • 5.0.11.RELEASE
    • 5.0.12.RELEASE
    • 5.0.13.RELEASE
    • 5.0.14.RELEASE
    • 5.0.15.RELEASE
    • 5.0.16.RELEASE
    • 5.0.17.RELEASE
    • 5.0.18.RELEASE
    • 5.0.19.RELEASE
    • 5.0.20.RELEASE
    • 5.1.0.RELEASE
    • 5.1.1.RELEASE
    • 5.1.2.RELEASE
    • 5.1.3.RELEASE
    • 5.1.4.RELEASE
    • 5.1.5.RELEASE
    • 5.1.6.RELEASE
    • 5.1.7.RELEASE
    • 5.1.8.RELEASE
    • 5.1.9.RELEASE
    • 5.1.10.RELEASE
    • 5.1.11.RELEASE
    • 5.1.12.RELEASE
    • 5.1.13.RELEASE
    • 5.1.14.RELEASE
    • 5.1.15.RELEASE
    • 5.1.16.RELEASE
    • 5.1.17.RELEASE
    • 5.1.18.RELEASE
    • 5.1.19.RELEASE
    • 5.1.20.RELEASE
    • 5.2.0.RELEASE
    • 5.2.1.RELEASE
    • 5.2.2.RELEASE
    • 5.2.3.RELEASE
    • 5.2.4.RELEASE
    • 5.2.5.RELEASE
    • 5.2.6.RELEASE
    • 5.2.7.RELEASE
    • 5.2.8.RELEASE
    • 5.2.9.RELEASE
    • 5.2.10.RELEASE
    • 5.2.11.RELEASE
    • 5.2.12.RELEASE
    • 5.2.13.RELEASE
    • 5.2.14.RELEASE
    • 5.2.15.RELEASE
    • 5.2.16.RELEASE
    • 5.2.17.RELEASE
    • 5.2.18.RELEASE
    • 5.2.19.RELEASE
    • 5.2.20.RELEASE
    • 5.2.21.RELEASE
    • 5.2.22.RELEASE
    • 5.2.23.RELEASE
    • 5.2.24.RELEASE
    • 5.2.25.RELEASE
    • 5.3.0
    • 5.3.1
    • 5.3.2
    • 5.3.3
    • 5.3.4
    • javax.servlet-api:4.x
    • 5.3.5
    • 5.3.6
    • 5.3.7
    • 5.3.8
    • 5.3.9
    • 5.3.10
    • 5.3.11
    • 5.3.12
    • 5.3.13
    • 5.3.14
    • 5.3.15
    • 5.3.16
    • 5.3.17
    • 5.3.18
    • 5.3.19
    • 5.3.20
    • 5.3.21
    • 5.3.22
    • 5.3.23
    • 5.3.24
    • 5.3.25
    • 5.3.26
    • 5.3.27
    • 5.3.28
    • 5.3.29
    • 5.3.30
    • 5.3.31
    • 5.3.32
    • 5.3.33
    • 5.3.34
    • 5.3.35
    • 5.3.36
    • 5.3.37
    • 5.3.38
    • 5.3.39
    • javax.* -> jakarta.*
      jakarta.servlet-api:6.x
      Java 17 min
    • 6.0.0
    • 6.0.1
    • 6.0.2
    • 6.0.3
    • 6.0.4
    • 6.0.5
    • 6.0.6
    • 6.0.7
    • 6.0.8
    • 6.0.9
    • 6.0.10
    • 6.0.11
    • 6.0.12
    • 6.0.13
    • 6.0.14
    • 6.0.15
    • 6.0.16
    • 6.0.17
    • 6.0.18
    • 6.0.19
    • 6.0.20
    • 6.0.21
    • 6.0.22
    • 6.0.23
    • 6.1.0
    • 6.1.1
    • 6.1.2
    • 6.1.3
    • 6.1.4
    • 6.1.5
    • 6.1.6
    • 6.1.7
    • 6.1.8
    • 6.1.9
    • 6.1.10
    • 6.1.11
    • 6.1.12
    • 6.1.13
    • 6.1.14
    • 6.1.15
    • 6.1.16
    • 6.1.17
    • 6.1.18
    • 6.1.19
    • 6.1.20
    • 6.1.21
    • 6.2.0
    • 6.2.1
    • 6.2.2
    • 6.2.3
    • 6.2.4
    • 6.2.5
    • 6.2.6
    • 6.2.7
    • 6.2.8
    • 6.2.9
    • 6.2.10
    • 6.2.11
    • 6.2.12
    • 6.2.13
    • 6.2.14
    • 6.2.15
    • 6.2.16
    • 6.2.17
    • 6.2.18
    • 7.0.0
    • 7.0.1
    • 7.0.2
    • 7.0.3
    • 7.0.4
    • 7.0.5
    • 7.0.6

    Versions in green have been tested.

  • spring-test 7.0.6 (Spring TestContext Framework)
  • jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
  • junit-jupiter-engine 6.0.3 (Module "junit-jupiter-engine" of JUnit)
  • hamcrest 3.0 (Core API and libraries of hamcrest matcher framework)
  • assertj-core 3.26.3 (Rich and fluent assertions for testing in Java)
  • JDK 25
  • Maven 3.9.11

Spring MVC - Customizing existing formatters using @InitBinder Select All Download
  • spring-formatters
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • TradeController.java
          • webapp
            • WEB-INF
              • views
        • test
          • java
            • com
              • logicbig
                • example

    See Also

    Join