Spring Boot : Default Exception Handling : 기본 에러 응답
Tags: java, springboot, exceptionhandling, errorresponse
Published: 2024-04-05 13:48:32

## 개발 환경

- Java 21
- Spring Boot 3.2.4


## 기본 에러 응답

Spring Boot에서 아무 설정도 하지 않고 RestController 구성하여 예외 발생시키면 다음과 같은 형태의 response body를 응답한다.

```json
{
    "timestamp": "2024-04-05T13:37:51.440+00:00",
    "status": 405,
    "error": "Method Not Allowed",
    "path": "/"
}
```


## 어디에서 기본 에러 응답 body를 만드나?

어디에서 어떤 과정을 거쳐 이런 형태의 에러 응답을 하는지 궁금하여 간략하게 정리해보았다.
아래에 그 과정에서 알게된 몇 가지 설정도 적어두었다.


### 아래에서 1차 처리

```java
package org.springframework.web.servlet.mvc.support;

public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionResolver {

    @Override
    @Nullable
    protected ModelAndView doResolveException(
            HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
        // ...
    }
}
```


### 에러 응답하는 곳

```java
package org.springframework.boot.autoconfigure.web.servlet.error;

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        HttpStatus status = getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity<>(status);
        }
        Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
        return new ResponseEntity<>(body, status);
    }
}
```


### response body 만드는 곳

```java
package org.springframework.boot.autoconfigure.web.servlet.error;

@Order(Integer.MIN_VALUE)
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {

    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = this.getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));
        if (!options.isIncluded(Include.EXCEPTION)) {
            errorAttributes.remove("exception");
        }
    
        if (!options.isIncluded(Include.STACK_TRACE)) {
            errorAttributes.remove("trace");
        }
    
        if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) {
            errorAttributes.remove("message");
        }
    
        if (!options.isIncluded(Include.BINDING_ERRORS)) {
            errorAttributes.remove("errors");
        }
    
        return errorAttributes;
    }
}
```


## Spring Boot DevTools의 영향

DevTools를 포함시키면 아래 설정들이 자동으로 들어가서 에러 응답이 ~~화려해진다.~~사용자에게 노출하지 않고 싶은 필드도 자동으로 추가된다.

```properties
server.error.include-binding-errors=always
server.error.include-message=always
server.error.include-stacktrace=always
```

gradle에서 다음과 같이 추가되어 있다면, 로컬에서 `gradle bootRun`으로 실행할 때에만 포함되고, jar 빌드할 때에는 포함되지 않는다.
즉, 별다른 설정을 하지 않아도 운영 환경에는 적용되지 않는다.

```gradle
dependencies {
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
}
```

maven에서도 spring-boot-maven-plugin이 똑같은 역할을 해준다.


만약 개발 환경에서 운영 환경의 에러 응답을 확인하고 싶다면 다음 두 가지 방법 중 하나를 적용하면 된다.

### 방법 1

아래 설정을 application.properties에 포함시킨다.
DevTools가 없을 때의 기본 값이다.
- 장점: 에러 응답에 관한 것만 적용할 수 있다.

```properties
server.error.include-binding-errors=never
server.error.include-message=never
server.error.include-stacktrace=never
```

### 방법 2

아래 설정을 application.properties에 포함시킨다.
- 장점: devtools로 인해 적용되는 properties를 한번에 끌 수 있다.

```properties
spring.devtools.add-properties=false
```