avatarREUBEN SHUMBA

Summary

The provided content outlines a method for standardizing API responses in Spring Boot applications using a custom Response class to encapsulate both success and failure scenarios, along with optional custom exception handling and a demonstration of its implementation in a controller and service.

Abstract

The guide presented on the website delves into the creation of a Response class in Spring Boot to ensure consistent API responses. It emphasizes the importance of a uniform response structure for clarity and ease of use by both developers and API consumers. The Response class includes attributes such as statusCode, message, success, and data to convey the result of an API call. The article further suggests the implementation of custom exceptions to handle application-specific errors and illustrates how to manage exceptions globally within a Spring Boot application. A controller example showcases the practical use of the Response class, and a service example demonstrates business logic that either returns a successful response or throws an exception, which is then handled globally to maintain the standardized response format. The guide concludes by encouraging developers to adopt these practices for building clear and resilient APIs.

Opinions

  • The author advocates for the use of a custom Response class to achieve a consistent response format across all endpoints in a Spring Boot API.
  • It is the author's view that handling both success and failure scenarios uniformly enhances the developer experience and the overall quality of the API.
  • The article suggests that global exception handling is a best practice in Spring Boot applications for providing consistent error responses.
  • The author believes that standardizing API responses not only aids developers in understanding the API but also contributes to a more user-friendly interface for API consumers.
  • By providing complete sample code and detailed explanations, the author expresses confidence that developers can easily implement the suggested standardized API response structure.
  • The author's enthusiasm for the topic is evident, as they encourage readers to follow them for more tutorials and invite questions and feedback through their contact information.

Standardized Spring Boot API Responses — Learn how to structure responses

Introduction

When developing Spring Boot APIs, maintaining a consistent and clear response format is essential for both developers and consumers. In this guide, we’ll explore how to achieve this by using a custom Response class. This class will help us handle success and failure scenarios uniformly.

Step 1: Create the Response Class

Let’s start by creating the Response class to encapsulate the structure of API responses.

package com.example.standardapiresponse.responses;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.http.HttpStatus;


@Getter
@Setter
@ToString
public class Response<T> {

    private int statusCode;
    private String message;
    boolean success = false;
    /**
     * Can be a hashmap or list Spring will render a nice Json response :-)
     *
     */
    private T data;

    public Response(int statCode, String statusDesc) {
        statusCode = statCode;
        message = statusDesc;

        if (statusCode == HttpStatus.OK.value()) {
            success = true;
        }
        
    }

    public Response() {
    }

    public static <T> Response<T> failedResponse(String message) {
        return failedResponse(HttpStatus.BAD_REQUEST.value(), message, null);
    }

    public static <T> Response<T> failedResponse(T data) {
        return failedResponse(HttpStatus.BAD_REQUEST.value(), "Bad request", data);
    }

    public static <T> Response<T> failedResponse(int statusCode, String message) {
        return failedResponse(statusCode, message, null);
    }

    public static <T> Response<T> failedResponse(int statusCode, String message, T data) {
        Response<T> response = new Response<>(statusCode, message);
        response.setSuccess(false);
        response.setData(data);
        return response;
    }

    public static <T> Response<T> successfulResponse(String message, T data) {
        return successfulResponse(HttpStatus.OK.value(), message, data);
    }

    public static <T> Response<T> successfulResponse(String message) {
        return successfulResponse(HttpStatus.OK.value(), message, null);
    }

    public static <T> Response<T> successfulResponse(int statusCode, String message, T data) {
        Response<T> response = new Response<>(statusCode, message);
        response.setSuccess(true);
        response.setData(data);
        return response;
    }

}

Purpose:

  • The Response class is designed to encapsulate the structure of API responses, providing a standardized format for success and failure scenarios.

Attributes:

  • statusCode: Represents the HTTP status code of the response.
  • message: Describes the status or reason for the response.
  • success: Indicates whether the operation was successful.
  • data: Holds the payload data of the response, which can be ant object.

Constructors:

  • Response(int statCode, String statusDesc): Initializes the Response with a status code and description. Sets success to true for HTTP OK (200) status.
  • Response(): Default constructor.

Static Methods:

  • failedResponse: Generates a failed response with various combinations of status code, message, and data.
  • successfulResponse: Generates a successful response with different combinations of status code, message, and data.

Step 2 (Optional): Implement Custom Exception handling

To adhere to best practices, it is recommended to implement custom exceptions in your codebase. Below is an example of a custom exception class designed to handle application-specific errors:

package com.example.standardapiresponse.exceptions;

import lombok.Data;
import lombok.Getter;
import org.springframework.http.HttpStatus;

import java.util.Collections;
import java.util.List;


@Data
public class ApplicationException extends RuntimeException  {

    private HttpStatus httpStatus;
    private List<String> errors;
    private Object data;


    public ApplicationException(String message) {
        this(HttpStatus.BAD_REQUEST, message);
    }

    public ApplicationException(HttpStatus httpStatus, String message) {
        this(httpStatus, message, Collections.singletonList(message), null);
    }

    public ApplicationException(HttpStatus httpStatus, String message, Object data) {
        this(httpStatus, message, Collections.singletonList(message),  data);
    }


    public ApplicationException(HttpStatus httpStatus, String message, List<String> errors, Object data) {
        super(message);
        this.httpStatus = httpStatus;
        this.errors = errors;
        this.data = data;
    }

    public void setHttpStatus(HttpStatus httpStatus) {
        this.httpStatus = httpStatus;
    }

    public void setErrors(List<String> errors) {
        this.errors = errors;
    }

    public void setData(Object data) {
        this.data = data;
    }

}

Now, let’s manage exceptions through global exception handling within a Spring Boot application. This GlobalExceptionHandler class handles various exceptions and provides consistent responses for different scenarios

package com.example.standardapiresponse.exceptions;

import com.example.standardapiresponse.responses.Response;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;


import java.net.UnknownHostException;

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

 @ExceptionHandler(Exception.class)
 public final ResponseEntity<Object> handleAllExceptions(Exception ex) {

  log.error(ex.getMessage(), ex);

  if (ex.getCause() instanceof UnknownHostException) {
   Response<String> error = Response.failedResponse(HttpStatus.NOT_FOUND.value(),
     ex.getLocalizedMessage());
   return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
  }
  Response<String> error = Response.failedResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(),
    "We are unable to process your request at this time, please try again later.", ex.getMessage());
  return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
 }

 @ExceptionHandler(ApplicationException.class)
 public ResponseEntity<Object> handleApplicationException(ApplicationException ex) {
  return ResponseEntity.status(ex.getHttpStatus()).body(Response.failedResponse(ex.getHttpStatus().value(), ex.getMessage()));
 }

}

This allows us to also have standard exceptions or error responses

Step 3: Create a Controller Using the Response Class

Now, let’s create a controller that uses the Response class to handle API responses consistently.

The Controller class returns the `ResponseEntity` of Response

package com.example.standardapiresponse.controllers;

import com.example.standardapiresponse.models.User;
import com.example.standardapiresponse.responses.Response;
import com.example.standardapiresponse.services.ExampleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/api/v1")
public class ExampleController {

    @Autowired
    private ExampleService exampleService;

    @GetMapping("/users")
    public ResponseEntity<Response<List<User>>> getUsers(){
        //Validate your request for other end point first

        // call your service
        Response<List<User>> response = exampleService.getUsers();

        //return Response entity.
        return ResponseEntity.status(response.getStatusCode()).body(response);

    }
}

Line of Interest: This line illustrates the method of returning a response object:

return ResponseEntity.status(response.getStatusCode()).body(response);

Step 4: Create a Simple Service

Next, let’s create a simple service that performs some business logic and must return Response or throw exception

package com.example.standardapiresponse.services;

import com.example.standardapiresponse.exceptions.ApplicationException;
import com.example.standardapiresponse.models.User;
import com.example.standardapiresponse.responses.Response;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ExampleService {

    public Response<List<User>> getUsers(){

        //Do your business logic or get data from Repository
        User helloWorld = new User("Hello World");
        User sample = new User("Sample of ");
        User standardized = new User("standardized");
        User responses = new User("responses");
        List<User> users = List.of(helloWorld, sample, standardized, responses);

       // =============Sample for Exception throwing response via exception =======================/*
       /**
         // sample of throwing exceptions
         // Please note that this response will be handled by global exceptions
        if(users.isEmpty()){
          throw new ApplicationException(HttpStatus.NOT_FOUND,"No user found", null);
        }
        */

       // =============Sample for failed response =======================
       /**
         Sample of return a failed response
        return  Response.failedResponse(HttpStatus.NOT_FOUND.value(),"No user found", null);
        */

        return  Response.successfulResponse("Successful", users);
    }
}

Samples Response:

  • Exception Throwing: This will be handled by the global exceptions and return standard response like the class we created
throw new ApplicationException(HttpStatus.NOT_FOUND, "No user found", null);
  • If the list of users is empty, an ApplicationException is thrown, which is meant to be handled by global exceptions. Failed Response: Demonstrates returning a failed response explicitly.
return Response.failedResponse(HttpStatus.NOT_FOUND.value(), "No user found", null);
  • Successful Response: Demonstrates returning a successful response with the list of users
return Response.successfulResponse("Successful", users);

This is how the response now look

Throwing exception

Complete sample code : https://github.com/reubenshumba/standardapiresponse.git

Conclusion

Following these steps, you have established a tailored structure for consistent API responses in Spring Boot. This approach improves the clarity and user-friendliness of your API, aiding developers in understanding and effectively utilizing the communicated data. Tailor the code to suit your specific requirements and continue building resilient and developer-friendly APIs with Spring Boot.

Stay tuned!!! I will be back with some more cool Spring boot tutorials in the next article. I hope you liked the article. Don’t forget to follow me 😇 and give some claps 👏. And if you have any questions feel free to comment. Thank you.

Thanks a lot for reading till the end. Follow or contact me via: Email: [email protected] LinkedIn: https://www.linkedin.com/in/reuben-shumba-a72aaa157/ BuyMeCoffee: https://www.buymeacoffee.com/reubenshumba REUBEN SHUMBA

Spring Boot
Spring Framework
API
Api Rest Java
Recommended from ReadMedium