Stage 7: apply Model and Thymeleaf to the calculator
Learning path / Stage 7 of 37
mvn spring-boot:run. Use the port specified for this stage and stop with Ctrl+C.Use two input fields and five buttons. A static form sends numbers to Java; the controller calculates the answer and selects a result template.
Download spring-calculator.zip
Form -> GET /calculate?a=20&b=6&operation=remainder
-> controller calculates 2.0
-> Model carries values
-> Thymeleaf renders result.html
-> browser displays a separate result pageThe two inputs submit a and b. Only the clicked button submits its operation value. @RequestParam reads these three values. Java chooses an operation using switch. Model carries named values from the controller to the template. return "result" selects templates/result.html; it does not send the literal word result. Thymeleaf replaces th:text expressions with the supplied values. There is no @ResponseBody on this method. Remainder (%) means what is left after division, not percentage. The result page includes messages for zero divisors.
Try and verify: Enter 20 and 6, then click each button. Expect 26.0, 14.0, 120.0, about 3.3333333333333335 and 2.0. Try division by zero, then Calculate again. The project uses double, so decimal rounding artifacts can occur.
Open the files to study in this stage
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Arithmetic Calculator</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<main>
<h1>Arithmetic Calculator</h1>
<p>Enter two numbers and choose an operation.</p>
<form action="/calculate" method="get">
<label for="a">First number</label>
<input id="a" name="a" type="number" step="any" required>
<label for="b">Second number</label>
<input id="b" name="b" type="number" step="any" required>
<div class="buttons">
<button type="submit" name="operation" value="add">Add (+)</button>
<button type="submit" name="operation" value="subtract">Subtract (-)</button>
<button type="submit" name="operation" value="multiply">Multiply (×)</button>
<button type="submit" name="operation" value="divide">Divide (÷)</button>
<button type="submit" name="operation" value="remainder">Remainder (%)</button>
</div>
</form>
<p class="note">Remainder means what is left after division: 20 % 6 = 2.</p>
</main>
</body>
</html>
package com.edupoly.calculator;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class CalculatorController {
@GetMapping("/calculate")
public String calculate(
@RequestParam("a") double first,
@RequestParam("b") double second,
@RequestParam("operation") String operation,
Model model) {
if (!Double.isFinite(first) || !Double.isFinite(second)) {
model.addAttribute("error", "Please enter two finite numbers.");
return "result";
}
double answer;
String symbol;
switch (operation) {
case "add":
answer = first + second;
symbol = "+";
break;
case "subtract":
answer = first - second;
symbol = "-";
break;
case "multiply":
answer = first * second;
symbol = "×";
break;
case "divide":
if (second == 0) {
model.addAttribute("error", "Cannot divide by zero.");
return "result";
}
answer = first / second;
symbol = "÷";
break;
case "remainder":
if (second == 0) {
model.addAttribute("error", "Cannot calculate a remainder with zero as the divisor.");
return "result";
}
answer = first % second;
symbol = "%";
break;
default:
model.addAttribute("error", "Please choose one of the five operations.");
return "result";
}
if (!Double.isFinite(answer)) {
model.addAttribute("error", "The result is too large. Please use smaller numbers.");
return "result";
}
model.addAttribute("first", first);
model.addAttribute("second", second);
model.addAttribute("symbol", symbol);
model.addAttribute("answer", answer);
return "result";
}
}
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Calculation Result</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<main>
<h1>Calculation Result</h1>
<div th:if="${error == null}">
<p class="equation">
<span th:text="${first}">10</span>
<span th:text="${symbol}">+</span>
<span th:text="${second}">5</span>
=
<strong id="answer" th:text="${answer}">15</strong>
</p>
</div>
<p id="error" class="error" th:if="${error != null}" th:text="${error}"></p>
<a href="/">Calculate again</a>
</main>
</body>
</html>
The ZIP contains every setup file and a README.html with instructions. You can run the supplied project directly.
After Stage 7: the Spring words to recognize
Focus: Reuse the Model and template concepts from the product catalog.
| Term | Our concrete example | Its place in Spring |
|---|---|---|
| Model attributes — reused | The controller supplies first, second, symbol and answer to result.html. | The view receives named calculation values instead of direct response text. |
| Conditional rendering | th:if selects the answer section or the error message. | Thymeleaf includes the relevant HTML according to a condition. |
| th:text — reused | The answer appears where result.html uses th:text="${answer}". | Thymeleaf writes a supplied value as escaped HTML text. |
You should now be able to say: Java calculates; Model carries the values; the result template presents the answer or error.
One small check: Which line calculates the answer, which line adds it to Model, and which HTML attribute displays it?
Concepts used in this stage
Use the right column for a quick reminder, or open a concept above for its full explanation.