I am trying to build a retry logic using Spring WebClient
. The problem that I am trying to solve is very simple. I am calling an API endpoint to get some values. If the API returns an error with say 401 response, then I will have to make call to Token service and renew my token and use the new token and make the same API call.
The general psudo is
try { GET /locations data } catch(401 Unauthorized) { POST /token and get renew Token --> This is another WebClient API call With New Token call again GET /locations and return value } catch (Another Exception) { throw Application Error }
Here is the Spring code that I am trying to do and it does not look like it is working. Any suggestion on how to do it.
public List<Location> getLocations(final User user) { if (null == user) { throw new ApplicationException("User cannot be null"); } if (null == user.getHoneyWellLinkToken()) { throw new ApplicationException(String.format("%s has not linked the account with Honeywell", user.getUsername())); } List<Location> locations = getLocationsAPI(user).block(); return locations; } private Mono<List<Location>> getLocationsAPI(final User user) { String endpoint = config.getApi().getLocationsEndpoint() .concat("?apikey=") .concat(config.getCredentials().getClientId()); return WebClient.builder().baseUrl(endpoint) .build() .get() .headers(httpHeaders -> httpHeaders.setBearerAuth(user.getHoneyWellLinkToken().getAccessToken())) .retrieve() .bodyToFlux(Location.class) .collectList() .doOnError(err -> { WebClient.builder().baseUrl(endpoint) .build() .get() .headers(httpHeaders -> httpHeaders.setBearerAuth(honeywellService.renewToken(user).block().getHoneyWellLinkToken().getAccessToken())) .retrieve().bodyToFlux(Location.class); }); }
This code is hosted on GitHub https://github.com/reflexdemon/home-use/blob/main/src/main/java/io/vpv/homeuse/service/HoneywellThermostatService.java
Advertisement
Answer
- Use
onErrorResume
instead ofdoOnError
- Do not
block
when renewing token
private Mono<List<Location>> getLocationsAPI(final User user) { String endpoint = config.getApi().getLocationsEndpoint() .concat("?apikey=") .concat(config.getCredentials().getClientId()); return getLocations(endpoint, user) .onErrorResume(err -> honeywellService.renewToken(user) .flatMap(newUser -> getLocations(endpoint, newUser))); } private Mono<List<Location>> getLocations(String endpoint, User user) { return WebClient.builder() .baseUrl(endpoint) .build() .get() .headers(httpHeaders -> httpHeaders.setBearerAuth(user .getHoneyWellLinkToken() .getAccessToken())) .retrieve() .bodyToFlux(Location.class) .collectList(); }
Also, it’s a good idea to use a single WebClient
instance instead of building a new one for each request.