Spring Boot Deep Dive — RestController (Part3)

Ragunath Rajasekaran
7 min readJul 24, 2019

--

This tutorial explains more about RestController Annotation and how we can configure different HTTP method REST calls from Spring.

This is a continuation of my previous posts where we had created a brand new Spring Boot application using Spring.io and talked about Annotations. (Recommending to checkout the previous one since this article is a continuation of it.)

Execution Plan

In our journey, we will re iterate our implementation based on the need and reusability. But why? On each iteration, we will understand Spring and its way of handling the configurations for web services. In short, we will do the following while creating endpoints to manage Account.

  1. Providing mock/temporary data : we can validate whether appropriate Java code gets executed when client calls endpoints.
  2. Providing In-Memory Source of truth : we can understand that without having Database we can write services.
  3. Using Service Layer In-Memory Source of truth : we can understand what code needs to be in the RestControllers. How we can segregate business logic from RestController. This Separation is important to decouple RestController from other responsibilities.
  4. Using Database as a Source of truth : we can understand how to change datasource in Spring. Understanding key concept of Java Persistence API (JPA) and how database tables have been mapped to Java classes.

What are we going to cover in this tutorial?

Covering all the above mentioned in a single tutorial will be too much to take in at a time, hence let’s cover only the first execution/iteration in this tutorial. In the up coming tutorials we will cover the remaining.

Model

Before we jump into the above approaches, let’s define an Account model and it will have a following attributes

  • id : Unique Identifier for an account
  • title : Title for an account
  • description : Description for an account
public class Account {
private Long id;
private String title;
private String description;
//...Getters & Setters
}

Create getters & setters for Account class properties.

Creating Instances

Instances can be created using new keyword but here we are instantiating classes using one of the design pattern called Builder. We can still instantiate and manipulate properties without builder pattern. But using Builder pattern, we can chain and instantiate in a linear way.

Creating an Account instance, we can call default constructor and set properties using Setter methods.

  • Creating instances using new
Account account = new Account();
account.setId(1L);
account.setTitle("Wallet");
account.setDescription("Wallet Account");
  • Define new constructor on Account class and passing all parameters as inputs.
public Account(Long id, String title, String description) {
this.id = id;
this.title = title;
this.description = description;
}

Instantiate using the constructor like below.

Account account = new Account(1L, "Wallet", "Wallet Description");
  • Define AccountBuilder class.
public final class AccountBuilder {
private Long id;
private String title;
private String description;
private AccountBuilder() {
}
public static AccountBuilder anAccount() {
return new AccountBuilder();
}
public AccountBuilder withId(Long id) {
this.id = id;
return this;
}
public AccountBuilder withTitle(String title) {
this.title = title;
return this;
}
public AccountBuilder withDescription(String description) {
this.description = description;
return this;
}
public Account build() {
Account account = new Account();
account.setId(id);
account.setTitle(title);
account.setDescription(description);
return account;
}
}

Instantiate the Account using AccountBuilder like below,

Account account = AccountBuilder
.anAccount()
.withId(1L)
.withTitle("Savings")
.withDescription("Savings Account")
.build();

Using builder, we are chaining object construction process. We can use either of them for instantiation but here we are going to use Builder Pattern.

Created both the classes (Account, AccountBuilder) under models package.

Execution 1 : Providing mock/temporary data

Let’s start our first approach for Accounts RestController. Here we are going to hard code (As we planned earlier) responses on each endpoint which helps to concentrate more on Annotation configuration for each endpoints, instead of moving our attention towards implementing business logic.

  • Create new class AccountRestController and annotate the class with RestController so that Spring will route client request to this class
  • Create a new method accounts to return all accounts

annotate it with GetMapping(“/accounts”) so that Spring route /accounts endpoint directly to this method

return type of the method is to be the ResponseEntity (Which we have talked in my previous post ) by having list of accounts as body

@RestController
public class AccountController {
@GetMapping(value = "/accounts")
private ResponseEntity<List<Account>> accounts() {
List<Account> accounts = Stream
.of(AccountBuilder
.anAccount()
.withId(1L)
.withTitle("Savings")
.withDescription("Savings Account")
.build())
.collect(Collectors.toList());
return ResponseEntity
.ok()
.body(accounts);
}
}

GetMapping

GetMapping is the Annotation for mapping HTTP GET requests onto specific handler methods. Let’s see how this annotation is being configured with other annotations.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping

As we have seen in previous post, It will be applicable on method level Target(ElementType.METHOD) . We did so by applying it on accounts method.

RequestMapping annotation is for mapping web requests onto methods in request-handling classes(AccountController) with flexible method signatures. So this annotation is responsible to map requests with appropriate method. Here, /accounts endpoint will be mapped to AccountController’s accounts method.

When Servlets dispatched /accounts GET endpoint is being mapped with the AccountController class accounts() method. See in the above screenshot, GET is being applied as a HTTP method because we used GetMapping. In a same way, Spring offers annotation for PostMapping, PutMapping, DeleteMapping & PatchMapping and all of them uses RequestMapping and configure appropriate HTTP methods.

Return type is ResponseEntity of list of accounts. We have used Stream.of() to create Java stream of collection and collected them as a list(Collectors.toList()) from collect() method.

List<Account> accounts = Stream
.of(AccountBuilder
.anAccount()
.withId(1L)
.withTitle("Savings")
.withDescription("Savings Account")
.build())
.collect(Collectors.toList());

Here we are using Java streams to construct Accounts list.

Let’s run the app and validate the same in PostMan Console.

The same approach will be applicable for remaining endpoints also. See the steps,

  1. Create a method
  2. Annotate with appropriate HTTP method annotations (GetMapping, PostMapping, PutMapping, DeleteMapping & PatchMapping)
  3. Return ResponseEntity and embed resulted object within its body
  4. Write Java code inside to perform Create, Retrieve, Update & Delete of accounts

Let’s try this to retrieve an account based by Id.

  • Create a new method accountById to return an account for an AccountID

annotate it with GetMapping(“/accounts/{account-id}”) so that Spring route /accounts/{account-id} endpoint directly to this method

return type of the method to be ResponseEntity by having an Account as body

Here we are not searching the account information and returning the result. Temporarily (As we planned earlier), we have instantiated new instance and returned.

@RestController
public class AccountController {
@GetMapping(value = "/accounts/{accountId}")
private ResponseEntity<Account> accountById(@PathVariable Long accountId) {
return ResponseEntity
.ok()
.body(AccountBuilder
.anAccount()
.withId(accountId)
.withTitle("Savings")
.withDescription("Savings Account")
.build());
}
}

PathVariable annotations helps to extract accountId from Http Request URL.

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable

See, PathVariable can only be applied on parameter level so used in method parameter to extract accountId.

Here we have,

  1. Created a new Account
  2. Assigned the accountId which is being received as a request parameter (extracted using PathVariable annotation)
  3. Returned the ResponseEntity by embedding the account (created in step 1) as body
AccountBuilder
.anAccount()
.withId(accountId)
.withTitle("Savings")
.withDescription("Savings Account")
.build()

Let’s run the app and validate the same in PostMan Console.

/accounts POST endpoint (To Create A new Account)

@PostMapping(value = "/accounts")
private ResponseEntity<Account> addAccount(@RequestBody Account account) {
return ResponseEntity
.ok().body(account);
}

RequestBody Annotation is indicating a method parameter should be bound to the body of the web request. HTTP post body is bound to Java class Account. HttpMessageConverter class is responsible to convert body as Account class.

Here we are hardcoding response as what we are receiving as http post body.

@PostMapping(value = "/accounts")
private ResponseEntity<Account> addAccount(@RequestBody Account account) {
return ResponseEntity
.ok().body(account);
}
@PutMapping(value = "/accounts/{accountId}")
private ResponseEntity<Account> updateAccount(@RequestBody Account account, @PathVariable Long accountId) {
return ResponseEntity
.ok().body(account);
}
@DeleteMapping(value = "/accounts/{accountId}")
private ResponseEntity deleteAccount(@PathVariable Long accountId) {
return ResponseEntity
.ok()
.build();
}

/accounts/{accountId} PUT endpoint (To Update An Account)

@PutMapping(value = "/accounts/{accountId}")
private ResponseEntity<Account> updateAccount(@RequestBody Account account, @PathVariable Long accountId) {
return ResponseEntity
.ok().body(account);
}
  • Used PutMapping annotation to configure HTTP PUT /accounts/{accountId} endpoint
  • RequestBody annotation to parse request and convert it into Account model
  • PathVariable annotation to get value of accountId value from endpoint
  • Return type is ResponseEntity of Account

/accounts/{accountId} Delete endpoint (To Delete an Account)

  • Used DeleteMapping annotation to configure HTTP DELETE /accounts/{accountId} endpoint
  • PathVariable annotation to get value of accountId value from endpoint
  • Return type is ResponseEntity

See all endpoints in PostMan

What we did so far,

  • We have completed our first iteration of RestController implementation with hardcoded responses.
  • Now we are able to write web services in Spring and are able to understand few of the annotations.
  • The approach of seeing implementation of annotation in Spring would help us to understand each annotation in detail for future implementation.
  • If you want to know about any annotation’s purpose, you can open its implementations to understand the usability on your own.
  • Now you are able to use PostMan (other sort of tools) to validate services and see in action. Have saved endpoints in PostMan and see below our endpoints.

Please checkout the continuation of this tutorial.

--

--

Ragunath Rajasekaran

Sr. Tech Lead | Spring Boot | Big Data | AWS | Terraform | Spark | React | Docker