I am working on a quite a big Spring Boot web service and I wanted a standardized and simplified way to handle responses and exceptions by following fluent API design.
So this is what I did and it works as I expected.
For handling responses, first i created an interface
like this,
public interface BaseResponse {
public short code(); //holds API specific code
public HttpStatus httpStatus(); // holds corresponding HTTP status code
}
And then i have created an enum
which implements above BaseResponse
interface,
All the response codes are defined here.
public enum ResponseType implements BaseResponse {
SUCCESS((short) 10000, HttpStatus.OK),
INVALID_FILE_TYPE((short) 10001, HttpStatus.BAD_REQUEST),
//rest...
}
Here's the response object (ApiResponse
) definition,
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiResponse {
//holds http status code
@JsonProperty("httpStatus")
private short httpStatus;
//holds API response code
@JsonProperty("code")
private short code;
//holds data return from API
@JsonProperty("data")
private Map<String, Object> data;
//holds errors return from API, if error response
@JsonProperty("error")
private Map<String, Object> error;
public static ErrorResponseBuilder error(ApiError err) {
return new ErrorResponseBuilder(err);
}
public static SuccessResponseBuilder success() {
return new SuccessResponseBuilder();
}
/**
*
*/
public static class ErrorResponseBuilder {
private ApiError err;
public ErrorResponseBuilder(ApiError err) {
this.err = err;
}
//returns error API response
public ApiResponse build() {
Map<String, Object> d = new HashMap<>();
d.put("error", err.getMessage());
short httpStatus = (short) err.getType().httpStatus().value();
return new ApiResponse(
httpStatus,
err.getType().code(),
null,
d
);
}
}
/**
*
*/
public static class SuccessResponseBuilder {
private Map<String, Object> pd;
public SuccessResponseBuilder() {
pd = new HashMap<>();
}
public SuccessResponseBuilder attr(String key, Object value) {
pd.put(key, value);
return this;
}
//returns success API response
public ApiResponse build(BaseResponse type) {
short httpStatus = (short) type.httpStatus().value();
return new ApiResponse(
httpStatus,
type.code(),
pd,
null
);
}
}
//this is for logging
@Override
public String toString() {
return new StringBuilder()
.append("httpStatus:").append(getHttpStatus()).append(", ")
.append("code:").append(getCode()).append(", ")
.append("data:").append(getData()).append(", ")
.append("error:").append(getError())
.toString();
}
}
Then i use this object to create responses like below,
Success response
//call `@Service` method, and get result of it
String fileId = this.fileHandlerService.storeFile(file);
//create success response
ApiResponse apiRes = ApiResponse.success()
.attr("fileId", fileId)
.build(ResponseType.SUCCESS);
//return HTTP response with 'apiRes' in body
return ResponseEntity.status(apiRes.getHttpStatus()).body(apiRes);
This results in below json
response,
{
"httpStatus": 200,
"code": 10000,
"data": {
"fileId": "a251cb619dc145f684b9682bcb503a5c"
}
}
Error response
To handle exception responses, i created a Runtime Exception
called ApiException
. This is being used to handle every exceptional cases. For example: user-not-found, inactive-user, invalid-file-type, etc...
ApiError
class was created for hold API error details,
public class ApiError {
private BaseResponse type;
private String message;
private String debugMessage;
private Exception exception;
}
ApiException
public class ApiException extends RuntimeException {
private ApiError apiError;
//boilerplate code omitted
}
Use this exception like this,
throw new ApiException(ApiError.builder()
.type(ResponseType.INVALID_FILE_TYPE)
.message("Invalid file, please upload ms-excel file")
.debugMessage("expected file extension:xlsx, but found:" + fileExt)
.build()
);
This results in below json
response,
{
"httpStatus": 400,
"code": 10001,
"error": {
"error": "Invalid file, please upload ms-excel file"
}
}
This is the mechanism used every where in the web service.
- Is this a good way to handle response ?
- How can i improve it?
1 Answer 1
As you are using Spring Boot
, the best way to handle you response is to add a RestControllerAdvice.
A few rules to follow :
Create custom
POJO
to represent your custom message (you did it already)Return directly the response message in your controller, by setting the HttpStatus, here
2XX
. You don't have to define your own HttpStatus,Spring Boot
provides it alreadyIntercept your exceptions in your
RestControllerAdvice
. It allows you to return the custom message you wantAvoid sending the full exception message to your user. Send a customized clear and simple message. Log your exception full message for internal investigations instead
Choose the right HttpStatus code for differents situations in your
RestControllerAdvice
:Parameters
null
orvalue not set
:400 => Bad request
Returned
value not found
(record or list empty) :404 => Not found
Exception from server (database error, network error etc.) :
500 => Internal server error
Following this, you should be able to change your code to follow those few rules.
Explore related questions
See similar questions with these tags.
data
field of success response, i can add objects, arrays, strings etc... \$\endgroup\$jackson-databind
package has to be added in the starting spring boot configuration, have you it in your configuration ? \$\endgroup\$