Workbook 8.2
In workbook 8.1, we handled bad requests by throwing and catching checked exceptions. This method is annoying because it forces you to modify the service methods.
In this workbook, you will handle bad requests using a ControllerAdvice
class.
Launch the starter project
Task 1
Create a new folder called exception
. Inside the folder, create a ContactNotFoundException
class.
Use the following code to create a custom unchecked exception:
public class ContactNotFoundException extends RuntimeException {
public ContactNotFoundException(String id) { //constructor gets called when exception is thrown
super("The id '" + id + "' does not exist in our records"); //passing an error message into the parent constructor allows us to access it later...
}
}
Task 2
Modify the findIndexById
function to throw the ContactNotFoundException
.
private int findIndexById(String id) {
return IntStream.range(0, contactRepository.getContacts().size())
.filter(index -> contactRepository.getContacts().get(index).getId().equals(id))
.findFirst()
.orElseThrow(() -> new ContactNotFoundException(id));
}
Because it's an unchecked RuntimeException
, there is no need to catch it. It will just be thrown as the application runs.
Task 3
Run the application and make a GET request from Postman. There is no data in the ArrayList, so pass in any id.
GET: localhost:8080/contact/123
Because your application threw an exception in the middle of a request, it sends back a 5xx error code.
5xx means there was a failure from the server (our application).
But really, the failure results from the client's bad request. So our application should not fail, it should send back a 4xx error code instead.
New Concept: @ControllerAdvice
class-level annotation that allows you to define global exception handlers.
New Concept: @ExceptionHandler
method-level annotation that defines an exception handler.
- Defines the exception that you want the method to handle.
- The thrown exception can be accessed from the list of arguments.
Task 4
Create a class called ApplicationExceptionHandler
. Then apply the @ControllerAdvice
annotation. By doing so, your class will serve as the global exception handler.
Task 5
Inside your global exception handler:
- Create a method called
handleContactNotFoundException
, and mark it as an@ExceptionHandler
. - Your exception handler must pick up exceptions of type
ContactNotFoundException
. - Your exception handler's return type will be
ResponseEntity<Object>
. This is common practice becauseObject
allows us to pass anything into theResponseEntity
. - For now, your method will return a
ResponseEntity
that contains a status code of404
.
Test Case: GET: localhost:8080/contact/123
The exception handler handles the exception by responding to the consumer with a 404 status code.
Task 6
It would be nice to send the user an error response. Inside the exception
folder, create a class called ErrorResponse
.
public class ErrorResponse {
}
Create the following field and generate a complete constructor, getters and setters.
private String message;
Task 7
Inside handleContactNotFoundException
, create a new ErrorResponse
object, and pass the exception's message into the constructor.
ErrorResponse errorResponse = new ErrorResponse(ex.getMessage());
Return the errorResponse
object along with the status code.
return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
Spring Boot will automatically serialize the errorResponse
object inside the ResponseEntity
into JSON.
Test Case: GET: localhost:8080/contact/123
Task 8
It would be nice to also send back a timestamp. Add another field inside ErrorResponse
, and generate the usual getters and setters.
private LocalDateTime timestamp;
Inside the constructor, set the timestamp equal to the current time.
public ErrorResponse(String message) {
this.timestamp = LocalDateTime.now();
this.message = message;
}
Test Case: GET: localhost:8080/contact/123
The timestamp is barely readable.
Task 9
You can configure how each property gets serialized into JSON.
- Defines the structure to use when serializing into JSON .
- Defines the pattern to use when serializing into JSON.
We wish to configure how the timestamp
property will be serialized into JSON. The structure we will serialize to will remain JSON STRING
type. The pattern will be dd-MM-yyyy hh:mm:ss
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
Test Case: GET: localhost:8080/contact/123