Keep the query-parameter example and add a second method. This time the numbers appear as path sections.
In /add/{a}/{b}, the braces mark values captured from the path. @PathVariable("a") supplies the captured value to first. @PathVariable("b") supplies second. Java performs the addition. Both methods can exist together because /add and /add/{a}/{b} are different mappings.
Requires JDK 21+ and Maven 3.9+. Extract the ZIP and open a terminal in spring-path-variables, where pom.xml is located. First check java -version and mvn -version. Internet is needed for the first dependency download.
Set an available port in src/main/resources/application.properties. The default is 8081. Stop the previous stage with Ctrl+C before starting this one, or choose another port.
mvn spring-boot:run
Open http://localhost:8081/ in your browser. Replace the port if you changed it.
Open /add/10/20 and expect 30. Open /add/7/5 and expect 12. Compare with /add?a=7&b=5: the input location changes, but the arithmetic is the same.
GET /add/10/20 -> addUsingPath(10, 20) -> return 30 -> browser displays 30
<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-path-variables</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.hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class HelloApplication {
public static void main(String[] args) {
SpringApplication.run(HelloApplication.class, args);
}
}
# Change this number if the port is already in use.
server.port=8081
<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><title>About Us</title></head>
<body>
<h1>About Us</h1>
<p>This is our first Spring website.</p>
<a href="/">Home</a>
</body>
</html>
<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><title>Home</title></head>
<body>
<h1>Hello World</h1>
<a href="/aboutus.html">About Us</a>
</body>
</html>
package com.edupoly.hello;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class PageController {
@GetMapping("/aboutus")
public String aboutUs() {
System.out.println("Someone requested the About Us page");
return "forward:/aboutus.html";
}
}
package com.edupoly.hello;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class CalculatorController {
@GetMapping("/add")
@ResponseBody
public int add(@RequestParam("a") int first,
@RequestParam("b") int second) {
return first + second;
}
@GetMapping("/add/{a}/{b}")
@ResponseBody
public int addUsingPath(@PathVariable("a") int first,
@PathVariable("b") int second) {
return first + second;
}
}
Focus: Use an existing controller with a different location for its input values.
| Term | Our concrete example | Its place in Spring |
|---|---|---|
| @PathVariable | /add/10/20 supplies 10 and 20 through {a} and {b}. | Spring MVC binds captured URL path values to method arguments. |
| Request mapping — reused | /add and /add/{a}/{b} select different methods. | Spring MVC chooses a handler using the configured path and HTTP method. |
You should now be able to say: The arithmetic is unchanged; @PathVariable reads numbers from the URL path.
One small check: In /add/7/5, which value goes into first and which goes into second?
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.