0. 기술 스택
- JAVA 21
- Spring Boot 3.2.9
- Spring OpenFeign 4.1.1
1. 문제상황
- 진행하고 있는 사이드 프로젝트에서 공공데이터를 읽어와야 하는 일이 생겼는데, 해당 데이터는 JSON이 아닌 XML 형식의 응답이었다. 기존에는 OpenFeign을 사용하여 JSON을 쉽게 처리했지만, XML을 직접 역직렬화하는 과정에서 여러 문제가 발생했다.
- 응답 예시
<dbs>
<db>
<mt20id>PF254457</mt20id>
<prfnm>SMTOWN LIVE</prfnm>
<prfpdfrom>2025.01.11</prfpdfrom>
<prfpdto>2025.01.12</prfpdto>
<fcltynm>고척스카이돔</fcltynm>
<poster>http://www.kopis.or.kr/upload/pfmPoster/PF_PF254457_241125_093822.jfif</poster>
<area>서울특별시</area>
<genrenm>대중음악</genrenm>
<openrun>N</openrun>
<prfstate>공연완료</prfstate>
</db>
<db>
<mt20id>PF254256</mt20id>
<prfnm>임영웅 리사이틀, RE: CITAL</prfnm>
<prfpdfrom>2024.12.27</prfpdfrom>
<prfpdto>2025.01.04</prfpdto>
<fcltynm>고척스카이돔</fcltynm>
<poster>http://www.kopis.or.kr/upload/pfmPoster/PF_PF254256_241121_094857.gif</poster>
<area>서울특별시</area>
<genrenm>대중음악</genrenm>
<openrun>N</openrun>
<prfstate>공연완료</prfstate>
</db>
</dbs>
2. 이슈 1) XML 역직렬화 문제 발생
2-1. 문제 상황
- OpenFeign을 사용하여 공공데이터 API를 호출했으나, 응답이 JSON이 아니라 XML이었다
@FeignClient(name = "kopis-api", url = "${kopis.api.uri}", configuration = FeignLoggerConfig.class)
public interface KopisRepository {
String SERVICE = "service";
String START_DATE = "stdate";
String END_DATE = "eddate";
String PAGE = "cpage";
String SIZE = "rows";
String GENRE_CODE = "shcate";
String VENUE_CODE = "prfplccd";
@GetMapping(value = "")
Object getPerformanceList(@RequestParam(SERVICE) String serviceKey,
@RequestParam(START_DATE) String startDate,
@RequestParam(END_DATE) String endDate,
@RequestParam(PAGE) Integer page,
@RequestParam(SIZE) Integer size,
@RequestParam(value = GENRE_CODE, required = false) String genreCode,
@RequestParam(value = VENUE_CODE, required = false) String venueCode
);
}
- XML을 처리할 라이브러리가 없어 아래와 같은 예외 발생
- 참고 : 테스트를 위해 우선 Object 객체로 받았다.
feign.codec.DecodeException: Could not extract response: no suitable HttpMessageConverter found for response type [class java.util.Objects] and content type [application/xml;charset=UTF-8]
Caused by: org.springframework.web.client.UnknownContentTypeException: Could not extract response: no suitable HttpMessageConverter found for response type [class java.util.Objects] and content type [application/xml;charset=UTF-8]
2-2. 원인 분석
- OpenFeign은 기본적으로 JSON을 처리하며, XML을 역직렬화할 HttpMessageConverter가 없음
- XML을 역직렬화하려면 별도의 라이브러리 필요
2-3. 해결) XML 역직렬화 라이브러리 추가
- XML을 역직렬화 해 줄 수 있는 라이브러리 추가
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml'
2-4. 결과
- 정상적으로 데이터를 받아온 것을 볼 수 있다.

3. 이슈 2) 외부 API 필드와 DTO 필드 매핑 문제
3-1. 문제 상황
- XML 응답의 필드명이 mt20id, prfnm, fcltynm 등으로 제공된다.
- 데이터를 정상적으로 매핑하려면 외부 API에서 정의한 값으로 받아야 했다.
- 아래처럼 mt20id, prfnm, fcltynm 등의 값을 정의해두면 해당 값이 어떤 데이터를 의미하는지 알 수 없고, 주석으로 기록해둔다 하더라도 추후 getter메서드를 사용할 때 헷갈릴 수 있다.
- getId() { return mt20id; } 같은 메서드를 정의할 수도 있겠지만, 보통 lombok 을 사용하기 때문에 하나하나 정의해주는 게 여간 귀찮은 일이 아니다.
@NoArgsConstructor
@AllArgsConstructor
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class KospiResponse<T> {
T db;
}
@NoArgsConstructor
@AllArgsConstructor
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class PerformanceInfoResDto {
String mt20id;
String prfnm;
String fcltynm;
}

3-2. 해결) @JacksonXmlProperty 사용하여 데이터 매핑
@NoArgsConstructor
@AllArgsConstructor
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class KospiResponse<T> {
@JacksonXmlProperty(localName = "db")
List<T> value;
}
@NoArgsConstructor
@AllArgsConstructor
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class PerformanceInfoResDto {
@JacksonXmlProperty(localName = "mt20id")
String id;
@JacksonXmlProperty(localName = "prfnm")
String name;
@JacksonXmlProperty(localName = "fcltynm")
String venue;
}
4. 이슈 3) XML 리스트 매핑 문제
4-1. 문제 상황
- xml이 정상적으로 dto에 매핑되지 않았다.
- 아래처럼 내가 원하는 필드명으로 선언하고, @JacksonXmlProperty를 사용하여 매핑될 데이터 필드를 지정해주었지만, 또다시 에러가 발생하였다.
@NoArgsConstructor
@AllArgsConstructor
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class KospiResponse<T> {
@JacksonXmlProperty(localName = "db")
List<T> value;
}
@NoArgsConstructor
@AllArgsConstructor
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class PerformanceInfoResDto {
@JacksonXmlProperty(localName = "mt20id")
String id;
@JacksonXmlProperty(localName = "prfnm")
String name;
@JacksonXmlProperty(localName = "fcltynm")
String venue;
}
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.puremadeleine.viewith.dto.client.kopis.PerformanceInfoResDto` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('PF254457')
at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 4, column: 25] (through reference chain: com.puremadeleine.viewith.dto.client.kopis.KospiResponse["db"]->java.util.ArrayList[0])
4-2. 원인 분석
- Jackson이 리스트를 감싸는 태그가 없다고 가정하여 오류 발생
4-3. 해결) @JacksonXmlElementWrapper 설정 추가
- @JacksonXmlElementWrapper(useWrapping = false) 설정을 추가하였다.
- default 값 : true
- XML 리스트를 감싸는 태그가 없다는 것을 명시

4-3-1. @JacksonXmlElementWrapper 이란
- @JacksonXmlElementWrapper : List 및 Map 속성을 감싸는 XML 요소를 지정할 수 있도록 허용하는 어노테이션
- 기본적으로 Jackson은 XML을 파싱할 때, 리스트(List)를 감싸는 별도의 XML 태그가 있어야 한다고 가정한다.
- 예를 들어, 내가 아래와 같이 dto를 정의했다고 해보자.
@NoArgsConstructor
@AllArgsConstructor
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class KospiResponse<T> {
@JacksonXmlProperty(localName = "db")
List<T> value;
}
@NoArgsConstructor
@AllArgsConstructor
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class PerformanceInfoResDto {
@JacksonXmlProperty(localName = "mt20id")
String id;
}
- 그럼 jacskon은 아래와 같은 데이터 구조를 기대한다.
<KospiResponse>
<db>
<Wrapper>
<mt20id>PF254457</mt20id>
<mt20id>PF123456</mt20id>
</Wrapper>
</db>
</KospiResponse>
- 그치만 우리 데이터는 아래와 같은 데이터이기 때문에 리스트를 감싸는 태그가 없어도 여러 개의 요소를 리스트로 변환 가능하도록 설정한다.
<KospiResponse>
<db>
<mt20id>PF254457</mt20id>
<mt20id>PF123456</mt20id>
</db>
</KospiResponse>