DEV Community

Bruno Oliveira
Bruno Oliveira

Posted on

Java - Using Spring to create a REST API

Introduction

Spring is one of the most popular frameworks for Java web development.
Extensions of it, namely SpringBoot and Spring Initializr plug-in for IntelliJ, have become one of industry's standards for developing web applications with Spring.
Within web applications, a very common and modern trend is developing REST APIs. These simply use HTTP as the means of communication between front-end and back-end. Typically, the front-end sends an http request to obtain a resource and the endpoint returns a response. These responses usually contain a resource and a response code.
We'll see how to write a basic endpoint that consumes and produces JSON and how to write an integration test for such an endpoint.

The setup

For our setup, we will use the Spring initializr for IntelliJ (but you can use whatever you have available in your IDE or even set up some things manually).

For the purpose of this simple post, let's assume that we want to send a JSON with the following structure in the body of a POST request:

{"name":"John", "age":12}

and as a response we want to return the following JSON:

{"greet":"Hi, John", "date":<return date of request>}

When working with REST APIs, it's usually necessary to have a few things in place:

  • DTOs (Data Transfer Objects): for both the request and the response, we need to be able to have objects in place that represent our request and response so that we can build it on the Java side. These objects will be used to do further data processing on the request and to build a response.

  • Use an ObjectMapper, like Jackson, to de/serialize from and to JSON.

REST APIs should accept JSON for request payload and also send responses to JSON. JSON is the standard for transferring data. Almost every networked technology can use it: JavaScript has built-in methods to encode and decode JSON either through the Fetch API or another HTTP client. Server-side technologies have libraries that can decode JSON without doing much work.

  • It's also good practice to have a "service class" to which we delegate the actual business logic, as the only

With these in place, we can then prepare our endpoint. Let's set them up.

Preparing the data transfer classes

For the request, let's say we have a PersonDTO class:

public class PersonDTO {
    private String name;
    private int age;

    //Needed for Jackson
    public PersonDTO() {
    }

    public PersonDTO(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

And, for the response, we will have a GreetDTO:

public class GreetDTO {
    private String greet;
    private Date date;

    //Needed for Jackson
    public GreetDTO() {
    }

    public GreetDTO(String greet, Date date) {
        this.greet = greet;
        this.date = date;
    }

    public String getGreet() {
        return greet;
    }

    public void setGreet(String greet) {
        this.greet = greet;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }
}

Note that if we want to use these classes in test contexts, or if we will need to load them from external sources for some reason, we need Jackson to de/serialize them to and from JSON and for that, we need the default constructor. To learn more about Jackson you can read my post about it.

Introduction to Spring's capabilities - the @RestController annotation

In Spring, controllers are used to provide application functionality and orchestrate its inner code flows.

When some class in Spring gets this specific annotation: @RestController ,the framework will know that there are certain additional annotations that can be supported in methods and members of the class to enhance it with the necessary capabilities to work as an endpoint.

What this means is that we can annotate methods with @RequestMapping annotations that will "bind" the method to a certain HTTP verb, used to perform a request.

When using @RequestMapping we need to specify the actual "verb" used, so PUT, GET, POST, DELETE, etc, or alternatively, we can use specific mappings for a certain verb, such as @PostMapping which indicates that the endpoint will only accept Post requests.

Let's see how the endpoint looks and let's break it down on the important bits:

@RestController
public class GreetingsResource {

    private static final Log LOG = LogFactory.getLog(GreetingsResource.class);

//1
    @PostMapping(value = "/greet", consumes = MediaType.APPLICATION_JSON_VALUE)
//2
    public ResponseEntity<GreetDTO> greet(@RequestBody PersonDTO person) {
        if(person==null) {
            LOG.error("Request Body can not be null");
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        }
        GreetingService greetingService = new GreetingService(person);
        GreetDTO response = greetingService.getGreeting();
        if(response==null){
            return new ResponseEntity<>(HttpStatus.EXPECTATION_FAILED);
        }

        return new ResponseEntity<>(response, HttpStatus.OK);
    }
}

We already discussed the @RestController annotation above, so, let's focus on the areas with the numbers as comments:

//1 - analyzing the @PostMapping annotation

The annotation is used to ensure that the greet method will map to the /greet endpoint and additionally, that it only accepts POST requests.
We also see the consumes = MediaType.APPLICATION_JSON_VALUE parameter, that states that this endpoint can only receive requests in JSON format. This is good for both consistency with the good practices of writing REST APIs and also serves as "documentation" for future uses of the API.

//2 - Drilling down into ResponseEntity<> and @RequestBody constructs

We see that there's a lot of useful information regarding the way the REST Controller works under the hood in the header of the method.

The @RequestBody annotation is used to define that the method parameter is going to be sent to our endpoint in the body of the http request.

When we send across the JSON, there will be an implicit mapping from the JSON to the Java DTO and the values from the JSON will be set on our Java object, so effectively our requestBody object will be a Java representation of our JSON request data.

Then, we can process our data using Java. Typically, this processing step takes place inside a service class which is a class typically used to encapsulate business logic. Thus, if the business logic changes, we do not need to change the endpoint and can simply adapt the service class.

The service class can also build a response, which is typically encapsulated by a ResponseEntity<T> class, which is a class from Spring that contains a response DTO, of type T, in our case: ResponseEntity<GreetDTO>.
The last thing we'll discuss is the implementation of the service class that will encapsulate the business logic for our endpoint.

The service class

To link all we have done together, the last thing we need is to see the actual implementation of the service class, which will contain the logic of our endpoint and will generate a ResponseEntity with the JSON and a success code. Let's see the implementation:

public class GreetingService {
      private final PersonDTO person;

      public GreetingService(PersonDTO person) {
             this.person = person;
      }

      public GreetDTO getGreeting() {
             return new GreetDTO("Hi, "+person.getName(), new Date());
      }
}

All the logic of the service class is trivial in this example and it's focused on building a new response object from the PersonDTO object which was what was sent in our request body.

This concludes all the work for our simple endpoint.

Conclusion

Hope you got a better idea on how to write a simple endpoint using Spring's capabilities! On the next post, we will see how to use Postman to actually see it working, and we will see what we can leverage in Spring's ecosystem to write an integration test for it.

Top comments (0)