Spring Boot의 @ControllerAdvice와 @RestControllerAdvice 사용법을 정리했습니다. 공통 예외 처리, basePackages, basePackageClasses, assignableTypes, annotations 옵션과 실무에서 많이 사용하는 프로젝트 구조까지 예제와 함께 자세히 설명합니다.

Spring Boot @ControllerAdvice 사용법 완벽 가이드
Spring MVC에서 여러 Controller에 동일한 기능을 적용해야 하는 경우가 자주 있습니다. 대표적인 예가 공통 예외 처리(Exception Handling)입니다.
매번 Controller마다 동일한 예외 처리 코드를 작성하면 코드가 중복되고 유지보수가 어려워집니다. 이러한 문제를 해결하기 위해 사용하는 것이 바로 **@ControllerAdvice**입니다.
많은 개발자가 @ControllerAdvice를 예외 처리용으로만 알고 있지만, 실제로는 공통 Model 데이터 추가와 DataBinder 설정까지 담당할 수 있습니다.
이 글에서는 @ControllerAdvice의 동작 원리부터 basePackages, basePackageClasses, assignableTypes, annotations 옵션, 그리고 실무에서 가장 많이 사용하는 프로젝트 구성 방법까지 함께 정리합니다.
@ControllerAdvice란?
@ControllerAdvice는 여러 Controller에 공통 기능을 적용하기 위한 Spring MVC 어노테이션입니다.
대표적으로 다음 기능을 제공합니다.
- 공통 예외 처리 (@ExceptionHandler)
- 공통 Model 데이터 추가 (@ModelAttribute)
- 공통 DataBinder 설정 (@InitBinder)
기본 형태는 매우 단순합니다.
@ControllerAdvice
public class GlobalControllerAdvice {
}
별도의 옵션을 지정하지 않으면 프로젝트에 등록된 모든 @Controller에 적용됩니다.
@RestController 역시 내부적으로 @Controller를 포함하고 있으므로 함께 적용됩니다.
예를 들어 다음과 같은 프로젝트 구조라면
com.example
├── controller
│ UserController
│ AdminController
│ ProductController
│
└── advice
GlobalControllerAdvice
모든 Controller가 Advice의 영향을 받습니다.
@RestControllerAdvice란?
REST API에서는 HTML View가 아닌 JSON을 반환하는 경우가 대부분입니다.
이때 사용하는 것이 @RestControllerAdvice입니다.
@RestControllerAdvice
public class ApiAdvice {
}
이는 다음 코드와 동일합니다.
@ControllerAdvice
@ResponseBody
public class ApiAdvice {
}
즉, 예외 처리 결과를 View가 아닌 HTTP Response Body(JSON)로 반환합니다.
MVC 프로젝트에서는 @ControllerAdvice, REST API 프로젝트에서는 @RestControllerAdvice를 사용하는 것이 일반적입니다.
basePackages 옵션
가장 많이 사용하는 옵션입니다.
@ControllerAdvice(
basePackages = "com.example.controller.user"
)
public class UserControllerAdvice {
}
이 경우 다음 Controller에만 적용됩니다.
com.example.controller.user.UserController
com.example.controller.user.MemberController
반면 아래 Controller에는 적용되지 않습니다.
com.example.controller.admin.AdminController
여러 패키지 지정
@ControllerAdvice(
basePackages = {
"com.example.user",
"com.example.admin"
}
)
public class CommonAdvice {
}
두 패키지 모두 적용됩니다.
하위 패키지도 적용
basePackages는 지정한 패키지뿐 아니라 하위 패키지까지 모두 포함합니다.
예를 들어
@ControllerAdvice(
basePackages = "com.demo.user"
)
다음은 모두 적용됩니다.
com.demo.user.UserController
com.demo.user.member.MemberController
com.demo.user.api.ApiController
하지만
com.demo.admin.AdminController
에는 적용되지 않습니다.
basePackageClasses 옵션
문자열 대신 클래스를 기준으로 패키지를 지정할 수 있습니다.
@ControllerAdvice(
basePackageClasses = UserController.class
)
public class UserControllerAdvice {
}
Spring이 UserController가 속한 패키지를 자동으로 찾아 적용합니다.
장점은 다음과 같습니다.
- 문자열 오타 방지
- IDE 리팩토링 지원
- 타입 안전(Type-safe)
- 패키지 이동 시 자동 변경
실무에서는 basePackages보다 basePackageClasses를 선호하는 팀도 많습니다.
assignableTypes 옵션
특정 Controller만 대상으로 지정할 수도 있습니다.
@ControllerAdvice(
assignableTypes = {
UserController.class,
AdminController.class
}
)
public class MyAdvice {
}
다음 두 Controller만 적용됩니다.
- UserController
- AdminController
또한 상속 관계도 포함됩니다.
public class BaseController {
}
public class UserController extends BaseController {
}
@ControllerAdvice(
assignableTypes = BaseController.class
)
이 경우 UserController도 함께 적용됩니다.
annotations 옵션
특정 어노테이션이 붙은 Controller만 대상으로 지정할 수 있습니다.
@ControllerAdvice(
annotations = RestController.class
)
public class ApiAdvice {
}
그러면 모든 @RestController에만 적용됩니다.
@Controller에는 적용되지 않습니다.
커스텀 어노테이션과 함께 사용하는 것도 가능합니다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
public @interface PublicApi {
}
@ControllerAdvice(
annotations = PublicApi.class
)
public class PublicApiAdvice {
}
ExceptionHandler 사용 예제
Controller
@GetMapping("/test")
public String test() {
throw new RuntimeException("에러 발생");
}
Advice
@ControllerAdvice(
basePackages = "com.example.user"
)
public class UserControllerAdvice {
@ExceptionHandler(RuntimeException.class)
public String runtime(RuntimeException e, Model model){
model.addAttribute("message", e.getMessage());
return "error/runtime";
}
}
결과
error/runtime.html
페이지가 출력됩니다.
REST API 예외 처리
Controller
@RestController
public class UserApi {
@GetMapping("/api/test")
public String test(){
throw new IllegalArgumentException("잘못된 요청입니다.");
}
}
Advice
@RestControllerAdvice(
basePackages = "com.example.api"
)
public class ApiAdvice {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> error(Exception e){
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(e.getMessage());
}
}
응답
HTTP 400
Body
잘못된 요청입니다.
@ModelAttribute 활용
공통 Model 데이터를 추가할 수도 있습니다.
@ControllerAdvice
public class CommonAdvice {
@ModelAttribute
public void common(Model model){
model.addAttribute("siteName", "My Blog");
}
}
모든 Controller의 Model에 siteName이 자동으로 추가됩니다.
헤더, 푸터, 사이트명, 로그인 사용자 정보 등을 공통으로 전달할 때 자주 사용됩니다.
@InitBinder 활용
공통 데이터 바인딩도 가능합니다.
@ControllerAdvice
public class BinderAdvice {
@InitBinder
public void init(WebDataBinder binder){
// 공통 바인딩 설정
}
}
날짜 형식이나 커스텀 Converter 등록 등에 활용합니다.
ExceptionHandler 우선순위
예외가 발생하면 Spring은 다음 순서로 처리합니다.
- Controller 내부 @ExceptionHandler
- @ControllerAdvice
- @Order 우선순위
예를 들어
@RestController
public class UserController {
@ExceptionHandler(RuntimeException.class)
public String local(){
return "local";
}
}
@ControllerAdvice
public class GlobalAdvice {
@ExceptionHandler(RuntimeException.class)
public String global(){
return "global";
}
}
위 경우에는 Controller 내부 Handler가 먼저 실행됩니다.
여러 Advice가 존재한다면 @Order를 이용해 우선순위를 지정할 수 있습니다.
@ControllerAdvice
@Order(1)
public class ApiAdvice {
}
@ControllerAdvice
@Order(2)
public class GlobalAdvice {
}
숫자가 작을수록 우선순위가 높습니다.
@ControllerAdvice가 처리하지 못하는 예외
많은 개발자가 오해하는 부분입니다.
@ControllerAdvice는 Controller를 거쳐 처리되는 요청에서 발생한 예외를 대상으로 합니다.
다음과 같은 예외는 처리하지 않습니다.
- Filter에서 발생한 예외
- Spring Security Filter Chain에서 발생한 예외
- DispatcherServlet 이전 단계에서 발생한 예외
이러한 경우에는 Filter, AuthenticationEntryPoint, AccessDeniedHandler 등에서 별도로 처리해야 합니다.
왜 basePackages가 필요한가?
프로젝트가 커질수록 영역별 예외 처리 정책이 달라집니다.
예를 들어
- 관리자 페이지 → 관리자 전용 에러 화면
- 사용자 페이지 → 일반 사용자 에러 화면
- REST API → JSON 응답
- 모바일 API → 모바일 전용 응답
이를 하나의 Advice에서 처리하면 관리가 어려워집니다.
그래서 영역별로 Advice를 분리하는 것이 일반적입니다.
실무에서 가장 많이 사용하는 프로젝트 구조
com.example
│
├── controller
│ ├── admin
│ ├── user
│ └── api
│
├── advice
│ ├── GlobalExceptionAdvice
│ ├── AdminExceptionAdvice
│ ├── UserExceptionAdvice
│ └── ApiExceptionAdvice
@ControllerAdvice(
basePackages = "com.example.controller.admin"
)
public class AdminExceptionAdvice {
}
@ControllerAdvice(
basePackages = "com.example.controller.user"
)
public class UserExceptionAdvice {
}
@RestControllerAdvice(
basePackages = "com.example.controller.api"
)
public class ApiExceptionAdvice {
}
@ControllerAdvice
public class GlobalExceptionAdvice {
}
이 구조를 사용하면 관리자, 사용자, REST API 각각 독립적인 예외 처리 정책을 유지할 수 있어 유지보수가 쉬워집니다.
@ControllerAdvice 주요 옵션 비교
옵션설명대표 사용 사례
| basePackages | 지정한 패키지와 하위 패키지에 적용 | 사용자/관리자/API 영역 분리 |
| basePackageClasses | 특정 클래스의 패키지를 기준으로 적용 | 타입 안전하게 패키지 지정 |
| assignableTypes | 특정 Controller 또는 하위 타입에만 적용 | 일부 Controller만 별도 처리 |
| annotations | 특정 어노테이션이 붙은 Controller에 적용 | REST API, 커스텀 어노테이션 |
어떤 옵션을 선택해야 할까?
상황추천
| 일반적인 프로젝트 | basePackages |
| 리팩토링이 잦은 프로젝트 | basePackageClasses |
| 일부 Controller만 처리 | assignableTypes |
| 어노테이션 기준으로 구분 | annotations |
실무에서는 basePackages 또는 basePackageClasses를 가장 많이 사용합니다.
FAQ
Q. @ControllerAdvice와 @RestControllerAdvice의 차이는 무엇인가요?
@RestControllerAdvice는 @ControllerAdvice에 @ResponseBody가 추가된 형태입니다. MVC에서는 View를 반환하고, REST API에서는 JSON을 반환할 때 사용합니다.
Q. 여러 개의 ControllerAdvice를 사용할 수 있나요?
가능합니다. @Order를 이용해 실행 우선순위를 지정할 수 있으며, 패키지나 어노테이션별로 역할을 분리하는 것이 일반적입니다.
Q. Filter에서 발생한 예외도 처리할 수 있나요?
아닙니다. @ControllerAdvice는 Controller 계층에서 발생한 예외를 처리합니다. Filter나 Spring Security에서 발생한 예외는 별도의 예외 처리 방식이 필요합니다.
Q. 실무에서는 어떤 옵션을 가장 많이 사용하나요?
대부분 basePackages 또는 basePackageClasses를 사용하며, 관리자 페이지와 REST API를 각각 분리해 관리하는 방식이 가장 많이 사용됩니다.
마무리
@ControllerAdvice는 단순한 예외 처리 기능을 넘어 Spring MVC에서 공통 정책을 관리하는 핵심 기능입니다.
프로젝트 규모가 커질수록 하나의 Advice에 모든 기능을 몰아넣기보다 관리자, 사용자, API 영역별로 분리하는 것이 유지보수성과 확장성 측면에서 훨씬 유리합니다.
특히 실무에서는 basePackages 또는 basePackageClasses를 이용해 적용 범위를 명확하게 구분하고, REST API는 @RestControllerAdvice를 사용하여 JSON 기반의 일관된 예외 응답을 제공하는 방식을 가장 많이 사용합니다.
'실무개발' 카테고리의 다른 글
| Java Enum 사용법 총정리, Spring Boot Entity와 JPA 실무 예제 (1) | 2026.08.05 |
|---|---|
| JavaScript event.target과 this 차이점, HTML dialog 클릭 이벤트 완벽 정리 (0) | 2026.07.30 |
| JWT 인증 실패와 권한 오류 처리 전략, 401과 403을 올바르게 구분하는 방법 (0) | 2026.07.28 |
| Spring @ModelAttribute 사용법, POST 요청에서도 자동 바인딩될까? (0) | 2026.07.24 |
| VS Code + Spring Boot + Gradle에서 외부 JAR 파일 적용하는 방법 (0) | 2026.07.24 |
