Introduction to Default Spring Boot Exceptions
Let's go ahead and learn how we can have custom exceptions with Spring Boot. So far in our application, if I just inspect this and then go to console, if I refresh or restart, you can see that right here we have this JSON payload and this is what Spring Boot gives us by default, right? So you can see that we have an error, this is internal server error, the message. This message comes from this "oops cannot get all students".
And then we have the path, status, and also timestamp. But let's say that we want to have custom exceptions and then have a completely different JSON structure. So let's go ahead and learn how to do that.
Creating a Custom Exception Class
So open up IntelliJ and let's go ahead and create a new package. So we're gonna go ahead and create new package and let's go ahead and call this package as exception. Now inside of the exception class, or actually package, go ahead and create a new class and let's go ahead and simply say ApiRequestException. So this will be our custom exception that we can throw throughout our application.
And now what we need to do is simply extend RuntimeException. Now let's go ahead and create a constructor. So I'm gonna press control and then enter, constructor. Now go ahead and pick the one that has the actual message, so the second one. And let's go ahead and also have the constructor that has the actual Throwable, so we can pass the actual message as well as the throwable. And this is pretty much what we need to do for our custom exception.
Building the Exception Handler Class
Now the way that we handle exceptions, or actually throw custom exceptions with Spring Boot, is by simply having an exception handler. So let's go ahead and simply create the class, and let's go ahead and simply call this as ApiExceptionHandler.
Now the purpose of this class is pretty much just to handle custom exceptions or even handle existing exceptions, but then we can customize the way that we throw the actual error to the client. So if I open up Chrome right here, you can see that this is what Spring Boot gives us by default. So let's go ahead and change this so you see how it's done. So let's go ahead and pretty much have a method.
And right here, simply go ahead and say public and then right here simply say ResponseEntity and this will be a ResponseEntity of Object. So right here, let's go ahead and simply say handleApiRequestException. So basically we're going to handle our custom exception that we've just created. So inside, what we're going to receive is the actual exception that we've just created, so ApiRequestException and let's simply call it e. In fact, if I put this fullscreen so you see what we are doing.
Now what we need to do is two things. So one, we need to create a payload that we will send inside of the ResponseEntity, so payload containing exception and then details. And then second, we need to pretty much return the actual ResponseEntity. So second, return ResponseEntity.
Defining the Custom JSON Payload
Let's go ahead and pretty much just create a class that will represent the actual exception. So let's go ahead and go into your class inside of the exception package and let's go ahead and simply name this as ApiException, just like that. Go ahead and simply say okay. And then inside what we're gonna do is actually a few things. So let's go ahead and add a few fields. So private final and then String and then message. Let's also go ahead and simply say private final and then we will include the actual throwable, so the client can have full visibility of what happened. But usually you wouldn't, you wouldn't pretty much just throw this to the client, right? But let's go ahead and simply throw this so you see how the client can view all the details of the actual exception.
So next, let's go ahead and simply include the actual HttpStatus. So private final and then HttpStatus and then simply name this as httpStatus. And finally let's go ahead and simply say private and then final and then ZonedDateTime and then timestamp, right? Let's also include the actual timestamp. So now let's go ahead and simply add this to constructor. And if I put this on a new line so you can see exactly what we're doing. Now let's go ahead and generate some getters. So control enter, getter, everything and there we go.
Implementing the Exception Handler Method
So now we can actually use this ApiException. So go back to ApiExceptionHandler and right here, so if we are catching this exception, so ApiRequestException, let's go ahead and create a new ApiException and then inside let's go ahead and pass the actual details of our payload. So for the actual message simply go ahead and say e.getMessage(). And then let's go ahead and pass the actual throwable, so in our case that will be e, so the actual exception. And then the HttpStatus, let's go ahead and simply press shift control and then space and then we can pick from this. So let's go ahead and simply say BAD_REQUEST, so it's a 400. And finally we have to include the actual timestamp. So simply go ahead and say ZonedDateTime.now() and you can pass the actual Zone ID. So shift control space and then ZoneId.of(), so basically I want to have the actual UTC timestamp. And there we go. And this should be double quotes I believe, just like that.
So now let me go ahead and simply end that with a semicolon and I can extract this to a variable. So I'm gonna press alt and then command V and then this is the actual ApiException. So this object right here contains the actual payload information. So currently this is our payload. So you can see right here that this is our payload. So if I go back, so basically this will replace that payload essentially. So now what we need to do is simply return the actual ResponseEntity. So simply go ahead and simply say return and then new ResponseEntity. And then inside, let's actually pass the actual ApiException and then we also have to pass the status. So simply press shift control and space and then request. And in fact, let's go ahead and extract this to a variable because we have it right here and also right here. So I'm going to press option command and then V and then replace all occurrences. And then this will be badRequest and there we go.
So we are pretty much done with our exception handler for this specific ApiRequestException. So now we have to tell Spring that this method right here will be responsible of handling this exception. So the way we do it is by simply adding an annotation right here and simply say @ExceptionHandler. Now inside of this exception handler we have to pass a value, so we can pass one or more values. So if you are catching multiple exceptions you can pass multiple values but in our case what I'm actually catching is the actual ApiRequestException.class. So basically this exception right here will be fed inside of this exception class that we've just created and then we can have access to the details.
And one more last thing that we have to do is to actually tell Spring that this class right here will be served as a class that pretty much handles multiple exceptions. I.e. you can have multiple exception handlers and the way we instruct Spring Boot that this class will be served for exception handling is simply by adding an annotation called @ControllerAdvice. And there we go.
Throwing and Debugging the Custom Exception
So let's go ahead and test this. So what I want to do first is actually stop my server. So I'll stop the server, and then I'm gonna add a breakpoint right here. So simply add a breakpoint right here and now open up the project tab, go to StudentController and then right here, instead of throwing IllegalStateException, let's go ahead and throw our new exception. So simply say throw new ApiRequestException and then pass a message. So let's simply go ahead and change this to the same message. So let's go and grab this and then ...with custom exception, just like that, and add a semicolon. Remove this IllegalStateException.
And what I'm gonna do now is simply start this in debug mode. So go ahead and press on this icon right here for debug. Just give it a second. And there we go, so now this is up and running. So what I'm going to do is refresh my web browser and if I restart, you can see that that takes me right away to this breakpoint because we are throwing this exception right here. So we are throwing this exception and then we have this exception handler. So right here you can see that we are building the actual contents. So it's a bad request, so you can see that it's a 400 right here. And then we are building this ApiException. So let me go ahead and simply step over, there we go. And if I inspect the contents of this ApiException, you can see that we have a message, so "oops cannot get all students with custom exception" and we also have the actual throwable. So this contains every single information. And right here you can see that we have the 400, it was a bad request, and then we have the actual timestamp.
So what I'm gonna do is simply continue and you can see that this payload now is sent to our client. And let's go ahead and inspect that. So right here you can see that we have a new message. And there we go. So you can see that "oops cannot get all students with custom exception", BAD_REQUEST, so right here, BAD_REQUEST, throwable, and then the actual timestamp. And this is awesome.
Refining the Payload and Final Words
So you see that we have this throwable right here which contains like a bunch of stuff that we don't need. So I just wanted to show you exactly how you can pass stuff around, but let's go ahead and remove this. So go back to IntelliJ, open up ApiException, remove this throwable right here. So command Y to delete there, as well, there as well, and there as well.
So if I now, let me stop the actual process and I want to run this instead of debug, run. Oh actually, I need to also remove the actual throwable right here. And then run. Just give me a second. So now if I go back to Chrome and then refresh, there we go. So now you can see that we have only three fields: the actual status, the message right here, and the actual timestamp. So there we go.
You now know how to throw custom exceptions with Spring Boot. If you have any questions, go ahead and drop me a message, but in my opinion, this is a very important topic that you must be aware of and know how to use because eventually, you will need to throw custom exceptions throughout your codebase.