junome

@RestControllerAdvice

아래는 개발 블로그에 바로 올려도 될 퀄리티로 정리한 @RestControllerAdvice 완전 이해 가이드야. Spring Boot + Kotlin/Java 개발자가 꼭 알아야 할 핵심 개념들만, 실무 스타일로 정리했어.

🚀 Spring Boot에서 @RestControllerAdvice 완벽 가이드

— “컨트롤러 예외 처리의 정석”

웹 애플리케이션을 만들다 보면 컨트롤러마다 이런 코드가 반복된다.

try { ... } catch (Exception e) { jsonData.put("result", "fail"); }

문제는? • 모든 API마다 try-catch 반복 → 지저분함 • 예외가 각각 다르게 처리됨 → 일관성 없음 • 에러 메시지 포맷 관리 불가 → 클라이언트 혼란 • 유지보수 난이도 ↑

Spring Boot에서는 이런 문제를 해결하기 위해 **전역 컨트롤러 예외 처리기(Global Exception Handler)**를 제공한다. 그 핵심이 바로 @RestControllerAdvice 이다.

1️⃣ @RestControllerAdvice란 무엇인가?

한마디로,

컨트롤러에서 발생하는 모든 예외를 한 곳에서 처리하는 전역 예외 처리기

다른 말로 하면… • 컨트롤러 예외를 하나의 클래스에서 처리 • API 응답 포맷을 통일 • 중복 try-catch 제거 • 공통 로깅·추가 작업 가능

✔ @ControllerAdvice + @ResponseBody 의 조합

@RestControllerAdvice는 아래 두 어노테이션을 합쳐 놓은 것과 같다.

어노테이션 의미 @ControllerAdvice 전역적으로(모든 컨트롤러에) 적용 @ResponseBody 반환 객체를 JSON으로 응답

그래서 API 개발에서는 사실상 필수라고 보면 된다.

2️⃣ 예외 처리의 기본 구조

예외 핸들러는 아래처럼 만든다.

@RestControllerAdvice class GlobalExceptionHandler {

@ExceptionHandler(Exception::class)
fun handleException(e: Exception): ApiResponse<Unit> {
    return ApiResponse(
        success = false,
        message = "시스템 오류가 발생했습니다.",
        errorCode = "INTERNAL_ERROR"
    )
}

}

3️⃣ 동작 방식 (Internals)

Spring MVC는 컨트롤러에서 예외가 발생하면 다음 순서로 처리한다: 1. 컨트롤러 내부에서 try-catch로 처리? → 여기서 끝. 2. 처리되지 않은 예외 발생 → DispatcherServlet이 @ExceptionHandler를 탐색한다. 3. 가장 맞는 타입의 예외 핸들러 메서드를 호출한다.

즉, 아래처럼 다양한 예외를 구분할 수도 있다.

@ExceptionHandler(IllegalArgumentException::class) fun handleBadRequest(e: IllegalArgumentException): ApiResponse { ... }

@ExceptionHandler(NoSuchElementException::class) fun handleNotFound(e: NoSuchElementException): ApiResponse { ... }

4️⃣ 실무에서 가장 많이 쓰는 예외 유형별 처리

✔ 잘못된 요청 (400)

@ExceptionHandler(IllegalArgumentException::class) fun badRequest(e: IllegalArgumentException) = ApiResponse(false, message = e.message ?: "잘못된 요청입니다.")

✔ 찾을 수 없음 (404)

@ExceptionHandler(NoSuchElementException::class) fun notFound(e: NoSuchElementException) = ApiResponse(false, message = "데이터를 찾을 수 없습니다.")

✔ 서버 오류 (500)

@ExceptionHandler(Exception::class) fun internal(e: Exception) = ApiResponse(false, message = "시스템 오류가 발생했습니다.")

이렇게 해두면 컨트롤러는 예외를 던지기만 해도 OK:

if (list.isEmpty()) throw NoSuchElementException()

5️⃣ 실제 실무 스타일 예제 (Response Wrapper 포함)

ApiResponse라는 공통 응답 DTO를 만들어서 통합 관리한다.

data class ApiResponse( val success: Boolean, val data: T? = null, val message: String? = null, val errorCode: String? = null )

그리고…

@RestControllerAdvice class GlobalExceptionHandler {

@ExceptionHandler(Exception::class)
fun handle(e: Exception): ResponseEntity<ApiResponse<Unit>> {

    // 로그
    e.printStackTrace()

    return ResponseEntity
        .status(HttpStatus.INTERNAL_SERVER_ERROR)
        .body(
            ApiResponse(
                success = false,
                message = "시스템 오류가 발생했습니다.",
                errorCode = "INTERNAL_ERROR"
            )
        )
}

}

6️⃣ 기존 코드가 어떻게 깔끔해지는지 비교해보기

🔥 Before (try-catch 반복)

@RequestMapping(...) public Map<String, Object> readSomething() { Map<String,Object> json = new HashMap<>(); try { json.put("data", service.read()); json.put("result", "ok"); } catch (Exception e) { e.printStackTrace(); json.put("message", "오류"); json.put("result", "fail"); } return json; }

🌟 After (@RestControllerAdvice 적용)

@GetMapping("/something") fun readSomething(): ApiResponse { val data = service.read() return ApiResponse(true, data) }

실수로 예외가 발생해도: • try-catch 필요 없음 • 응답 포맷 자동 정리 • 로그도 자동 • 일관성 유지

7️⃣ 흔히 쓰는 고급 패턴들

✔ 비즈니스 예외 직접 생성하기

class BizException( val code: String, override val message: String ) : RuntimeException(message)

핸들러:

@ExceptionHandler(BizException::class) fun handleBizException(e: BizException) = ApiResponse(false, message = e.message, errorCode = e.code)

컨트롤러에서는 그냥 던지면 됨:

if (!valid) throw BizException("VALID_FAIL", "유효하지 않은 값입니다.")

8️⃣ 왜 실무에서 무조건 쓰는가?

장점 설명 중복 제거 모든 try-catch 제거 유지보수성 에러 정책 변경이 매우 쉬움 일관성 모든 API 응답 패턴을 통일 확장성 예외 종류별 처리 가능 보안 내부 에러 스택을 클라이언트에 노출하지 않음

특히 MES/ERP 같이 화면이 수십~수백개인 시스템에서는 한 줄당 1초씩만 절약되어도 몇 시간 절약됨.

9️⃣ 실제 현업에서 사용하는 패턴 조합 • @RestControllerAdvice • @ExceptionHandler • 공통 Response DTO (ApiResponse) • Custom BizException • ValidationException 처리 • Authentication/Authorization Exception 처리

이 세트를 템플릿화하면 새로운 프로젝트 시작할 때 바닥공사 80%는 이미 끝난 상태가 된다.

🔟 마무리

@RestControllerAdvice는 **“Spring Boot API 개발의 필수 인프라”**다.

이걸 적절히 설계해두면: • 컨트롤러는 “정상 로직”만 작성 • 예외는 자동 관리 • 응답도 자동 통일 • 유지보수 시간 절약 • 코드 가독성 상승

결국 개발자가 본질적인 업무 로직에 집중할 수 있게 해주는 핵심 기술이다.

원하면: 🛠 Kotlin + Spring Boot 기반으로 “전역 예외처리 모듈 세트(파일 3개)”를 완성 형태로 생성해서 제공해줄게. Junha MES 프로젝트 형태에 맞추어 바로 사용할 수 있는 수준으로.