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:
- POST
/api/tasks
: This endpoint will be used to create a task when a HTTP POST request will be made. - 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:
- 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. - 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:
- We created a method named
create
and used@PostMapping
annotation with it. @PostMapping is a shorthand for@RequestMapping(method="post")
. - The
create
method expects a request of typeCreateTaskRequest
. 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. - 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.
I see you have example for Get List of tasks and create one task, Can you also add an example to get single TaskRequest object.