Skip to content

RestTemplate to RestClient migration guide for exceptions #35620

@wtell400

Description

@wtell400

I face a challenge when migrating from RestTemplate to RestClient. The documentation mentions the changes in calls, but I have specific exception handling dealing that I have to return to the client (e.g. timeouts, invalid urls, invalid ssl certificate). These corner cases are difficult to test during this migration, and I am afraid I might miss some new ones.

Could you provide a migration guide for the exceptions thrown by RestTemplate, to the exceptions thrown by RestClient? Has anything changed?

  • Are RestTemplate's HttpStatusCodeException and ResourceAccessException still thrown by RestClient?
  • Are the underlying exceptions still retrievable?

My project started with Spring Boot 1.5.4 and is now on 3.5.x, so I might also have missed some changes in the meantime.

My code looks mostly like this:

record HttpRequest<T>(HttpMethod method, String url, Map<String, String> headers, T body) {}
record HttpResponse(HttpStatusCode httpStatusCode, Map<String, String> headers, String body) {}

public HttpResponse sendRequest(HttpRequest<?> request)
{
	try
	{
		HttpHeaders headers = new HttpHeaders();
		request.getHeaders().forEach(headers::set);
		HttpEntity<?> requestEntity = new HttpEntity<>(request.getBody(), headers);
		
		ResponseEntity<String> response = restTemplate.exchange(request.getUrl(), request.getMethod(), requestEntity, String.class);
		
		return new HttpResponse(response.getStatusCode(), response.getHeaders().toSingleValueMap(), response.getBody());
	}
	catch (HttpStatusCodeException e)
	{
		HttpStatusCode status = e.getStatusCode();
		
		switch (status.value())
		{
			case 400: { /*...*/}
			case 401: { /*...*/}
			//etc.
		}
	
		return new HttpResponse(status, e.getResponseHeaders().toSingleValueMap(), e.getResponseBodyAsString());
	}
	catch (ResourceAccessException e)
	{
		Class<?> exceptionClass = e.getRootCause() != null ? e.getRootCause().getClass() : null;
		final HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR; 

		ObjectNode body = objectMapper.createObjectNode();

		if (isA(java.net.UnknownHostException.class, exceptionClass))
		{
			// UnknownHostException - Invalid URL (DNS resolve failed)
			body.put("reason", "Invalid URL");
		}
		else if (isA(java.net.ConnectException.class, exceptionClass))
		{
			// ConnectException - Connection refused
			body.put("reason", "Server not connecting. Connection refused.");
		}
		else if (isA(org.apache.hc.client5.http.ConnectTimeoutException.class, exceptionClass))
		{
			// ConnectTimeoutException - Server not connecting within timeout
			body.put("reason", "Server not connecting in a timely fashion");
			body.put("timeout", restProperties.getConnectionRequestTimeout());
		}
		else if (isA(java.net.SocketTimeoutException.class, exceptionClass))
		{
			// SocketTimeoutException - Server not responding within timeout
			body.put("reason", "Server not responding in a timely fashion");
			body.put("timeout", restProperties.getSocketTimeout());
		}
		else if (isA(javax.net.ssl.SSLPeerUnverifiedException.class, exceptionClass))
		{
			// SSLPeerUnverifiedException - Certificate not valid for this domain
			body.put("reason", "Certificate not valid for this domain");
		}
		else
		{
			// Other
			body.put("reason", e.getRootCause() != null ? e.getRootCause().getMessage() : "Unknown root cause");
		}

		return new HttpResponse(httpStatus, null, body.toString());
	}
}

Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)type: documentationA documentation task

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions