【Spring】RestTemplateが投げる例外クラスまとめ

## 概要
SpringのRestTemplateを使ったAPIコールにおいて、エラー時に投げられる例外クラスの種類をまとめた。
背景としては、SpringのRestTemplateを使っていて、これからエラー時の処理を実装するにあたって必要だと思ったので。

調査方法としては、RestTemplateのソースコードを漁ってみて、例外クラスのjavadocを見たり、実際にthrowされている箇所を見たりしてわかったことをまとめた。

## 環境
spring-web-4.3.16.RELEASE

## 例外クラスまとめ
RestTemplateの中には全部で7種類の例外が定義されていた。
それらをクラス図っぽくしてみたのが下記。

![RestTemplate (2).png](https://qiita-image-store.s3.amazonaws.com/0/48525/def61a11-f1e7-8422-7cdd-ec1ec32c5bf1.png)

ひとつずつ見てみる。

### RestClientException

javadocは以下

```java
/**
 * Base class for exceptions thrown by {@link RestTemplate} whenever it encounters
 * client-side HTTP errors.
 */
public class RestClientException extends NestedRuntimeException {
```

RestTemplateの実行において、エラーが発生したときにRestTemplateが投げるベースの例外クラス。
RestTemplateに関する例外全ての親。

RestClientExceptionはNestedRuntimeExceptionを継承しているが、RestTemplateの話からはずれるのでここでは取り上げない。

### ResourceAccessException

javadocは以下

```java
/**
 * Exception thrown when an I/O error occurs.
 */
public class ResourceAccessException extends RestClientException {
```

IO系のエラーが発生したら投げられる例外クラス。
コードを見た感じでは、リクエスト時に`IOException`が投げられたらcatchして`ResourceAccessException`をthrowしてるっぽい。

### RestClientResponseException

javadocは以下

```java
/**
 * Common base class for exceptions that contain actual HTTP response data.
 */
public class RestClientResponseException extends RestClientException {
```

HTTPのレスポンスに関する例外を扱うクラス。
どうやら下記で述べる`UnknownHTTPStatusCodeException`と`HTTPStatusCodeException`に継承されているだけで、このクラスが名指しでthrowされることは無さそうで、だったら抽象クラスでもいいんじゃ？と思いましたが、どうなんでしょう。

### UnknownHTTPStatusCodeException

javadocは以下

```java
/**
 * Exception thrown when an unknown (or custom) HTTP status code is received.
 */
public class UnknownHttpStatusCodeException extends RestClientResponseException {
```

未知のHTTPステータスコードが返されたときに投げられる。
具体的には、返されたステータスコードが、`org.springframework.http.HttpStatus`のenumで定義されているステータスコード達の中に含まれていなかったら投げられる。

### HTTPStatusCodeException

javadocは以下

```java
/**
 * Abstract base class for exceptions based on an {@link HttpStatus}.
 */
public abstract class HttpStatusCodeException extends RestClientResponseException {
```

HTTPのレスポンスのステータスコードに関するException。
これは抽象クラスで、下記で述べる`HTTPServerErrorException`と`HTTPClientErrorException`に継承されている。

### HTTPServerErrorException

javadocは以下

```java
/**
 * Exception thrown when an HTTP 5xx is received.
 */
public class HttpServerErrorException extends HttpStatusCodeException {
```

5xx系のステータスコードが返されたときに投げられる。

### HTTPClientErrorException

javadocは以下

```java
/**
 * Exception thrown when an HTTP 4xx is received.
 */
public class HttpClientErrorException extends HttpStatusCodeException {
```

4xx系のステータスコードが返されたときに投げられる。

### エラーハンドリングの方針
以上のルールを頭に入れたうえで、catchしたい例外をcatchすればよさそう。
RestTemplateに関する例外を全てcatchしたければ、`RestClientException`をcatchすればよい。

### おまけ：エラー系のステータスコードが返ったときの挙動を少し追ってみる

まずエラーかどうかの判定するのが下記のメソッド
ステータスコードが4xx系, もしくは5xx系だったらtrueが返るみたい
未知のステータスコードの場合、つまり`org.springframework.http.HttpStatus`のenumで定義されているステータスコード達の中に含まれていなかったら、`getHttpStatusCode()`の中で`UnknownHTTPStatusCodeException`が投げられる。

```java
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
	try {
		return hasError(getHttpStatusCode(response));
	}
	catch (UnknownHttpStatusCodeException ex) {
		return false;
	}
}

protected boolean hasError(HttpStatus statusCode) {
	return (statusCode.series() == HttpStatus.Series.CLIENT_ERROR ||
				statusCode.series() == HttpStatus.Series.SERVER_ERROR);
}
```

そして、`hasError`がtrueを返したら呼ばれるのが下記のメソッド。

4xx系の場合は`HTTPClientErrorException`、5xx系の場合は`HTTPServerErrorException`が投げられる。

それ以外の場合は`RestClientException`が投げられるみたいだけど、そもそも未知のステータスコードの場合は, `hasError()`の時点で`getHttpStatusCode`を呼んだ時点で例外が投げられるのでは？ここが呼ばれることはあるのか？ってなってるのが今。

```java
@Override
public void handleError(ClientHttpResponse response) throws IOException {
	HttpStatus statusCode = getHttpStatusCode(response);
	switch (statusCode.series()) {
		case CLIENT_ERROR:
			throw new HttpClientErrorException(statusCode, response.getStatusText(),
					response.getHeaders(), getResponseBody(response), getCharset(response));
		case SERVER_ERROR:
			throw new HttpServerErrorException(statusCode, response.getStatusText(),
					response.getHeaders(), getResponseBody(response), getCharset(response));
		default:
			throw new RestClientException("Unknown status code [" + statusCode + "]");
	}
}
```