Spring Boot RestClient Error Handling
Key Takeaways
The video demonstrates best practices for error handling in Spring Boot's RestClient, covering default RestClient builder, global exception handlers, custom exceptions, and retry support in Spring Framework 7. It utilizes tools such as Spring Initializr, Spring Boot, Maven, Java, and httpbin.org to showcase error handling techniques.
Full Transcript
I was recently asked a question on what are some of the best practices when it comes to the rest client and error handling. So, I thought we would just put together a video on that. And to do so, I'm going to kick up a new project using the Spring Initializr, but I'm going to use my terminal UI. Uh if you haven't seen this, I built a terminal UI using the Spring Initializr so that we can kind of kick off a project here. I will leave a link uh down below and somewhere up here if possible. So, to get started, I am going to run Spring Again, this is just the Spring Initializr. You can do this from start.spring.io. I'm going to kick off a Maven project using Java Spring Boot 4.0.5. Uh we're going to call this Let's just call this uh rest client error handling RCEH. We're going to use JDK 26 because that is now available, so I'm going to use that. Uh but nothing in here is JDK 26 specific. >> [snorts] >> I am just going to pick the Spring Web and the HTTP client. When I pick Web and HTTP client uh Spring is smart enough to know that, "Hey, you're in a web app. We need to use the rest client." So, from there I can go ahead and generate the project. I will open it up in IntelliJ IDEA. And also, the nice feature of this is that I can have some post uh generation hooks. And so, in my case, I always kind of open up Cloud Code in the terminal there as well. So, that'll open that up. So, I'm going to go ahead and trust this project. And let's go ahead and make this full screen and get me off of there. We are going to build a client that talks to an API. Now, we need an API to stand up. I didn't want to stand one up and have to have that included in the project. So, we're going to use this service called httpbin.org. This is really great. It's a simple HTTP request and response service. What you get out of this though is really nice. You get some methods. If you just want to call get method you can call this. You can try it out here, execute it, and you'll get a response from that. What's really cool though is you can do things like auth and status code. So, status codes here we can perform a get request with {slash} status and then the code that you want it to return. This will allow us to go ahead and test out some error handling on specific codes. So, let's do that. Um so, what we'll need is we'll need uh this. This is the uh base URL. And from there, we can go ahead and get going. So, let's go ahead and uh create a new Java class. I'm going to call this the HTTP bin controller. And inside the controller, we're going to make this a rest controller. So, the first thing that we need is a rest client. And this is really going to be my first tip here. So, we're going to go ahead and create a private private Dan, private, really? Private final rest client. we'll call this Let's call this client. And then we'll go ahead and get this through constructor injection. So, the rest client itself is an interface. Uh in this interface, there's a whole bunch of really useful information. Uh there's ways to create a a rest client using the static factory methods. So, you can create a new one. Uh if you just call create, this returns the default rest client builder. We'll talk about that in a second. You could pass in a base URL um and return the default rest client builder with that base URL. Um but what we want to look at is the default rest client builder because that is um there's actually a builder in here, too. So, if we look at rest client builder that is also an interface. There is a default implementation of this called the default rest client builder. In any case, when when we're able to, we want to use this rest client builder because it comes with a whole lot of like preconfigured things. Uh most notably uh uh observability. So, when we get into calling other APIs and we include something like the actuator or in Spring Boot 4 the new Spring Boot Open Telemetry starter, then we get all of that baked-in observability that we wouldn't get if we kind of hand-rolled our own. So, my number one tip is when possible, use the default rest client builder that is given to you. So, we'll go ahead and I like to call this builder. Let's say builder. And then we can say builder.base URL. This is the uh HTTP bin. And then we'll go ahead and build an instance from there. So, that's tip number one. So, now that we have a rest client we can go ahead and uh make some calls out to HTTP bin, right? So, I'm going to paste just in a couple of methods here. Uh this is a {slash} get. This will call that same URI that we just saw in the uh explorer. And it will turn return a string with with some some random response in it, right? And then we also have the ability to call get status and then the code that we want to pass um which will give us a an an error code like or not just an error code a status code, right? >> [snorts] >> So, these are our kind of two of the building blocks here. Let's go ahead and run this application. And go over to the terminal. And let's go ahead and make {slash} get. And if we do that, we see we get that information back. Here's the args that we passed, the headers the origin and the URL. All right. So, I'll make a request to HTTP localhost 8080 {slash} get {slash} status uh {slash} 404. And that is going to return a 500 because the rest client has something built in here. The default behavior is to throw an exception on a 4 or 500 response before your code even runs. So, I can go ahead and say on status, if it is an error then let's just suppress the default exceptions. So, let's go ahead and run that again. And I'll go back to my terminal, run that again. And now we see that success, but we get that 404. So, with that, um one of the patterns that I've seen and that I've done myself, right? Is I may come in here and because we get the response, I can go ahead and check for certain status codes. So, I'm checking if the status code is not found, do this. If it's internal server error, do this. Unauthorized, you get it, right? This works [clears throat] for this method, but the second I have to write another method and handle statuses then I'm duplicating a lot of code and that's not a lot of fun, right? So, let's get rid of that. And what we'll do is we'll change something here in the rest client uh builder. So, let's do this. We'll say base URL. And then we're going to use the um default status handler. So, I'll paste some code in here. And now we are catching this at the rest client level. So, we're saying if there's an error go ahead and throw an HTTP status code exception. So, if we rerun this again um we're not actually catching this right now, but if we go ahead and run this and we see success 404 back in the console um everything looks good. We are catching that. Oops, we are not. Let's go ahead and get rid of this. Sorry. So, let's rerun that. >> [sighs and gasps] >> And then uh go back to the console, clear this out. 404. And now we see we're getting that internal server error. That's a 500. Because we're throwing that here uh we are not catching it anywhere. So the next tip here is to define a global exception handler. So, let's go ahead and do that. So, I'll create a new Java class called global exception handler. This is going to be uh annotated with rest controller advice. So, if we take a look at that a shortcut annotation that combines controller advice with response body. In effect, simply a controller advice whose exception handler methods render to the response body. So, with that in place, we can write an exception handler for that exception being thrown uh which is the HTTP status code exception. Uh we can say public. And here's where we want to return something, right? That something is uh came in the version of Spring Boot 3.x. I can't remember exactly which one, maybe 3.0. Uh and we get this idea of a problem detail. So, a problem detail is a representation for an RFC 9457 problem detail. This includes spec-defined properties and a properties map for additional non-standard properties, which means you can add your own. So, problem detail gives us a way to define uh return a specific set of problems, a structured response, if you will. So, we have this. We'll call this handle. We will look for the HTTP status code exception, call this X. And then what we'll do is return problem detail for uh that looks okay. Maybe I just want to return get message. Yes. And now we have something that will catch that specific exception. So, let's go ahead and rerun this. And run this again. And now we see 404 error colon with this. This is the problem detail. It has the detail, instance, status, title. And again, you can kind of add your own to this. So, so far, so good. Starting to look a little better. I think what we need to do now is um kind of move this out. So, right now, we're using this uh rest client builder in our HTTP bin controller class. But if we wanted to use this elsewhere, then we kind of have to duplicate this logic right here, right? So, I want to get away from that. So, I'm going to create a rest client config. So, let's say new Java class rest client config. And this is going to be a configuration class. Yes. And what we're going to do in here is I want to get the base URL for the service that we're going to talk to. So, in this case, we'll pull it from a properties file here, a YAML file. So, I've defined the base URL for HTTP bin. We'll pull that in. And then we're going to define a bean. So, this is going to be a rest client. We'll call this HTTP bin client. And this is going to do pretty much the same thing, right? We're going to take in the builder. And what we'll do from that is return the builder the base URL. Sure. That looks good. That looks good. And then what I'm going to do is use that default status handler here. But now we're going to define some custom exceptions. Instead of just throwing that HTTP status code exception, let's take a look at this. We can go ahead and do some checks on this. If it's not found, I want to throw a new not found exception. I've seen all kinds of strategies here. You can throw ones for four x's, for five x's. In this case, I'm just catching the not found. And for anything else, I'm going to throw this API exception. So, I'll just copy and paste some code in here. These are just my custom exceptions. I have an API exception. This extends from runtime exception. We take in the message, the status code, and then we set the status code here. We'll create a not found exception that extends our API exception. And again, just takes in the message. We'll pass the message in and the status of not found. So, now back in here, now we're going to throw that specific not found exception. So, let's go Oh, wait. One more thing. We've created this rest client. But back in our controller, we're using this. We don't want to use this. We're just going to create a constructor. And now it's going to take in the rest client because we use the builder over here to create the instance. We're going to ask for an instance of the rest client. We've created one in the form of a bean. So, Spring will auto wire that in. If you don't know, if you're kind of new to Spring, this is the same as saying auto wire. This says, "Hey, I want to auto wire in these arguments." So, these will be injection points. Spring will look at it. So, do I have anything? This is implicit when we have a single constructor, so we don't need that. So, now this should do give us a little bit more improvement here. Let's go ahead and rerun this. Let's go ahead and go back here and then hit 404. And we see that we have our problem detail here throwing this error. But if we look in the console here, we see that this exception has been thrown. So, our custom not found exception has been thrown. And that gets us a little closer to where we want to be. All right, let's take a look at one more thing we can do here in our HTTP bin controller. I'm going to create a method here. So, this is public string get unstable. There's an unstable endpoint on HTTP bin. And I'm going to need a logger here. So, let's go up here and use a handy IntelliJ live template to do that. And so, what I'm going to do is just log.info and say attempting to get unstable resource. So, we're going to try to get that. And then what we'll do is use the client. We'll call get. We'll pass it a URI of unstable. Retrieve body to string. Yes, that looks good. So, now we're going to reach out to HTTP bin at the unstable endpoint. And that is going to basically not be available. So, what I want to do is take advantage of some of the new things in Spring Boot 4 and Spring Framework 7. And that is the ability that we have built in the resilience methods from the Spring Retry project into the framework. So, now what I can do is let's go ahead and get a get mapping for this to get unstable. And now I can use the @Retryable annotation. And so, I can say this is going to include what? We don't want to retry this for every exception. In our case, maybe just the API exception that we've defined. So, when that is thrown, I want you to retry it. How many times do I want you to retry it? Let's go ahead and say three. What is the delay going to be? This is the default, but I'm just putting it here to show this off. This will be a 1-second delay. And then I have a multiplier of two. And what this means is that on the first time, it'll be a 1-second delay. On the second time, it'll be two. On the third time, it'll be four. And this is really that kind of exponential back off. The ability to do that is nice cuz you don't want to just try it three times in a row a second apart, right? >> [snorts] >> So, now we've got this built in here. And this, instead of just airing out, we can go ahead and retry it. And this this is a really nice way to kind of handle this. So, let's go ahead and run this again. And what did we say here? Get unstable. So, let's go ahead and say 8080/get/unstable. And Oh, we forgot something. Let's go back to our application. Where is that? And to do that, we need to enable the enable resilience methods. So, let's go ahead and restart that. Take a look back at our console here. And I'll go Oops. Wrong one, Dan. Clear this out. And let's just run that same one, unstable. And if we go over to the console, we'll see attempting to get unstable resource one, two, three times. And then it'll be done. So, that is taking advantage of the resilience methods that have been brought into Spring Framework 7. All right, and that's all I got. Not a ton here, but I was asked like, "Hey, what are some of the What are some tips when using the rest client and specifically around error handling?" There is more. Check out the Spring Framework 7 documentation. If you want to learn more about like handling errors, there's some really good information in there. More than I covered today, but I just thought we would kind of touch the surface on some things that we could do. Also, the the tip of, you know, basically creating an instance of the rest client, use the default rest client builder when you can. You get a lot of things out of the box with that that you wouldn't if you basically created your own instance. So, use that when possible. Hey, let me know. What are some other tips that you have for error handling when it comes to the rest client? What are some best practices that you like to deploy? Again, this wasn't an exhaustive video. I just thought I'd throw throw a few things out there and kind of kick off the discussion. But hey, I had some fun putting this together. If you learned something new today, do me a big favor. Leave me a thumbs up. Subscribe to the channel. And as always, happy coding. Here we go. Here we go. Here we go. Here we go. Yeah. Here we go. Here we go. Here we go. Here we go. Yeah. Here we go. Here we go. Here we go. Here we go. Yeah. Here we go. Here we go. Here we go. Here we go. Yeah. Here we go. Here we go. Here we go. Here we go. [music] Yeah. Here we go. Here we go. Here we go. Here we go. Yeah. Here we go. Here we go. Here we go. Here we go. Yeah. Here we go. Here we go. Here we go. Here we go. Yeah. Here we go. Here we go. Here we go. Here we go. Yeah. Here we go. Here we go. Here we go. Here we go.
Original Description
Are you handling errors in the Spring RestClient the right way? In this video, I'll walk you through best practices for error handling, from using the default RestClient builder to global exception handlers, custom exceptions, and the brand-new retry support built into Spring Framework 7.
We'll build a REST client from scratch that talks to HTTPBin.org, set up a default status handler to catch HTTP errors centrally, create custom exceptions with Problem Detail responses (RFC 9457), extract REST client configuration into a reusable bean, and finish by adding automatic retries with exponential backoff using the new @Retryable annotation in Spring Boot 4.
- Always use the default RestClient.Builder — it comes pre-configured with observability and other Spring Boot features
- Use defaultStatusHandler on the builder to centralize error handling instead of duplicating status checks in every method
- Create custom exceptions (e.g., NotFoundException, ApiException) and a global @RestControllerAdvice to return structured ProblemDetail responses
- Extract your RestClient configuration into a @Configuration class with a @Bean so it can be injected and reused across controllers
- Leverage the new @Retryable annotation in Spring Framework 7 for built-in retry with exponential backoff on transient failures
If you found these tips helpful, smash that thumbs up button, subscribe for more Spring Boot content, and drop a comment with YOUR favorite RestClient error handling strategies!
0:00 - Intro & Project Setup
1:30 - Exploring HTTPBin.org as a Test API
2:45 - Tip #1: Use the Default RestClient Builder
4:30 - Making GET Requests & Default Error Behavior
5:45 - Suppressing Default Exceptions with onStatus
6:30 - Why Per-Method Status Checking Doesn't Scale
7:15 - Using defaultStatusHandler on the Builder
8:00 - Tip #2: Global Exception Handler with @RestControllerAdvice
9:15 - Returning ProblemDetail (RFC 9457) Responses
10:30 - Extracting RestClient Config into a @Bean
12:00 -
Watch on YouTube ↗
(saves to browser)
Sign in to unlock AI tutor explanation · ⚡30
More on: RAG Basics
View skill →Related Reads
📰
📰
📰
📰
Why I Ditched Socket.IO for Raw WebSockets (And What I Learned)
Dev.to · Nikhil Sharma
atob() can't decode a JWT — the Base64URL gotcha (and the fix)
Dev.to · Daniel Cheong
Why Debugging Made Me a Better Developer
Medium · JavaScript
Mapping Go Domain Errors to HTTP Status Codes at the Boundary
Dev.to · Gabriel Anhaia
Chapters (10)
Intro & Project Setup
1:30
Exploring HTTPBin.org as a Test API
2:45
Tip #1: Use the Default RestClient Builder
4:30
Making GET Requests & Default Error Behavior
5:45
Suppressing Default Exceptions with onStatus
6:30
Why Per-Method Status Checking Doesn't Scale
7:15
Using defaultStatusHandler on the Builder
8:00
Tip #2: Global Exception Handler with @RestControllerAdvice
9:15
Returning ProblemDetail (RFC 9457) Responses
10:30
Extracting RestClient Config into a @Bean
🎓
Tutor Explanation
DeepCamp AI