Lesson Thursday

As we learned in the previous lesson, dealing with Exceptions properly is a big part of keeping our applications stable, user friendly, and performant. In this lesson, we'll learn how to create custom exceptions we can use to tailor our applications even more to our user's needs.

As we don't have a frontend for our application, this will help us out a lot - after all, what if someone sends data to our application that doesn't arrive in the right format, or creates some kind of error? We don't have a frontend to control what the user submits even a little bit. Exceptions can really help us out here.

Let's create a custom exception that displays an error message, and an HTTP status code when a user tries to submit something incorrectly.

In your src/main/java package, create a new package called exceptions.

Adding a new Exception

Exceptions aren't mystical beings from faraway lands - they are actually just normal, Plain Jane, Average Joe, boring old Java objects. Let's write one!

Create a new Java class and call it ApiException.

Add the following code:

src/main/java/exceptions/ApiException.java
package exceptions;

public class ApiException extends RuntimeException{

   private final int statusCode;

   public ApiException (int statusCode, String msg){
       super(msg);
       this.statusCode = statusCode;
   }

   public int getStatusCode() {
       return statusCode;
   }
}

This is a short file - it doesn't need to be long, because it is, as you can see, inheriting functionality from the RuntimeExeption class.

You are probably wondering what this line is about:

super(msg); What is happening here is called calling super or calling to super or even super call.

What this means, in plain English, is that we are calling the superclass' (i.e. the object class we are inheriting from, the parent class) constructor in order to instantiate an unnamed object at this location. The next line, this.statusCode = statusCode; is affecting that object that the call to super created.

Because we don't really need to have the exception object here, the call to super() is more concise and carries less overhead. If you are curious about learning more about what this means, the Java Docs for once have a fairly straightforward explanation you can read (here)[https://docs.oracle.com/javase/tutorial/java/IandI/super.html]

Handling Exceptions with our Custom Exception

Now it's time to implement our custom exception. Let's throw a new error if a user tries to access a route with an id that doesn't exist. Change your route handler for a single restaurant as follows:

src/main/java/App.java
get("/restaurants/:id", "application/json", (req, res) -> {
   int restaurantId = Integer.parseInt(req.params("id"));

   Restaurant restaurantToFind = restaurantDao.findById(restaurantId);

   if (restaurantToFind == null){
       throw new ApiException(404, String.format("No restaurant with the id: \"%s\" exists", req.params("id")));
   }

   return gson.toJson(restaurantToFind);
});

If we boot up postman and fire a request at localhost:4567/restaurants/132, we will see the following in our terminal window:

[qtp1736140021-16] ERROR spark.http.matching.GeneralError -
exceptions.ApiException: No restaurant with the id: "132" exists
    at App.lambda$main$3(App.java:85)

Right on! So this is where these errors come from. But, we are still seeing an ugly 500 server error in our Postman window, which is not ideal, it would be better if we displayed a custom message here too that the user can see when they are using the API. Let's work on that next.

Improving Error Handling in our App.java with Filters

We can use a filter, similar to our after() filter to improve how we handle our errors.

Open up your App.java, and add the following code above your after filter:

src/main/java/App.java
exception(ApiException.class, (exc, req, res) -> {
   ApiException err = (ApiException) exc;
   Map<String, Object> jsonMap = new HashMap<>();
   jsonMap.put("status", err.getStatusCode());
   jsonMap.put("errorMessage", err.getMessage());
   res.type("application/json"); //after does not run in case of an exception.
   res.status(err.getStatusCode()); //set the status
   res.body(gson.toJson(jsonMap));  //set the output.
});

Similar to how a filter can run before or after every route, and a route runs whenever a URL is requested, this exception rule runs whenever an exception is generated by the server. If you implement the code above and and fire a request at localhost:4567/restaurants/132 again, you'll see our ugly 500 server error has been replaced by a new output (in JSON!) in Postman. This is much better.

Let's walk through exactly what happens in this exception handler.

exception(ApiException.class, (exception, req, res) -> { When an exception happens, generated an exception object. ApiException err = (ApiException) exception; Cast the generic exception object as a specific ApiException Map jsonMap = new HashMap<>(); Make a new hashmap to store some information - as hashmap is the closest data structure we have to a JSON object as it is key/value pairs. jsonMap.put("status", err.getStatusCode()); Add in the status as a key, and the code as the value to show it to our user. jsonMap.put("errorMessage", err.getMessage()); Add the error message as a key, and the code as the value to show it to our user. res.body(gson.toJson(jsonMap)); Set the output. We need to do this here explicitly, as our after filter does not run in the event of an exception. (remember: we are using our after filter to automatically set all outputs to data type: JSON.)

Cool. Now you have yet another tool in your toolbox that make your apps communicate well with your user, and handle issues gracefully. You can use you knowledge of custom exceptions to improve your Blog or To Do List by building custom 404 or error pages, redirecting users when they hit 404's, and much, much more.