Spring formatter API provides a facility to bind an Annotation to a org.springframework.format.Formatter implementation.
That means while creating a custom Formatter we can define a corresponding custom annotation and bind that to our formatter. We will see an example on that in the next tutorial.
In this tutorial, we are going to explore Spring predefined formatter annotations.
Spring has following built-in formatter annotations: @NumberFormat, @DurationFormat and @DateTimeFormat.
Example
Here we are going to modify our last example for using @NumberFormat and @DateTimeFormat instead of @InitBinder method configuration for them.
The backing Object
package com.logicbig.example;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;
import java.math.BigDecimal;
import java.util.Currency;
import java.util.Date;
public class Trade {
private long tradeId;
private String buySell;
private Currency buyCurrency;
private Currency sellCurrency;
@NumberFormat(pattern = "#,###,###,###.##")
private BigDecimal amount;
@DateTimeFormat(pattern = "MM-dd-yyyy")
private Date tradeDate;
.............
}
The Controller
In InitBinder method, as compared to our last example, we are not going to remove the formatters registration except for CurrencyStyleFormatter. We don't have a corresponding annotation for CurrencyStyleFormatter. Also this formatter access some dynamic information which will not be possible on annotation level.
package com.logicbig.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.number.CurrencyStyleFormatter;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@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;
}
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) {
Trade trade = tradeService.getTradeById(tradeId);
if (trade == null) {
model.addAttribute("msg", "No trade found");
return "no-trade-page";
}
model.addAttribute("trade", trade);
return "trade-page";
}
}
The rest of the stuff is same as our last example. Also the outcome will be same here.
Running Example
To try examples, run embedded Jetty (configured in pom.xml of example project below):
mvn jetty:run
$ curl -s http://localhost:8080/spring-format-annotation/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>
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-format-annotation: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-format-annotation >------------ [INFO] Building spring-format-annotation 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ war ]--------------------------------- [INFO] [INFO] --- clean:3.2.0:clean (default-clean) @ spring-format-annotation --- [INFO] Deleting D:\example-projects\spring-mvc\spring-format-annotation\target [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ spring-format-annotation --- [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-format-annotation\src\main\resources [INFO] [INFO] --- compiler:3.5.1:compile (default-compile) @ spring-format-annotation --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 5 source files to D:\example-projects\spring-mvc\spring-format-annotation\target\classes [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ spring-format-annotation --- [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-format-annotation\src\test\resources [INFO] [INFO] --- compiler:3.5.1:testCompile (default-testCompile) @ spring-format-annotation --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 1 source file to D:\example-projects\spring-mvc\spring-format-annotation\target\test-classes [INFO] [INFO] --- surefire:3.2.5:test (default-test) @ spring-format-annotation --- [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.644 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.433 s [INFO] Finished at: 2026-04-30T16:05:29+08:00 [INFO] ------------------------------------------------------------------------
Example ProjectDependencies and Technologies Used: - spring-webmvc 7.0.6 (Spring Web MVC)
Version Compatibility: 4.2.0.RELEASE - 7.0.6 Version compatibilities of spring-webmvc with this example: 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)
- assertj-core 3.26.3 (Rich and fluent assertions for testing in Java)
- hamcrest 3.0 (Core API and libraries of hamcrest matcher framework)
- JDK 25
- Maven 3.9.11
|