본문으로 건너뛰기

enableDefaultTyping 과 주의점

2026. 3. 19.

enableDefaultTyping

Jackson 이 직렬/역직렬 화 시 타입 정보 자동으로 포함하게 해주는 것

objectMapper.copy()
        .enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
  • DefaultTyping.NON_FINAL : final 이 아닌 모든 클래스에 대해 타입 정보 포함

다형성을 위해 타입이 필요하다. - 어떤 클래스인지 추적 및 JSON 기반 객체 복구
String, Integer, Long 같은 final 클래스들은 타입을 포함시키지 않는다.
-> final 클래스는 서브 클래스가 존재할 수 없으므로 타입 정보가 필요없다.

  • JsonTypeInfo.As.PROPERTY : JSON 객체 내부에 프로퍼티로 삽입
{ "@class": "com.example.MyDto", "name": "test", "value": 123 }

@class 가 기본값으로 명시된다.

주의점

final 클래스는 저장하지 않으므로, 주의해서 사용해야 하는 부분이 있다.

래퍼 클래스 Caching

public Long getLatestSnapshotId(long provisionId) {
        var optionalValue = ...;
        if (optionalValue.isEmpty()) {
            return null;
        }
        return optionalValue.get().getId();
    }

와 같은 코드를 짠다면?
-> 캐시를 호출할 때 에러가 발생하게 된다 ☠️☠️

이는 교모하게 enableDefaultTyping 과 GenericJackson2JsonRedisSerializer 가
맞물리면서 발생한다.

  1. Long 은 타입을 없이 저장한다.
  2. 캐싱을 읽고 역직렬화 할 때, 숫자를 보고 Jackson 이 알아서 판단한다.
  • Integer 범위이면, Integer 로 타입 캐스팅
  • Integer 보다 크면, Long 으로 타입 캐스팅
  1. Long 을 기대했는데 Integer 타입을 받아서 예외 발생

=> 굳이 Long 이여야 할 필요가 없다면, 래퍼 클래스 long 을 사용하자.

Collections.unmodifiableList

@Transactional(readOnly = true)
@Cacheable
public List<ValueDto> getList() {
    return valueRepository.findAll()
            .stream()
            .map(ValueDto::toDto)
            .toList();
}

엔티티 -> DTO 변환후 캐싱할때 아무 생각없이 .toList 를 사용할 수 있다.
이 역시도 주의 해야한다! ☠️

toListImmutableCollections 타입을 기록한다.

["java.util.ImmutableCollections$ListN", [{"@class": "com.example.MyDto", ...}]]

이 타입은 JDK 내부 클래스로

  • 리플렉션 접근 제한
  • 기본 생성자 X - Jackson 이 객체 만드는 방식 사용 불가

등을 통해 에러가 발생한다.

=> .collect(Collectors.toList()) 를 통해 ArrayList 로 생성해줘야 한다.
(Map 도 동일, Map.of 시 에러 발생)

["java.util.ArrayList", [{"@class": "com.example.MyDto", ...}]]

익명, 람다 클래스

위와 같은 맥락으로 런타임 클래스명을 복원 불가능하기 때문에 역직렬화 할 수 없다.

MyService$$Lambda$123/0x00000... 추적 불가능

Deprecated

objectMapper.enableDefaultTyping(NON_FINAL, As.PROPERTY)
사실 2.10+ 부터 Deprecated 되어있다.

역직렬화 공격 취약점 때문인데, @class 에 악의적 클래스명을 넣으면 임의 코드 실행이 가능해진다.

var ptv = BasicPolymorphicTypeValidator.builder()
          .allowIfBaseType(Object.class)
          .build();
  objectMapper.activateDefaultTyping(ptv, NON_FINAL, As.PROPERTY);

와 같이, TypeValidator 로 허용하는 타입을 명시하고
activeDefaultTyping 으로 설정하면 기존과 동일하게 동작한다.

당연히 Object 말고 패키지 지정이나, 객체를 허용하는게 더 좋은 방향