Stage 4: read numbers from the URL path

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.

Run this project

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.

Try 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

Complete files

pom.xml

<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>

src/main/java/com/edupoly/hello/HelloApplication.java

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);
    }
}

src/main/resources/application.properties

# Change this number if the port is already in use.
server.port=8081

src/main/resources/static/aboutus.html

<!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>

src/main/resources/static/index.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>

src/main/java/com/edupoly/hello/PageController.java

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";
    }
}

src/main/java/com/edupoly/hello/CalculatorController.java

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;
    }
}

After Stage 4: the Spring words to recognize

Focus: Use an existing controller with a different location for its input values.

TermOur concrete exampleIts 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?

When ready: useful next stages after Stage 7

A useful continuation: planned next stages

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 stageOne useful changeWords to learn when we make that change
8. A separate product-file readerMove 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 operationsAdd ProductService for rules such as choosing products currently in stock.@Service, business rule and separation of responsibilities.
10. Product data accessPlace file-based reading and lookup behind a product repository.Repository pattern, @Repository and data access.
11. MongoDB storageReplace the text-file storage with MongoDB while keeping the page flow familiar.@Document, ID, MongoRepository, persistence and CRUD.
12. Explicit bean configurationSupply 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.