Keeping Spring Boot Powered REST API Request and Response Objects Minimal


I am a big fan of Spring Boot. It is my preferred choice for building REST APIs in Java. It takes less than 5 minutes(given that you have Maven dependencies on your local machine) to get started with Spring Boot thanks to its auto configuration. In this blog, I will talk about a specific aspect of building REST API with Spring Boot. We will look at the request and response objects. The request object that you receive during the HTTP POST request and response object that you send in the response of HTTP GET request. I will discuss how you can keep these request and response objects to the bare minimum so that we can avoid writing and maintaining useless getters and setters.

Let’s take a simple example of building a JSON REST API for a task management application. To keep things simple, this application will only have two endpoints as listed below:

  1. POST /api/tasks : This endpoint will be used to create a task when a HTTP POST request will be made.
  2. GET /api/tasks: This endpoint will list all the tasks stored in the application.

 

Let’s now build this API to learn how we can keep request and response objects simple and minimal. We will start by creating a new class TaskResource shown below.

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(path = "/api/tasks")
public class TaskResource {
}

In the code shown above:

  1. We annotated our resource class with @RestController annotation. This marks the resource class as a controller where every method returns a domain object instead of a view. This eliminates the need to use @ResponseBody annotation with each method.
  2. Next, we used @RequestMapping annotation that defines mapping between web requests and handler classes.

So far so good. Let’s now write the endpoint that will be used to create a task. If you have used Spring Boot this will be very natural to you. We will create a method and use @PostMapping as shown below.

import javax.validation.Valid;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(path = "/api/tasks")
public class TaskResource {

@PostMapping(path = "")
public ResponseEntity<Void> create(@RequestBody @Valid CreateTaskRequest request) {
return new ResponseEntity<>(HttpStatus.CREATED);
}

}

There is a lot going in here. In the code shown above:

  1. We created a method named create and used @PostMapping annotation with it. @PostMapping is a shorthand for @RequestMapping(method="post").
  2. The create method expects a request of type CreateTaskRequest. We marked with @RequestBody so that body of the web request will be mapped to the method parameter. We used JSR 303 bean validation annotation @Valid to enforce data validation.
  3. Finally, we created ResponseEntity object passing it HTTP status 201.

CreateTaskRequest is an empty class as shown below.

public class CreateTaskRequest {
}

Rather than testing this code manually, let’s write a unit test for this TaskResource class.

import com.google.gson.Gson;
import org.junit.Before;
import org.junit.Test;

import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

public class TaskResourceTests {

private Gson gson = new Gson();
private MockMvc mockMvc;

@Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(new TaskResource()).build();
}

@Test
public void should_create_a_task() throws Exception {
String jsonRequest = gson.toJson(new CreateTaskRequest());
mockMvc.perform(
post("/api/tasks")
.contentType(MediaType.APPLICATION_JSON).content(jsonRequest))
.andDo(print())
.andExpect(status().isCreated());
}
}

In the code shown above, we used MockMvcBuilders.standaloneSetup for unit testing our resource class. You can learn more about Spring MVC testing in the documentation.

When you will run the test case, it will succeed as expected. We just passed an empty JSON object and got HTTP status 201 in return.

Things become interesting when you start adding properties to the request object. Let’s suppose CreateTaskRequest has two properties title, description. One way could be to define these properties with their setters and getters as shown below.

import org.hibernate.validator.constraints.NotBlank;

public class CreateTaskRequest {

@NotBlank
private String title;
@NotBlank
private String description;

public String getTitle() {
return this.title;
}

public void setTitle(String title) {
this.title = title;
}

public String getDescription() {
return this.description;
}

public void setDescription(String description) {
this.description = description;
}

}

We also added bean validations. So, if you run the test now it will fail with HTTP 400 BadRequest error. We expect that user/test should provide title and description properties. Let’s fix the test case by creating a valid request object.

@Test
public void should_create_a_task() throws Exception {
CreateTaskRequest request = new CreateTaskRequest();
request.setTitle("Task 1");
request.setDescription("Task 1 description");
String jsonRequest = gson.toJson(request);
mockMvc.perform(
post("/api/tasks")
.contentType(MediaType.APPLICATION_JSON).content(jsonRequest))
.andDo(print())
.andExpect(status().isCreated());
}

After making the changes shown above, test will pass successfully.

But, setters suck. I don’t even like getters. I know you don’t have to write them yourself as your IDE can spit those for you but still you have to maintain them. Now, don’t tell me use Lombok I dislike it even more!

So, what’s the solution. Our best friend constructor. Let’s have parameterised constructor as shown below. We will also remove setters as shown below.

import org.hibernate.validator.constraints.NotBlank;

public class CreateTaskRequest {

@NotBlank
private String title;
@NotBlank
private String description;

public CreateTaskRequest(String title, String description) {
this.title = title;
this.description = description;
}

public String getTitle() {
return this.title;
}

public String getDescription() {
return this.description;
}
}

Make the test compile by passing data in the constructor. Once compiling, run the test case. This time test case will fail again with 400 BadRequest Error. The reason is that Jackson(the library Spring Boot uses underneath) fails to convert JSON to Java object. You have to help it by providing some metadata. This is done using @JsonCreator and @JsonProperty annotations.

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotBlank;

public class CreateTaskRequest {

@NotBlank
private String title;
@NotBlank
private String description;

@JsonCreator
public CreateTaskRequest(@JsonProperty("title") String title, @JsonProperty("description") String description) {
this.title = title;
this.description = description;
}
// .. getters remove for breviety.
}

Do we really need getters?

You can get rid of getters by making your fields public. You can also make fields final to make your object immutable as shown below.

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotBlank;

public class CreateTaskRequest {

@NotBlank
public String title;
@NotBlank
public String description;

@JsonCreator
public CreateTaskRequest(@JsonProperty("title") String title, @JsonProperty("description") String description) {
this.title = title;
this.description = description;
}

}

Now, we have minimal request object. I bet you would have seen unnecessary getters and setters every where. I find above more readable and maintainable.

What about methods that return JSON as response? They also can do the same. Below is list method that returns a list of tasks.

@GetMapping(path = "")
public ResponseEntity<List<TaskResponse>> list() {
return new ResponseEntity<>(
Arrays.asList(
new TaskResponse("Task 1", "Task 1 description"),
new TaskResponse("Task 2", "Task 2 description"),
new TaskResponse("Task 3", "Task 3 description")
),
HttpStatus.OK
);
}

The TaskResponse class is shown below.

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class TaskResponse {

public final String title;

public final String description;

@JsonCreator
public TaskResponse(@JsonProperty("title") String title, @JsonProperty("description") String description) {
this.title = title;
this.description = description;
}
}

I have been using this way of declaring request and response object for sometime now. I find it much more clean.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s