Post

serialVersionUID와 InvalidClassException

[!info] 직렬화, 역직렬화는 보통 json을 이용해서 처리하게 되고, 그렇게 하는 것이 좋아보인다.
json이라는 명확한 표준이 있어 이식성도 좋고, 변환 로직도 심플해서 아래와 같은 잠재적인 문제를 피할 수 있기 때문이다.

  • 어떤 사정으로 인해 json serialization 하지 못하는 경우, java에서는 Serializable 구현하고 이를 이용해서 직렬화 하게 되는데…
  • 직렬화/역직렬화 시, 클래스와 객체의 동일성 판단이 json에 비해 매우 민감하기 때문에 주의해야 한다.
1
2
3
4
5
6
7
8
import dev.umbum.Address;

// 변경 전 User
public class User implements Serializable {
    private String name;
    private String email;
    private Address address;
}

User 클래스의 객체를 직렬화 해서 redis에 올린 다음, Address의 위치가 변경되어(내용은 변경되지 않았다) import 주소를 아래와 같이 변경하면, 역직렬화 시 InvalidClassException 발생한다.

1
2
3
4
5
6
7
8
import dev.umbum.model.Address;

// 변경 후 User
public class User implements Serializable {
    private String name;
    private String email;
    private Address address;
}
  • 필드가 아무것도 변경되지 않았기 때문에 역직렬화 성공할 것이라고 예상하지만, import 구문만 달라져도 역직렬화에 실패한다.
  • int -> long으로 변경하는 것도 json이라면 호환이 되지만, java 직렬화는 InvalidClassException 발생한다.

호환이 된다/안된다 판단은? serialVersionUID 참고

  • 호환 가능 판단은 serialVersionUID를 보고 판단하게 되는데, 명시적으로 지정하지 않더라도 Serializable 인터페이스가 있으면 컴파일러가 알아서 계산하게 된다.
    • 클래스, 필드, 메서드 등 종합적으로 보고 컴파일러가 계산하며 컴파일러 세부 구현에 따라 값이 또 다를 수 있다는게 단점이다.
  • import 구분을 변경하거나, type을 변경하는 등 Incompatible changes가 있었다면, 생성되는 serialVersionUID 값이 달라지게 된다.
    • e.g. -2649918656647101333 -> 3656463606950018647 로 변경
  • 어떤 변경이 serialVersionUID 값을 달라지게 만드는지는, compatible, Incompatible 변경 spec 참고
  • 그래서 docs 에서는 아예, serialVersionUID를 지정해서 처리하는 것을 권장하고 있다.

It is strongly recommended that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected serialVersionUID conflicts during deserialization, causing deserialization to fail.

직접 serialVersionUID를 생성 할 수 있는 방법이 있을까?

This post is licensed under CC BY 4.0 by the author.