스프링 MVC에서 리스트 파라미터 바인딩시 주의할 점

스프링 MVC의 컨트롤러에는 콤마(,)로 구분된 파라미터를 리스트로 바인딩 해주는 편리한 기능이 있다. ID 목록 등을 요청으로 받을 때 자주 사용하는 기능이다.

@PathViriable@RequestParam 모두 사용할 수 있으며, 사용법은 아래와 같이 간단한다. 바인딩 될 파라미터 변수를 List로 선언해주면 된다.

1
2
3
4
5
@GetMapping("/items/{ids}")
public List<Long> getItems(@PathVariable List<Long> ids) {
System.out.println(ids);
return ids;
}

클라이언트에서는 아래와 같이 콤마로 구분된 파라미터로 요청할 수 있다.

1
http://localhost:8080/items/1,2,3

결과 화면
결과 화면

그런데 클라이언트에서 아래와 같이 호출했을때는 어떻게 바인딩이 될까?

1
http://localhost:8080/items/1,,3,4

내가 예상한 결과는 아래와 같았다.

내가 예상한 결과

즉, 1번 아이디 뒤에는 값이 비어있으므로 비어있는 부분은 바인딩 되지 않고 존재하는 값인 1, 3, 4 번만 바인딩이 될것이라 생각했다. 그러나 실제 결과는 아래와 같았다.

실제 결과

비어있는 값은 null 로 바인딩이 되었다. 물론 클라이언트 측에서 제대로된 값으로 요청을 하면 좋겠지만 저런식으로 요청이 들어오게 되면 서버에서는 NPE(Null Pointer Exception)가 발생할 수 있다. (실제 운영중인 서버에서 같은 이유로 NPE가 난 적이 있다.)

아래는 NPE가 발생하는 코드의 예이다.

1
2
3
4
5
6
7
@GetMapping("/items/{ids}")
public List<Long> getItems(@PathVariable List<Long> ids) {
for (Long id : ids) {
System.out.println(id.toString());
}
return ids;
}

실제 ids 값을 디버깅해 보면 다음처럼 보인다.
ids 값 디버깅
리스트 요소에 null이 포함되어 있어도 실제 사이즈가 4이고 루프도 4번 돌기 때문에 두번째 id를 출력하는 과정에서 NPE가 발생하게된다.

이처럼 리스트 파라미터를 바인딩 할때는 중간 값으로 null이 들어올 수 있음을 주의해야하고 적절한 null 처리가 필요하다. 혹은 파라미터를 사용하기 전에 null요소를 미리 제거하고 사용하는 방법도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
@GetMapping("/items/{ids}")
public List<Long> getItems(@PathVariable List<Long> ids) {
final List<Long> nullRemovedIds = ids.stream()
.filter(Objects::nonNull)
.collect(Collectors.toList());
for (Long id : nullRemovedIds) {
System.out.println(id.toString());
}
return nullRemovedIds;
}
Share