ArrayListはSerializableだが、subListで取得したListはRandomAccessSubListが実体で、Serializeできない。
HttpSession等はSerializableしか受け付けてくれないので、うっかりSubListを渡さないようにしなければならない。

Listのシリアライズの検証

public static void main(String[] args) throws ClassNotFoundException  {

    // Arrays.asListの場合、serialize / deserializeできるか
    List<String> asList = Arrays.asList("a", "b", "c");
    try {
        storage = serialize(asList);
        try (InputStream is = new ByteArrayInputStream(storage);) {
            List<?> asListDeserialized = deserialize(is);
            System.out.println("asList: serializable = " + asList.equals(asListDeserialized));
        }
    } catch (IOException e) {
        System.out.println(e.getClass().getName());
    }
    
    // List.subListの場合、serialize / deserializeできるか
    List<String> subList = asList.subList(0, 2);
    try {
        storage = serialize(subList);
        try (InputStream is = new ByteArrayInputStream(storage);) {
            List<?> subListDeserialized = deserialize(is);
            System.out.println("subList: serializable = " + subList.equals(subListDeserialized));
        }
    } catch (IOException e) {
        System.out.println(e.getClass().getName());
    }
    
    // List.subListをArrays.asListでラッピングすると、serialize / deserializeできるか
    List<String> subListWrapped = Arrays.asList(subList.toArray(new String[0]));
    try {
        storage = serialize(subListWrapped);
        try (InputStream is = new ByteArrayInputStream(storage);) {
            List<?> subListWrappedDeserialized = deserialize(is);
            System.out.println("Arrays.asList(subList.toArray()): serializable = " + subListWrapped.equals(subListWrappedDeserialized));
        }
    } catch (IOException e) {
        System.out.println(e.getClass().getName());
    }
    
    // List.subListをArrayListでラッピングすると、serialize / deserializeできるか
    List<String> subListWrapped2 = new ArrayList<>(subList);
    try {
        storage = serialize(subListWrapped2);
        try (InputStream is = new ByteArrayInputStream(storage);) {
            List<?> subListWrappedDeserialized2 = deserialize(is);
            System.out.println("new ArrayList(subList): serializable = " + subListWrapped2.equals(subListWrappedDeserialized2));
        }
    } catch (IOException e) {
        System.out.println(e.getClass().getName());
    }
    
    // List.subListをCollections.unmodifiableListでラッピングすると、serialize / deserializeできるか
    List<String> subListWrapped3 = Collections.unmodifiableList(subList);
    try {
        storage = serialize(subListWrapped3);
        try (InputStream is = new ByteArrayInputStream(storage);) {
            List<?> subListWrappedDeserialized3 = deserialize(is);
            System.out.println("Collections.unmodifiableList(subList): serializable = " + subListWrapped3.equals(subListWrappedDeserialized3));
        }
    } catch (IOException e) {
        System.out.println(e.getClass().getName());
    }
}
    
/**
* @param list シリアライズしたいList
* @return シリアライズしたデータの格納用byte配列
* @throws IOException
*/
public static byte[] serialize(List<?> list) throws IOException {
    try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(bos);
    ){
        out.writeObject(list);
        return bos.toByteArray();
    }
}

/**
* @param is シリアライズしたデータのストリーム
* @return デシリアライズしたList
* @throws IOException
* @throws ClassNotFoundException
*/
public static List<?> deserialize(InputStream is) throws IOException, ClassNotFoundException {
    try (ObjectInputStream in = new ObjectInputStream(is);) {
        List<?> deserialized = (List<?>)in.readObject();
        return deserialized;
    }
}

出力結果

asList: serializable = true
java.io.NotSerializableException
Arrays.asList(subList.toArray()): serializable = true
new ArrayList(subList): serializable = true
java.io.NotSerializableException

SubListをシリアライズ可能にするには、Arrays.asList(subList.toArray())new ArrayList(subList)でSubListをラッピングしてあげればいい。

注意すべきはCollectionsクラス。内部でlist instanceof RandomAccessを判断していて、RandomAccessインタフェースをimplementしたクラスを返すため、Serializeできない問題が残る。