Skip to content

REST

Starter spring-boot-starter-web allows you to easily and quickly create a web application. It provides autoconfiguration for this layer and allows you to use objects that will help in applying the MVC (Model-View-Controller) architectural pattern.

The MVC pattern assumes that the entire application is divided into three separate layers:

  • model - represents objects that describe the problem to be solved
  • view - represents the layer that is responsible for displaying a part of the model based on the user's action
  • controller - a layer that accepts data and actions from the user and decides what view to display (and update the model based on it)

Spring Web, in addition to controllers that display the view using prepared HTML files, also allows you to create REST services (the so-called REST API). In other words, this launchpad allows you to create an application:

  • in which views (and thus the frontend layer) are embedded (MVC)

MVC-web

  • the results of which can be consumed by other applications (e.g. using Angular) REST-web

The application using spring-boot-starter-web starts the application by default using Tomcat and port 8080.

DispatcherServlet

Before we move on to showing how to define a controller, you need to understand what role does the so-called DispatcherServlet play. DispatcherServlet is an object that we do not deal with directly, but in the context of a Spring-based web application it has a very important task. This is called A front controller that accepts all incoming HTTP requests. Then, based on data such as:

  • the HTTP method is used when making an HTTP request (e.g. POST, PUT, GET, DELETE)
  • URL where the request was sent
  • request parameters
  • the type of object that comes in the body (body) of the HTTP request

filters the endpoints available in the application and selects the one that can handle such a request (ie delegates work).

dispatcher

Creation of REST services

In order to create a controller that is visible both in context and also visible to DispatcherServlet, you need to create a class that is annotated @Controller. Thanks to this, the bean created in this way will go to this part of the context about which it "knows" DispatcherServlet - WebApplicationContext.

Each controller can consist of many endpoints that we create by defining public methods inside this class which are marked with the annotation @RequestMapping. This annotation can be set using the field:

  • path - path to the endpoint
  • method - HTTP method handled by endpoint

ResponseEntity

REST services, based on some HTTP request, have to respond with an object consisting of status, headers and body. This object is represented by a generic type ResponseEntity, which specifies the type of object that is represented in the response body. We can create the ResponseEntity instance using the new operator or using builder, e.g .:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Message {
  private String text;
}
// creating a ResponseEntity with a constructor
new ResponseEntity<>(HttpStatus.NO_CONTENT); // ResponseEntity with the status 204

new ResponseEntity<>("Response Body", HttpStatus.CREATED); // response entity with body and status 201

new ResponseEntity<>("ResponseBody",
        new LinkedMultiValueMap<>(Map.of("some-header", List.of("header-value"))),
        HttpStatus.OK); // response entity with body, headers and status 200

// creating a ResponseEntity with a constructor
ResponseEntity.ok();

ResponseEntity.badRequest().body("Something went bad");

ResponseEntity.status(HttpStatus.CREATED)
        .contentType(MediaType.APPLICATION_JSON)
        .body(new Message("helloMsgAsJson"));

The example below shows how to create a simple controller with two endpoints. One of them is available at the path / api/hello and returns a Message object with a fixed value (JSON by default). The second is available under the /api/hi URI. It returns a String with the texthi:

import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class HelloWorldController {

  @RequestMapping(method = RequestMethod.GET, path = "/api/hello")
  public ResponseEntity<Message> sayHello() {
    return ResponseEntity.ok().body(new Message("Hello World"));
  }

  @RequestMapping(method = RequestMethod.GET, path = "/api/hi")
  public ResponseEntity<String> sayHi() {
    return ResponseEntity.ok().body("hi");
  }
}

As both of the above endpoints require the GET operation, we can check their result in the browser by typing in the address bar:

  • http://localhost:8080/api/hello
  • http://localhost:8080/api/hi

NOTE: The port the application is running on may be different.

NOTE: When testing REST services, it is better to use tools such as Postman instead of the browser, especially since the browser will not allow you to easily send requests using the PUT orDELETE.

@ResponseBody annotation

When creating endpoints, we rarely set additional response headers at their level. Usually, we perform some operation and return nothing in the response body (and we use the HTTP 204 status) or we return the created or downloaded object (and we usually return the 200 or 201 status). Therefore, instead of always returning a ResponseEntity object, it would be better to only return the object that should be wrapped in the response body (e.g. as a JSON object) or nothing (void) in case we want to return an empty body.

This possibility is offered by the use of the @ResponseBody annotation. Using it over the endpoint definition saves us from having to return the ResponseEntity object. Instead, from the method representing the endpoint, we return the object that is to go to the response body, e.g .:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HelloWorldController {

  @ResponseBody
  @RequestMapping(method = RequestMethod.GET, path = "/api/hello")
  public Message sayHello() {
    return new Message("Hello World");
  }

  @ResponseBody
  @RequestMapping(method = RequestMethod.GET, path = "/api/hi")
  public String sayHi() {
    return "hi";
  }

  @ResponseBody
  @RequestMapping(method = RequestMethod.GET, path = "/api/nothing")
  public void doNothing() {
  }
}

The @ResponseBody annotation looks for a suitable converter based on the HTTPAccept header. By default, the response will be converted to JSON.

Annotation @PathVariable

REST services often allow you to download a collection of certain objects or an object with a specific identifier. This identifier is most often part of the URL used when sending the request. It is a variable part of the path. We define such a part:

  • by wrapping part of it (and any name of your choice) inside { and } characters, e.g. {id}
  • by mapping the variable part of the path to the method argument with the annotation @PathVariable, in which we indicate the name used in the path

The next example shows how to map a variable part of a path to a variable:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HelloWorldController {

  @ResponseBody
  @RequestMapping(method = RequestMethod.GET, path = "/api/hello/{id}")
  public Message sayHelloWithId(@PathVariable(name = "id") final Integer id) {
    return new Message("Hello World " + id);
  }
}

By sending the following request:

curl --location --request GET 'http://localhost:8080/api/hello/1'

in response we get the following body:

{
    "text": "Hello World 1"
}

When using the @PathVariable annotation, remember that:

  • the variable type can be adjusted to the needs (we can use e.g. Integer,Long, String or UUID - the conversion will take place automatically)
  • the name field is optional when the name used in the path is equal to the name of the method argument (i.e. in the previous example, we could remove this field, because both the method argument and the variable part of the path are namedid)

Annotation @RequestBody

Some endpoints may expect some object in the request sent. This object is often sent as JSON or XML. Similar to using the @ResponseBody annotation, we can use one of the configured converters, to get an object created in Java. For this purpose, we use the @RequestBody annotation next to the method argument that represents the request body.

The following example shows the use of this annotation, and also shows that it can be used in tandem with [@PathVariable] (# pathvariable-annotation

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@Controller
public class MessageController {

  private final List<Message> messages = new ArrayList<>();

  @ResponseBody
  @RequestMapping(value = "/api/messages", method = RequestMethod.POST)
  public void createMessage(@RequestBody final Message message) {
    messages.add(message);
  }

  @ResponseBody
  @RequestMapping(value = "/api/messages/{index}", method = RequestMethod.PUT)
  public void updateMessage(@RequestBody final Message message, @PathVariable final Integer index) {
    messages.get(index).setText(message.getText());
  }
}

The above controller can be tested by sending the following requests:

curl --location --request POST 'http://localhost:8080/api/messages' \
--header 'Content-Type: application/json' \
--data-raw '{
    "text": "hello"
}'
curl --location --request PUT 'http://localhost:8080/api/messages/1' \
--header 'Content-Type: application/json' \
--data-raw '{
    "text": "new-hello"
}'

Set up response statuses

When creating a certain endpoint, the default status is 200 (or 500 in case of exception). While performing some operations, we want this status to have a different value, e.g.:

  • 201 (CREATED) when we have successfully created the object
  • 204 (NO_CONTENT) when the action was successfully performed but no body was returned

We can set the response status in two ways:

  • by setting it to the ResponseEntity build level
  • by setting it using the @ResponseStatus annotation (over the method representing the endpoint)

The next example shows the use of the @ResponseStatus annotation:

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@Controller
public class MessageController {

  private final List<Message> messages = new ArrayList<>();

  @ResponseStatus(HttpStatus.CREATED)
  @ResponseBody
  @RequestMapping(value = "/api/messages", method = RequestMethod.POST)
  public void createMessage(@RequestBody final Message message) {
    messages.add(message);
  }

  @ResponseStatus(HttpStatus.NO_CONTENT)
  @ResponseBody
  @RequestMapping(value = "/api/messages/{index}", method = RequestMethod.PUT)
  public void updateMessage(@RequestBody final Message message, @PathVariable final Integer index) {
    messages.get(index).setText(message.getText());
  }
}

NOTE: Using the @ResponseStatus annotation, it is better to return the object that should be wrapped in the response body from the method representing the endpoint than theResponseEntity object.

Annotation @RequestParam

Requests can also have required (or optional) parameters. These parameters are at the end of the URL, separated by the & character, and the key and value are separated by the = character, e.g .: http://localhost:8080/api/resources?param1=val1&param2=val2

In order to retrieve the values of these parameters into the method arguments, we use the @RequestParam annotation. Like the @PathVariable annotation, it allows you to define the name of a parameter, whether it is required, and it places responsibility for matching the value to the type to the programmer.

The example shows the use of the @RequestParam annotation, from which a response can be obtained via the following URL:

http://localhost:8080/api/messages/search?param1=7&param2=eff3168f-974d-4a37-888f-f96a08658525

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.UUID;

@Controller
public class MessageController {

  @RequestMapping(method = RequestMethod.GET, path = "/api/messages/search")
  @ResponseBody
  @ResponseStatus(HttpStatus.OK)
  public void searchMessages(@RequestParam(name = "param1") final Integer param1, @RequestParam(name = "param2", required = false) final UUID param2) {
    // code responsible for search
  }
}

Shortened notations

All of the above examples were created using basic annotations. However, Spring offers to use some class-level operations instead of a method. Some of the annotations, in turn, can be used on the class and methods simultaneously.

Instead of using the @ResponseBody annotation on all methods representing endpoints, it can only be used over the class, i.e. instead of defining endpoints as follows:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HelloWorldController {

  @ResponseBody
  @RequestMapping(method = RequestMethod.GET, path = "/api/hello")
  public Message sayHello() {
    return new Message("Hello World");
  }

  @ResponseBody
  @RequestMapping(method = RequestMethod.GET, path = "/api/hi")
  public String sayHi() {
    return "hi";
  }
}

we can define them as below:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@ResponseBody
public class HelloWorldController {

  @RequestMapping(method = RequestMethod.GET, path = "/api/hello")
  public Message sayHello() {
    return new Message("Hello World");
  }

  @RequestMapping(method = RequestMethod.GET, path = "/api/hi")
  public String sayHi() {
    return "hi";
  }
}

Moreover, REST services very often use a combination of the @Controller annotation together with@ResponseBody over the class definition. In this case, it is better to use @RestController, which is a combination of the two, e.g .:

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

@RestController
public class HelloWorldController {

  @RequestMapping(method = RequestMethod.GET, path = "/api/hello")
  public Message sayHello() {
    return new Message("Hello World");
  }

  @RequestMapping(method = RequestMethod.GET, path = "/api/hi")
  public String sayHi() {
    return "hi";
  }

}

The @RequestMapping annotation allows you to define an endpoint available under many HTTP methods. Most often, however, the value of the method field in the@RequestMapping annotation has a single value. In such a case, it is worth replacing its use with one of the annotations where this field does not need to be entered, and whose value results from the name of the annotation, i.e .:

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @PatchMapping
  • @DeleteMapping

Hence the following annotation:

@RequestMapping(method = RequestMethod.GET, path = "/api/hello")

można zastąpić:

@GetMapping("/api/hello")

or similarly the following annotations:

@RequestMapping(value = "/api/messages", method = RequestMethod.POST)
@RequestMapping(value = "/api/messages/{index}", method = RequestMethod.PUT)
@RequestMapping(value = "/api/messages/{index}", method = RequestMethod.DELETE)

can be simplified accordingly:

@PostMapping("/api/messages")
@PutMapping("/api/messages/{index}")
@DeleteMapping("/api/messages/{index}")

Furthermore, if multiple endpoints in a class are available under a path that has a common origin, they can be defined directly above the class (using the @RequestMapping annotation), and a unique part of the endpoint path directly above the method (also using the@RequestMapping annotation) ), e.g .:

import org.springframework.web.bind.annotation.*;

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

  @GetMapping("/hello") // endpoint available under /api/hello
  public Message sayHello() {
    return new Message("Hello World");
  }

  @GetMapping("/hi") // endpoint available under /api/hi
  public String sayHi() {
    return "hi";
  }
}