QueryString 처리 방식 비교#
Spring에서 QueryString을 처리하는 두 가지 방식을 비교해보겠습니다.
방식 1: Object로 받기 (ParameterObject)#
@Operation(tags = {"swagger"})
@GetMapping("/hello/parameters1")
public ResponseEntity<List<ResponseTest>> parameterObjectTest(ParameterObjectReq req) {
ResponseTest response = new ResponseTest(req.email(), req.password(), req.occupation());
return ResponseEntity.ok(List.of(response));
}
방식 2: 개별 파라미터로 받기 (@RequestParam)#
@Operation(tags = {"swagger"})
@GetMapping("/hello/parameters2")
public ResponseEntity<List<ResponseTest>> parameterObjectTest2(
@RequestParam(value = "email") String email,
@RequestParam(value = "pw") String password,
@RequestParam(value = "oq") OccupationStatus status
) {
ResponseTest response = new ResponseTest(email, password, status);
return ResponseEntity.ok(List.of(response));
}
모델 정의#
ParameterObjectReq (Request DTO)
public record ParameterObjectReq(
String email,
String password,
OccupationStatus occupation
) {
}
OccupationStatus (Enum)
public enum OccupationStatus {
STUDENT,
EMPLOYEE,
UNEMPLOYED
}
두 방식의 차이점#
일반적으로 request를 QueryString으로 받을 경우 @RequestParam을 사용하지만, 받는 인자가 많을 경우 첫 번째 방식처럼 QueryString을 Object 형태로 받을 수 있습니다.
@RequestParam vs ParameterObject#
- @RequestParam: 기본적으로
required = true로 설정되어 있어 request value를 필수로 받습니다. - ParameterObject: Spring에서 별도의 어노테이션 없이도 QueryString을 객체의 필드값에 자동으로 바인딩합니다. 하지만
required가 기본 설정되지 않아null값이 들어올 수 있습니다.
Springdoc에서의 ParameterObject vs @RequestParam 변환 비교#
ParameterObject 사용 시#

@RequestParam 사용 시#

@ParameterObject 어노테이션 활용#
ParameterObject를 Springdoc이 @RequestParam을 사용했을 때처럼 변환해주고 Required 여부를 표시하려면 다음과 같이 설정합니다.
코드 예제#
@ParameterObject
public record ParameterObjectReq(
@NotNull
String email,
@NotNull
String password,
OccupationStatus occupation
) {
}
@ParameterObject는 Springdoc 어노테이션으로, 여러 개의 QueryString을 Object 형태로 받을 경우 해당 클래스 위에 명시하면 @RequestParam처럼 인식하고 변환해줍니다.

JSR-303 지원#
Springdoc은 JSR-303을 지원하며, 다음과 같은 validation 어노테이션을 사용할 수 있습니다
@NotNull@Min,@Max@Size- 기타 validation 어노테이션
Springdoc 공식 문서에 따르면
This library supports
- OpenAPI 3
- Spring-boot (v1, v2 and v3)
- JSR-303, specifically for @NotNull, @Min, @Max, and @Size
- Swagger-ui
- OAuth 2
- GraalVM native images
변환 결과#
ParameterObject도 @RequestParam으로 인식되도록 spec 파일이 작성되었고, @NotNull을 붙이지 않은 occupation에는 Required가 optional 형태로 표시됩니다.


좌측 이미지: @ParameterObject를 명시했을 경우 Springdoc이 인식하고 정상적인 spec으로 변환
Swagger2 → Swagger3 Annotations#
| Swagger2 | Swagger3 | 설명 |
|---|---|---|
| @Api | @Tag | 클래스단에 swagger 리소스 표시(그룹화 시켜줌)name : 태그의 이름description : 태그에 대한 설명 |
| @ApiIgnore | @Parameter(hidden = true) @Operation(hidden = true) @Hidden | 해당 어노테이션을 통해 파라미터를 swagger-ui 에서 숨길 수 있음. requestBody 나 ResponseBody 의 경우는 @JsonProperty(access = JsonProperty.Access.READ_ONLY) 를 사용 |
| @ApiImplicitParam | @Parameter | 단일 RequestParam 에 대한 설정 및 리소스 표시 |
| @ApiImplicitParams | @Parameters | 여러개의 RequestParam 을 설정 |
| @ApiModel | @Schema | description : 한글명defaultValue : 기본값allowableValues : 허용가능한 값(열거형으로 정의가능할 경우 설정합니다) |
| @ApiModelProperty(hidden = true) | @Schema(accessMode = READ_ONLY) | |
| @ApiOperation(value = “foo”, notes = “bar”) | @Operation(summary = “foo”, description = “bar”) | summary : api에 대한 간략 설명description : api에 대한 상세 설명responses : api Response 리스트parameters : api 파라미터 리스트 |
| @ApiParam | @Parameter | name : 파라미터 이름description : 파라미터 설명in : 파라미터 위치 (query, header, path, cookie) |
| @ApiResponse(code = 404, message = “foo”) | @ApiResponse(responseCode = “404”, description = “foo”) | responseCode : http 상태코드description : response에 대한 설명content : Response payload 구조schema : payload에서 이용하는 Schemahidden : Schema 숨김여부implementation : Schema 대상 클래스 |
- 여러 개의 request query params를 캡처하기 위해 객체를 사용하는 경우, 해당 메서드 인자에
@ParameterObject어노테이션을 사용하세요 - 이 단계는 선택사항입니다: 여러 개의
Docket빈이 있는 경우에만GroupedOpenApi빈으로 교체하세요
@Tag 어노테이션 활용#
@Tag 어노테이션을 사용하면 다음과 같은 그룹핑이 가능합니다
- Controller 단위로 그룹핑
- Controller 내부의 메서드 단위로 그룹핑
@Tag에 명명한 이름에 따라 spec 파일로 전환 시 그룹핑 수행- OpenAPI Generator를 이용한 client code 생성 시 해당 이름으로 파일 생성
@Tag 중복 사용 시 주의사항#
질문: 최상위 레벨에 @Tag로 그룹핑하고, 하위 메서드의 @Operation에서 다른 이름으로 tag를 설정하면 어떻게 될까요?
테스트 결과#

최상위에 @Tag(name = "swagger")를 설정하고, postHello 메서드의 @Operation에서 tags = {"swagger123"}을 추가한 경우, 같은 엔드포인트가 다른 그룹으로 중복 생성됩니다.
문제점#
이 상태에서 OpenAPI Generator를 사용하면 아래와 같이 중복된 client 코드가 생성되는 문제가 발생합니다.



권장사항: 특별한 경우가 아니라면 @Tag를 이용한 그룹핑은 Controller의 최상위에서만 사용하는 것을 권장합니다.
OpenAPI Generator Client Code 생성 시 파일명#
Client 코드 생성 시 @Tag에서 명명한 이름 + -api가 postfix로 붙습니다. 이 부분을 커스터마이징하려면 Mustache 파일을 수정해야 합니다.
참고 자료#
- Using Templates | OpenAPI Generator
- Mustache.js GitHub
- OpenAPI Generator 사용법
- OpenAPI Generator로 API의 안전한 Model과 정형화된 구현코드 자동생성하기
인증 관련 OpenAPI 스펙#
OpenAPI는 다양한 인증 방식을 지원합니다. 주요 설정 항목은 다음과 같습니다.
type (인증 형식)#
현재 API Key, HTTP, OAuth2, OpenID Connect 방식을 지원합니다. 참고: OpenAPI v2 스펙에서는 OpenID Connect 방식을 지원하지 않습니다.
지원되는 타입
http: Basic, Bearer 및 기타 HTTP 인증 체계apiKey: API 키 및 쿠키 인증oauth2: OAuth2 인증openIdConnect: OpenID Connect 검색
주요 설정 항목#
name: 인증 키 이름 (API Key 방식 사용 시 필요)in: 인증 키의 위치 지정 (query,header,cookie중 선택, API Key 방식 사용 시 필요)scheme: 인증 방식 지정 (Basic또는Bearer, HTTP 인증 방식 사용 시 필요)bearerFormat: Bearer 토큰 형식 (일반적으로JWT사용)flows: OAuth2 플로우 타입 (implicit,password,clientCredentials,authorizationCode중 선택)openIdConnectUrl: OpenID Connect URL (OpenAPI v2 스펙에서는 OAuth2나 Bearer 토큰 방식으로 대체 권장)
@Deprecated 전략#
API 버전 업데이트로 DTO 스펙에 변경이 있을 경우, 다음과 같은 단계적 전략을 사용합니다.
1단계: @Deprecated 표시#
먼저 변경될 필드에 @Deprecated 어노테이션을 붙입니다.
public class UserDto {
@Deprecated
private String oldField;
private String newField;
}
OpenAPI spec 상에도 해당 스키마의 필드에 deprecated가 표시되고, 프론트엔드에서 코드 생성 시 해당 필드에 deprecated 표시가 나타납니다. 이를 통해 프론트엔드 팀에게 곧 해당 필드가 제거될 것임을 미리 알립니다.
2단계: @Schema(hidden = true) 적용#
프론트엔드에서 새로운 스펙으로 마이그레이션이 완료되면, 서버에서 해당 @Deprecated 필드에 @Schema(hidden = true)를 추가하여 더 이상 OpenAPI spec에 해당 필드가 생성되지 않도록 합니다.
public class UserDto {
@Deprecated
@Schema(hidden = true)
private String oldField; // spec에서 제외됨
private String newField;
}
3단계: 필드 제거#
충분한 시간이 지난 후 해당 필드를 완전히 제거합니다.
이러한 단계적 접근 방식을 통해 프론트엔드와 백엔드 간의 안전한 API 버전 관리가 가능합니다.



