code: protected <T> T handleRetryExhausted(RecoveryCallback<T> recoveryCallback,
  			RetryContext context, RetryState state) throws Throwable {
  		context.setAttribute(RetryContext.EXHAUSTED, true);
  		if (state != null && !context.hasAttribute(GLOBAL_STATE)) {
  			this.retryContextCache.remove(state.getKey());
  		}
  		if (recoveryCallback != null) {
  			T recovered = recoveryCallback.recover(context);
  			context.setAttribute(RetryContext.RECOVERED, true);
  			return recovered;
  		}
  		if (state != null) {
  			this.logger
  					.debug("Retry exhausted after last attempt with no recovery path.");
  			rethrow(context, "Retry exhausted after last attempt with no recovery path");
  		}
  		throw wrapIfNecessary(context.getLastThrowable());
  	}
docs: Actions to take after final attempt has failed. If there is state clean up the
  cache. If there is a recovery callback, execute that and return its result.
  Otherwise throw an exception.
  @param recoveryCallback the callback for recovery (might be null)
  @param context the current retry context
  @param state the {@link RetryState}
  @param <T> the type to classify
  @throws Exception if the callback does, and if there is no callback and the state
  is null then the last exception from the context
  @throws ExhaustedRetryException if the state is not null and there is no recovery
  callback
  @return T the payload to return
  @throws Throwable if there is an error