
SOFTMOA TECHNOLOGY
Spring RequestParam defaultValue가 빈 문자열을 기본값으로 바꿀 때 처리 방법
소프트모아가 정리한 기술 기록입니다.
Contents
Spring MVC에서 limit=을 보냈는데 조회 개수가 20으로 들어온다면 @RequestParam의 defaultValue를 확인하세요. 기본값은 파라미터 누락뿐 아니라 빈 값에도 적용됩니다.[1] 누락은 허용하되 빈 입력은 오류로 처리하려면 기본값 치환 전에 문자열을 구분해야 합니다. 숫자로 변환된 20만 보고는 호출자가 20을 보냈는지 알 수 없습니다.
빈 값과 생략을 같은 뜻으로 받을 것인가
빈 값을 기본 조회로 받아도 된다면 defaultValue="20"이 맞습니다. 입력 오류라면 성공 응답이 문제를 가립니다. 기본값을 지정하면 required는 사실상 false이므로 required=true를 덧붙여 해결하지 못합니다.[1]
아래 예제의 계약은 ‘생략하면 20개, 보내면 1~100 사이의 십진 정수’입니다. 앞의 0, 부호, 공백은 허용하지 않습니다. 20과 100은 설명을 위한 운영 기준이며 Spring이 정한 제한이 아닙니다. /fallback은 기본값만 지정한 비교 대상이고 /strict가 이 계약을 검사합니다. String이 아닌 인자는 자동 형변환을 거치므로, 원래 입력을 구분할 단계에서는 String으로 받습니다.[2]
같은 입력으로 두 처리 방식을 실행하기
LimitCheck.java로 저장하세요. 웹 서버나 DB 없이 Java 25.0.4.1과 Spring Framework 6.2.12로 실행한 독립 MockMvc 검사입니다. 버전은 재현 조건이며 도입 권장 버전은 아닙니다.
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.http.HttpStatus;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@RestController
public class LimitCheck {
@GetMapping("/fallback")
public String fallback(
@RequestParam(name="limit", defaultValue="20") int limit) {
return String.valueOf(limit);
}
@GetMapping("/strict")
public String strict(
@RequestParam(name="limit", required=false) String raw) {
if (raw == null) return "20";
if (!raw.matches("[1-9][0-9]?|100")) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
}
return String.valueOf(Integer.parseInt(raw));
}
public static void main(String[] args) throws Exception {
var mvc = MockMvcBuilders.standaloneSetup(new LimitCheck()).build();
String[] inputs = {null, "", " ", "abc", "0", "1", "100", "101"};
String[][] expected = {
{"200:20", "200:20", "400:", "400:", "200:0", "200:1", "200:100", "200:101"},
{"200:20", "400:", "400:", "400:", "400:", "200:1", "200:100", "400:"}
};
String[] paths = {"/fallback", "/strict"};
for (int p = 0; p < paths.length; p++) {
for (int i = 0; i < inputs.length; i++) {
var req = get(paths[p]);
if (inputs[i] != null) req.param("limit", inputs[i]);
var res = mvc.perform(req).andReturn().getResponse();
String got = res.getStatus() + ":" + res.getContentAsString();
System.out.println(paths[p] + " [" + inputs[i] + "] -> " + got);
if (!got.equals(expected[p][i])) throw new AssertionError(got);
}
}
}
}
별도 작업 폴더의 lib에 Spring 6.2.12의 core·jcl·beans·context·expression·aop·web·webmvc·test JAR, Micrometer 1.14.12의 observation·commons JAR, jakarta.servlet-api 6.0.0 JAR를 넣고 실행한 명령입니다. 운영 애플리케이션의 의존성을 이 버전으로 교체하지 마세요.
javac -cp 'lib/*' LimitCheck.java
java -cp '.:lib/*' LimitCheck
200 응답에서도 입력과 반환값을 비교하세요
16개 검사의 상태와 본문이 모두 일치했습니다. [null]은 파라미터 누락, []는 빈 문자열, [ ]는 공백 한 칸입니다. 200:20은 상태 200과 본문 20입니다.
- 누락: 두 경로 모두 200:20입니다. 선택 항목이라는 계약은 유지됩니다.
- 빈 문자열: /fallback은 200:20, /strict는 400입니다. 조용히 기본값으로 바뀌던 입력을 거절합니다.
- 공백 한 칸과 abc: 두 경로 모두 400입니다. 빈 문자열의 기본값 동작을 모든 잘못된 문자열에 일반화하면 안 됩니다.
- 0과 101: /fallback은 각각 200:0, 200:101을 반환했지만 /strict는 400입니다. 정수 변환 성공이 조회 범위 검증을 대신하지 않습니다.
- 1과 100: 두 경로 모두 200이며 본문도 입력 숫자와 같습니다. 양쪽 경계값이 허용됩니다.
수정 핵심은 required=false만이 아니라 raw가 null일 때만 기본값을 주고, 나머지는 정규식으로 거르는 순서입니다. 이 검사에서 400 응답 본문은 비어 있습니다. 서비스의 공통 오류 응답 형식까지 검증한 결과는 아닙니다.
전역 바인더가 빈 값을 null로 바꾸는 프로젝트는 실제 MVC 설정으로 다시 검사하세요. 중복 파라미터, JSON, 보안 필터는 검사 범위 밖입니다. 과거 요청을 복구하는 수정도 아닙니다. 기존 호출자가 새로 400을 받는지 배포 전에 확인하세요.
확인한 공식 문서
업무용 웹 시스템 개발 안내는 softmoa.com에서 확인할 수 있습니다.
소프트모아는 해당 시스템을 구축합니다. 문의하기
