Enter two numbers and click Add, Subtract, Multiply, Divide or Remainder. Java calculates the answer and sends it directly to the browser as text.
Requires JDK 21+ and Maven 3.9+. Extract the ZIP and open a terminal in spring-calculator-basic, beside pom.xml. Internet is needed for the first dependency download.
Choose a free port in src/main/resources/application.properties. The default is:
server.port=8084
mvn spring-boot:run
Wait for startup, then open http://localhost:8084/. Use your chosen port if different. Keep the terminal open; stop the app with Ctrl+C.
Enter 10 and 5 and click Add. The browser navigates to /calculate?a=10&b=5&operation=add and displays:
10.0 + 5.0 = 15.0
Use the browser Back button to return to the form. Other results for 10 and 5 are 5.0, 50.0, 2.0 and 0.0. Remainder (%) is not percentage. Division and remainder with a zero divisor show a useful message.
Form: enter 10 and 5, click Add -> GET /calculate?a=10&b=5&operation=add -> @RequestParam reads the three values -> Java calculates 15.0 -> @ResponseBody sends "10.0 + 5.0 = 15.0" -> browser displays the answer as text on the next page
name="a" and name="b" set the parameter names. Each submit button has name="operation" and its own value. Only the clicked button submits that value. GET places these values in the URL.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Arithmetic Calculator — Stage 5</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>The answer opens as text on the next page. Use the browser Back button to calculate again.</p>
<p class="note">Remainder means what is left after division: 20 % 6 = 2.</p>
</main>
</body>
</html>
@GetMapping connects /calculate to our Java method. @RequestParam reads the numbers and operation. The switch chooses the calculation. @ResponseBody sends the returned String directly as the response. There is no Model or Thymeleaf in this stage.
package com.edupoly.calculator;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class CalculatorController {
@GetMapping("/calculate")
@ResponseBody
public String calculate(
@RequestParam("a") double first,
@RequestParam("b") double second,
@RequestParam("operation") String operation) {
if (!Double.isFinite(first) || !Double.isFinite(second)) {
return "Please enter two finite numbers.";
}
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) {
return "Cannot divide by zero.";
}
answer = first / second;
symbol = "÷";
break;
case "remainder":
if (second == 0) {
return "Cannot calculate a remainder with zero as the divisor.";
}
answer = first % second;
symbol = "%";
break;
default:
return "Please choose one of the five operations.";
}
if (!Double.isFinite(answer)) {
return "The result is too large. Please use smaller numbers.";
}
return first + " " + symbol + " " + second + " = " + answer;
}
}
Missing or nonnumeric required parameters sent directly to /calculate produce HTTP 400. The HTML fields use browser validation. This example uses double, so decimal rounding artifacts can occur.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.1</version>
<relativePath/>
</parent>
<groupId>com.edupoly</groupId>
<artifactId>spring-calculator-basic</artifactId>
<version>1.0.0</version>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
package com.edupoly.calculator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CalculatorApplication {
public static void main(String[] args) {
SpringApplication.run(CalculatorApplication.class, args);
}
}
# Choose another available port if 8084 is busy.
server.port=8084
body { margin: 0; padding: 32px 16px; font-family: system-ui, sans-serif; color: #17253d; background: #f5f7fb; }
main { max-width: 620px; margin: 32px auto; padding: 28px; background: white; border: 1px solid #dbe2ec; border-radius: 12px; }
h1 { margin-top: 0; font-size: 28px; }
p { line-height: 1.6; }
label { display: block; margin: 18px 0 6px; font-weight: 600; }
input { box-sizing: border-box; width: 100%; padding: 12px; font-size: 18px; border: 1px solid #8796ac; border-radius: 5px; }
.buttons { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 22px; }
button { padding: 10px 14px; background: #2457a6; border: 0; border-radius: 5px; color: white; font-size: 15px; cursor: pointer; }
button:hover { background: #193f7b; }
a { color: #2457a6; }
.note { font-size: 14px; color: #536075; }
.equation { font-size: 28px; overflow-wrap: anywhere; }
.error { color: #a52323; }
Stage 6 introduces Model and Thymeleaf using a product catalog with a table and details page. Stage 7 then uses those concepts for this calculator. First understand this direct response version.
Open all stages in the online tutorial.
Focus: Let the HTML form build the request we previously typed ourselves.
| Term | Our concrete example | Its place in Spring |
|---|---|---|
| Form parameters | The fields named a and b supply the numbers to /calculate. | The browser creates a GET query string; Spring MVC reads it using @RequestParam. |
| Clicked button value | Clicking Add submits operation=add. | The controller receives the button value as a request parameter and chooses the calculation. |
| @ResponseBody — reused | The browser displays the controller's returned text on the next page. | Spring MVC sends the response directly; no result template is selected. |
You should now be able to say: The form sends numbers and an operation; Java calculates and returns a text response.
One small check: Which part of the request tells the controller whether to add or multiply?
These are the next learning steps, not additional projects already implemented in this bundle. Each proposed change gives the new terminology a concrete example.
| Next stage | One useful change | Words to learn when we make that change |
|---|---|---|
| 8. A separate product-file reader | Move the existing file-reading code into ProductFileReader and have Spring supply that object to ProductController through its constructor. Print object creation and request messages to observe reuse. | @Component, dependency, constructor injection, DI, IoC and default singleton scope. |
| 9. Product operations | Add ProductService for rules such as choosing products currently in stock. | @Service, business rule and separation of responsibilities. |
| 10. Product data access | Place file-based reading and lookup behind a product repository. | Repository pattern, @Repository and data access. |
| 11. MongoDB storage | Replace the text-file storage with MongoDB while keeping the page flow familiar. | @Document, ID, MongoRepository, persistence and CRUD. |
| 12. Explicit bean configuration | Supply a helper through a configured factory method and inspect how it is registered. | @Configuration, @Bean, factory method, bean definition and registry. |
Until Stage 8 is implemented, our examples do not demonstrate Spring injecting a separate ProductFileReader into ProductController. The current product controller still reads the file itself. Keep the original getInputStream() and try-with-resources code for that step.