It's often the case in REST APIs that you need a URL that includes an ID or other variable. That's what makes the @PathVariable annotation in Spring Boot so valuable.

Sure, sometimes REST URLs include fixed paths, like /api/employees

Something like that might list all employees.

But what if you just want to grab the details of one employee? For that, you need to provide some info about the employee, such as his or her ID.

And so you end up with a URL that looks like this: /api/employee/3392

But how do you handle that in Spring Boot? Specifically that last part with the ID?

You might already know a thing or two about controllers in Spring Boot. You may even know that you can specify a full path with something like:

@GetMapping("/api/employees")
public ResponseEntity<List<Employee>> allEmployees() {
...

That's fine and dandy when the URL is always fixed. But what about in the case of the example above when the last part of the path isn't fixed?

If you're looking for an ID, for example, sometimes it could be 3392 while other times it might be 4322.

What's a developer to do?

Fortunately, Spring Boot has an answer for that. It's the @PathVariable annotation.

The @PathVariable Annotation

So what is the @PathVariable thing, anyway?

As the name implies, it lets you specify a variable as part of the URL path. It's a great choice for paths that include IDs. 

So how do you use it? For that, let's look at something resembling a real-world business application.

The Use Case

Your friendly boss Smithers walks into your office and says that he needs the CRM (Customer Relationship Management tool) that you're working on to include a REST endpoint that retrieves a single contact by his or her ID.

At first, you break out into a cold sweat. You're using Spring Boot to handle REST requests and you know that URL paths are typically fixed in Spring Boot.

Ah, but then you remember the @PathVariable annotation. You breathe a sigh of relief.

Smithers walks out of your office knowing you got this.

The Controller

You've already got the Spring Boot application in place. So there's no need to start from scratch. 

To implement your "retrieve a contact by ID" solution, you go straight to the controller layer instead. Here's what your controller class looks like:

@RestController
@RequestMapping("/contact")
public class ContactController {

	@Autowired
	private ContactService contactService;
		
	@GetMapping("/{id}")
	public ResponseEntity<Contact> contact(@PathVariable Integer id) {
		HttpStatus status = HttpStatus.OK;
		Contact contact = contactService.fetchContactById(id);
		
		if (contact == null) {
			status = HttpStatus.NOT_FOUND;
		}

		return ResponseEntity.status(status).body(contact);
	}	
}

 

There's not too much that's earth-shattering in that code. The most important thing to notice for the purposes of this guide is the contact() method.

It's annotated with @GetMapping and you probably already know what that means or you wouldn't be here.

Just in case you don't, though, it's telling Spring boot to execute this method in the event of a specific GET request. A GET request is a typical HTTP read-only request. You used a GET request to access this web page.

But what path is associated with the GET mapping? That's specified in the parentheses.

In this case, though, the path in the parentheses includes an id token in curly braces. So what's that all about?

Here's what that's all about: that's telling Spring Boot to treat the id as a variable and not a fixed part of the URL path.

In other words, we're not looking for the word "id" in the path here. Instead, we're looking for a variable that represents the id.

In this case, the ID is an integer. But it doesn't have to be.

Take a gander at the top of the class as well. You'll note two annotations.

The @RestController annotation tells Spring Boot that we're using this controller to respond to REST requests. It's not serving a web page.

The @RequestMapping annotation gives us the root of the URL path. In this case, it's /contact.

So if this Spring Boot application gets deployed to localhost and responds to the default port of 8080, the URL for retrieving an contact by ID will look like this:

http://localhost:8080/contact/342

Where 342 is the actual contact ID.

Accessing the Path Variable

So how do you, a professional web developer, access that path variable (or ID) in your code?

Take a look at the signature in the contact() method. You'll see this: @PathVariable Integer id

That's associating the id variable with the {id} path variable from the annotation.

Voila! Now you have access to whatever ID the consumer of your REST API put in the path.

If we stick with the sample URL above, the id variable in the method signature will be set to 342. 

And yeah, it does the conversion from String to Integer on your behalf. What's not to love about that?

Once you're armed with that ID, all that's up to you is to access your repository or service to retrieve the contact info with the ID.

The Contact Model

Normally, any self-respecting CRM would include a contact model loaded up with info about the person. To make things simple for this guide, though, we're just going to use a flyweight model.

Here's what it looks like:

public class Contact {

	private Integer id;
	private String firstName;
	private String lastName;
	private boolean authority;
	private String email;
	private Integer salesOwnerId = 1;

	
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getFirstName() {
		return firstName;
	}
	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}
	public String getLastName() {
		return lastName;
	}
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	public boolean isAuthority() {
		return authority;
	}
	public void setAuthority(boolean authority) {
		this.authority = authority;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public Integer getSalesOwnerId() {
		return salesOwnerId;
	}
	public void setSalesOwnerId(Integer salesOwnerId) {
		this.salesOwnerId = salesOwnerId;
	}
}

 

For the purposes of this guide, key in on the id field. That's the field we'll use to grab the correct contact based on the ID provided in the URL.

The model also includes other relevant info about the contact, such as the person's first name, last name, and email address.

The Service Layer

As you may have noticed in the section covering the controller class, the code relies on an @Autowired service. It's called ContactService.

Now, normally that class would function as a gateway to a backend database. But for the purposes of this guide, we're just mocking everything up.

Here's what the code looks like:

@Service
public class ContactService {

	public Contact fetchContactById(Integer id) {
		switch (id) {
			case 1:
				return fetchJimmyBuffet();
			case 2:
				return fetchDarthVader();
			default:
				return null;
		}
	}
	
	
	private Contact fetchJimmyBuffet() {
		Contact contact = new Contact();
		
		contact.setId(1);
		contact.setAuthority(true);
		contact.setEmail("jimmy.buffet@xmail.com");
		contact.setFirstName("James");
		contact.setLastName("Buffet");
		
		return contact;
	}

	
	private Contact fetchDarthVader() {
		Contact contact = new Contact();
		
		contact.setId(2);
		contact.setAuthority(true);
		contact.setEmail("darth.vader@xmail.com");
		contact.setFirstName("Darth");
		contact.setLastName("Vader");
		
		return contact;
	}
}

 

As you can see, we've only got two contacts so far (hey, you gotta start somewhere).

The public method at the top just reads the ID and retrieves the contact associated with that ID. Nothing fancy.

Now, let's revisit the controller.

The Controller, Part Deux

Let's take a look at that controller class again:

@RestController
@RequestMapping("/contact")
public class ContactController {

	@Autowired
	private ContactService contactService;
	
	
	@GetMapping("/{id}")
	public ResponseEntity<Contact> contact(@PathVariable Integer id) {
		HttpStatus status = HttpStatus.OK;
		Contact contact = contactService.fetchContactById(id);
		
		if (contact == null) {
			status = HttpStatus.NOT_FOUND;
		}

		return ResponseEntity.status(status).body(contact);
	}	
}

 

So what is that contact() method doing?

For starters, it's defaulting the HTTP status to OK. Hey, we're optimistic.

And why do we need to set the HTTP status, anyway? Well if you look at the object that the method returns, it's a ResponseEntity.

ResponseEntity object not only includes the object we want to send back to the consumer, but also the status code as well. You can also use it to set headers, but we're not getting into that here.

After setting the HTTP status, the code uses the service to fetch the contact by the ID provided in the URL.

Now, of course the possibility exists that the consumer supplied an ID that isn't valid. That's why the code checks to see if the contact is null in the next line.

If the contact is null, that means the service didn't find anyone matching the ID. In that case, the service will return a NOT FOUND (404) response with a null body.

The last line in the method constructs and returns the ResponseEntity object.

Testing It Out

Let's deploy our app and give the girl a whirl.

If you're running Eclispe, you just need to right-click on the Spring Boot application class and select Run As... from the context menu that appears. Then, select Java Application from the pop-out menu that appears.

 

Just like that up there.

Once you've got the app running, just head over to a browser and enter the following URL:

http://localhost:8080/contact/1

Again, that assumes you've deployed it to listen on port 8080. If not, adjust the URL accordingly.

As you can see, the URL hits the /contact path segment. Remember: that's mapped at the top of the controller class with the @RequestMapping annotation.

The following path segment is a single digit: the number 1. 

That means we're looking for the contact with ID 1.

So if we visit that URL with our browser, we should get something like this back:

{"id":1,"firstName":"James","lastName":"Buffet","authority":true,"email":"jimmy.buffet@xmail.com","salesOwnerId":1}

 

And that's exactly right because Jimmy Buffet's ID is 1.

Wrapping It Up

So there you have it. A quick primer on @PathVariable with Spring Boot.

Feel free to check out the full code on GitHub. It should work in Eclipse or Spring Tool Suite right out of the box.

Also, I've added some extra code in there to show you how to work with multiple path variables. Take a look at that as well.

Have fun!