준호씨의 블로그

Jackson - kafka listener StringJsonMessageConverter에서 enum에 없는 값이 들어 올 때 기본값 지정 방법. @JsonEnumDefaultValue 본문

개발이야기

Jackson - kafka listener StringJsonMessageConverter에서 enum에 없는 값이 들어 올 때 기본값 지정 방법. @JsonEnumDefaultValue

준호씨 2021. 1. 4. 00:05
반응형

org.springframework.kafka.listener.ListenerExecutionFailedException: Listener failed; nested exception is org.springframework.kafka.support.converter.ConversionException: Failed to convert from JSON; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `kr.pe.junho85.demo.model.TestType` from String "TEST": not one of the values accepted for Enum class: [BETA, UNKNOWN, ALPHA]
 at [Source: (String)"{"testType": "TEST"}"; line: 1, column: 14] (through reference chain: kr.pe.junho85.demo.model.TestMessage["testType"])
	at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.decorateException(KafkaMessageListenerContainer.java:2105) ~[spring-kafka-2.6.4.jar:2.6.4]
	at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeErrorHandler(KafkaMessageListenerContainer.java:2090) ~[spring-kafka-2.6.4.jar:2.6.4]
	at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeRecordListener(KafkaMessageListenerContainer.java:1989) ~[spring-kafka-2.6.4.jar:2.6.4]
	at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeWithRecords(KafkaMessageListenerContainer.java:1916) ~[spring-kafka-2.6.4.jar:2.6.4]
	at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeRecordListener(KafkaMessageListenerContainer.java:1810) ~[spring-kafka-2.6.4.jar:2.6.4]
	at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeListener(KafkaMessageListenerContainer.java:1529) ~[spring-kafka-2.6.4.jar:2.6.4]
	at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.pollAndInvoke(KafkaMessageListenerContainer.java:1176) ~[spring-kafka-2.6.4.jar:2.6.4]
	at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.run(KafkaMessageListenerContainer.java:1073) ~[spring-kafka-2.6.4.jar:2.6.4]
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) ~[na:na]
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
	at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
Caused by: org.springframework.kafka.support.converter.ConversionException: Failed to convert from JSON; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `kr.pe.junho85.demo.model.TestType` from String "TEST": not one of the values accepted for Enum class: [BETA, UNKNOWN, ALPHA]
 at [Source: (String)"{"testType": "TEST"}"; line: 1, column: 14] (through reference chain: kr.pe.junho85.demo.model.TestMessage["testType"])
	at org.springframework.kafka.support.converter.JsonMessageConverter.extractAndConvertValue(JsonMessageConverter.java:117) ~[spring-kafka-2.6.4.jar:2.6.4]
	at org.springframework.kafka.support.converter.MessagingMessageConverter.toMessage(MessagingMessageConverter.java:123) ~[spring-kafka-2.6.4.jar:2.6.4]
	at org.springframework.kafka.listener.adapter.MessagingMessageListenerAdapter.toMessagingMessage(MessagingMessageListenerAdapter.java:304) ~[spring-kafka-2.6.4.jar:2.6.4]
	at org.springframework.kafka.listener.adapter.RecordMessagingMessageListenerAdapter.onMessage(RecordMessagingMessageListenerAdapter.java:77) ~[spring-kafka-2.6.4.jar:2.6.4]
	at org.springframework.kafka.listener.adapter.RecordMessagingMessageListenerAdapter.onMessage(RecordMessagingMessageListenerAdapter.java:51) ~[spring-kafka-2.6.4.jar:2.6.4]
	at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeOnMessage(KafkaMessageListenerContainer.java:2057) ~[spring-kafka-2.6.4.jar:2.6.4]
	at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeOnMessage(KafkaMessageListenerContainer.java:2039) ~[spring-kafka-2.6.4.jar:2.6.4]
	at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeRecordListener(KafkaMessageListenerContainer.java:1976) ~[spring-kafka-2.6.4.jar:2.6.4]
	... 8 common frames omitted
Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `kr.pe.junho85.demo.model.TestType` from String "TEST": not one of the values accepted for Enum class: [BETA, UNKNOWN, ALPHA]
 at [Source: (String)"{"testType": "TEST"}"; line: 1, column: 14] (through reference chain: kr.pe.junho85.demo.model.TestMessage["testType"])
	at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:67) ~[jackson-databind-2.11.3.jar:2.11.3]
	at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:1702) ~[jackson-databind-2.11.3.jar:2.11.3]
	at com.fasterxml.jackson.databind.DeserializationContext.handleWeirdStringValue(DeserializationContext.java:947) ~[jackson-databind-2.11.3.jar:2.11.3]
	at com.fasterxml.jackson.databind.deser.std.EnumDeserializer._deserializeAltString(EnumDeserializer.java:257) ~[jackson-databind-2.11.3.jar:2.11.3]
	at com.fasterxml.jackson.databind.deser.std.EnumDeserializer.deserialize(EnumDeserializer.java:181) ~[jackson-databind-2.11.3.jar:2.11.3]
	at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129) ~[jackson-databind-2.11.3.jar:2.11.3]
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:371) ~[jackson-databind-2.11.3.jar:2.11.3]
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:164) ~[jackson-databind-2.11.3.jar:2.11.3]
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4526) ~[jackson-databind-2.11.3.jar:2.11.3]
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3468) ~[jackson-databind-2.11.3.jar:2.11.3]
	at org.springframework.kafka.support.converter.JsonMessageConverter.extractAndConvertValue(JsonMessageConverter.java:114) ~[spring-kafka-2.6.4.jar:2.6.4]
	... 15 common frames omitted

KafkaListener로 메시지를 받아와서 처리할 때 enum에 맞는 값이 없어서 파싱 오류가 나는 경우가 있습니다.

예를 들어 아래와 같은 KafkaListener가 있습니다.

@KafkaListener(id = "test", topics = "test")
public void testListener(TestMessage testMessage) {
    System.out.println(testMessage);
}

KafkaConfig에서 kafkaListenerContainerFactory() 설정을 아래처럼 만들어서 String이 들어오면 json을 파싱 해서 객체에 맵핑되도록 합니다.

@Bean
KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, Object>> kafkaListenerContainerFactory() {
    ConcurrentKafkaListenerContainerFactory<String, Object> factory = new ConcurrentKafkaListenerContainerFactory<>();
    factory.setMessageConverter(new StringJsonMessageConverter());
    factory.setConsumerFactory(consumerFactory());
    return factory;
}

TestMessage는 아래처럼 TestType을 받습니다.

@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestMessage {
    private TestType testType;
}

TestType에는 예제와 같이 ALPHA, BETA가 있고요.

public enum TestType {
    ALPHA,
    BETA;
}

kafka메시지가

{"testType": "ALPHA"}

처럼 TestType에 있는 값이 들어오면 정상적으로 처리됩니다.

하지만 아래처럼 TestType에 없는 값이 들어오면

{"testType": "TEST"}

아래와 같이 enum에 없는 타입이라고 deserialize오류가 발생합니다.

Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `kr.pe.junho85.demo.model.TestType` from String "TEST": not one of the values accepted for Enum class: [BETA, ALPHA]
 at [Source: (String)"{"testType": "TEST"}"; line: 1, column: 14] (through reference chain: kr.pe.junho85.demo.model.TestMessage["testType"])

 

없는 타입이 들어올 경우 특정 타입을 지정해 주는 방법입니다.

일단 enum에 없는 값이 들어올 경우 기본적으로 지정하는 타입을 만들고 @JsonEnumDefaultValue를 지정해 줍니다.

public enum TestType {
    ALPHA,
    BETA,
    @JsonEnumDefaultValue UNKNOWN;
}

jackson설정에 READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE를 활성화시켜줍니다.

spring:
  jackson:
    deserialization:
      READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE: true

그리고 kafkaListenerContainerFactory()의 StringJsonMessageConverter에 objectMapper를 인자로 넘겨줍니다.

@RequiredArgsConstructor
@Configuration
public class KafkaConfig {
    ...
    private final ObjectMapper objectMapper;
    ...

    @Bean
    KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, Object>> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, Object> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setMessageConverter(new StringJsonMessageConverter(objectMapper));
        factory.setConsumerFactory(consumerFactory());
        return factory;
    }

이제 다음과 같이 enum에 정의되지 않은 타입의 메시지가 들어오면

{"testType": "TEST"}

@JsonEnumDefaultValue로 지정된 UNKNOWN이 기본 지정되게 됩니다.

TestMessage(testType=UNKNOWN)

 

코드: github.com/junho85/spring-kafka-enum

 

junho85/spring-kafka-enum

Contribute to junho85/spring-kafka-enum development by creating an account on GitHub.

github.com

 

#ListenerExcutionFailedException #ConversionExceptoin #InvalidFormatException

반응형
Comments