You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sd...@apache.org on 2022/01/11 23:57:26 UTC

[ignite-3] branch ignite-16250 created (now d2637e7)

This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a change to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git.


      at d2637e7  IGNITE-16250 Raft, SQL and TX use marshallable and network messages

This branch includes the following new commits:

     new 255d9a6  - manually squash via patch
     new 5f1bcdd  - make ClassDescriptorFactoryContext not implement IdIndexedDescriptors
     new 1c26598  - improve javadoc
     new 54f04af  - improve javadoc
     new 67c494a  - fix null marshalling
     new 6183eeb  - make sure Externalizable constructor is called on unmarshalling
     new d7a3c01  - add tests to make sure Serializable constructor is not called on unmarshalling, while first non-serializable parent constructor gets called
     new 5d77e86  - make sure Serializable constructor is not called on unmarshalling, while first non-serializable parent constructor gets called
     new 2b275f1  - rename a class
     new a1e8362  - add a test to make sure that constructors are not invoked for arbitrary objects
     new 27915ab  - improve javadoc
     new c86131b  - clean up code
     new 10df1f8  - refactor mark/reset a bit
     new 03df52c  - split tests
     new 8364385  - remove a TODO
     new b22f022  - resolve a few TODOs
     new 595839a  - postpone 2 TODOs
     new 7b4063e  - postpone one more TODO
     new 3695413  - marshal null correctly
     new f8f2898  - use correct exception
     new fdcd2fc  - improve code readability
     new 43a84a7  - improve javadocs
     new 7dcb576  - improve javadocs
     new 2432830  - use varints for descriptor/command/object ID and container serialization
     new 0dab994  - resolve a TODO
     new d013a48  - support List.of(), Set.of(), Map.of()
     new d2637e7  IGNITE-16250 Raft, SQL and TX use marshallable and network messages

The 27 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


[ignite-3] 01/27: - manually squash via patch

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 255d9a6f9735a3e708f4e1d2885732be58c6e70d
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Mon Jan 10 18:28:28 2022 +0400

    - manually squash via patch
---
 .../network/message/ClassDescriptorMessage.java    |  23 +
 .../network/message/FieldDescriptorMessage.java    |   5 -
 .../network/serialization/BuiltinType.java         |   8 +-
 .../network/serialization/ClassDescriptor.java     |  80 +-
 .../serialization/ClassDescriptorFactory.java      | 104 +--
 .../PerSessionSerializationService.java            |  43 +-
 .../network/serialization/Serialization.java       |  33 +-
 .../serialization/SerializedStreamCommands.java    |   2 +-
 .../serialization/SpecialSerializationMethods.java |  29 +
 .../SpecialSerializationMethodsImpl.java           |  89 ++-
 .../marshal/ArbitraryObjectMarshaller.java         | 112 ++-
 .../marshal/BuiltInContainerMarshallers.java       |  27 +-
 .../serialization/marshal/BuiltInMarshalling.java  |  87 +--
 .../marshal/BuiltInNonContainerMarshallers.java    |  11 +-
 ...eWriter.java => DefaultFieldsReaderWriter.java} |  23 +-
 .../marshal/DefaultUserObjectMarshaller.java       | 111 +--
 .../marshal/ExternalizableMarshaller.java          |  48 +-
 .../serialization/marshal/MarshallingContext.java  |  46 ++
 .../serialization/marshal/ProtocolMarshalling.java |  73 ++
 .../serialization/marshal/TypedValueWriter.java    |   8 +-
 .../serialization/marshal/UnmarshalException.java  |   4 +
 .../marshal/UnmarshallingContext.java              |  46 ++
 .../marshal/UosObjectInputStream.java              | 174 +++++
 .../marshal/UosObjectOutputStream.java             | 171 +++++
 .../network/serialization/marshal/ValueReader.java |   8 +-
 .../network/serialization/marshal/ValueWriter.java |   8 +-
 .../serialization/ClassDescriptorFactoryTest.java  |  84 ++-
 .../serialization/DefaultDescriptorsTest.java      |   2 -
 .../DefaultUserObjectMarshallerCommonTest.java     |  48 ++
 ...erObjectMarshallerWithArbitraryObjectsTest.java |  11 +
 ...efaultUserObjectMarshallerWithBuiltinsTest.java |  14 +-
 ...UserObjectMarshallerWithExternalizableTest.java | 303 +++++++-
 ...ltUserObjectMarshallerWithSerializableTest.java | 809 +++++++++++++++++++++
 .../network/serialization/marshal/Throwables.java} |  22 +-
 34 files changed, 2353 insertions(+), 313 deletions(-)

diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/message/ClassDescriptorMessage.java b/modules/network/src/main/java/org/apache/ignite/internal/network/message/ClassDescriptorMessage.java
index 8cf7745..1bf857b 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/message/ClassDescriptorMessage.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/message/ClassDescriptorMessage.java
@@ -24,6 +24,7 @@ import org.apache.ignite.internal.network.serialization.Serialization;
 import org.apache.ignite.internal.network.serialization.SerializationType;
 import org.apache.ignite.network.NetworkMessage;
 import org.apache.ignite.network.annotations.Transferable;
+import org.jetbrains.annotations.Nullable;
 
 /** Message for the {@link ClassDescriptor}. */
 @Transferable(NetworkMessageTypes.CLASS_DESCRIPTOR_MESSAGE)
@@ -43,6 +44,21 @@ public interface ClassDescriptorMessage extends NetworkMessage {
     int descriptorId();
 
     /**
+     * Super-class name.
+     *
+     * @see ClassDescriptor#superClassDescriptor()
+     */
+    @Nullable
+    String superClassName();
+
+    /**
+     * Super-class descriptor ID. {@link Integer#MIN_VALUE} if super-class is missing.
+     *
+     * @see ClassDescriptor#superClassDescriptor()
+     */
+    int superClassDescriptorId();
+
+    /**
      * List of the class fields' descriptors.
      *
      * @see ClassDescriptor#fields()
@@ -64,6 +80,13 @@ public interface ClassDescriptorMessage extends NetworkMessage {
     boolean hasSerializationOverride();
 
     /**
+     * Has readObjectNoData().
+     *
+     * @see Serialization#hasReadObjectNoData()
+     */
+    boolean hasReadObjectNoData();
+
+    /**
      * Has writeReplace.
      *
      * @see Serialization#hasWriteReplace()
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/message/FieldDescriptorMessage.java b/modules/network/src/main/java/org/apache/ignite/internal/network/message/FieldDescriptorMessage.java
index 1409544..aa7a3ec 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/message/FieldDescriptorMessage.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/message/FieldDescriptorMessage.java
@@ -39,9 +39,4 @@ public interface FieldDescriptorMessage extends NetworkMessage {
      * Field's class name.
      */
     String className();
-
-    /**
-     * The name of The class in which this field is declared.
-     */
-    String declaringClassName();
 }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BuiltinType.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BuiltinType.java
index 46d22e5..c27c595 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BuiltinType.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BuiltinType.java
@@ -80,9 +80,8 @@ public enum BuiltinType {
     HASH_MAP(40, HashMap.class),
     LINKED_HASH_MAP(41, LinkedHashMap.class),
     BIT_SET(42, BitSet.class),
-    NULL(43, Null.class),
-    VOID(44, Void.class)
-    // 45 is REFERENCE command, see SerializedStreamCommands#REFERENCE
+    NULL(43, Null.class)
+    // 44 is REFERENCE command, see SerializedStreamCommands#REFERENCE
     ;
 
     /**
@@ -125,7 +124,7 @@ public enum BuiltinType {
     }
 
     /**
-     * Creates a descriptor for this default type.
+     * Creates a descriptor for this built-in type.
      *
      * @return Descriptor.
      */
@@ -133,6 +132,7 @@ public enum BuiltinType {
         return new ClassDescriptor(
                 clazz,
                 descriptorId,
+                null,
                 Collections.emptyList(),
                 new Serialization(SerializationType.BUILTIN)
         );
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptor.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptor.java
index 36e8752..322ffcf 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptor.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptor.java
@@ -20,18 +20,13 @@ package org.apache.ignite.internal.network.serialization;
 import java.lang.reflect.Modifier;
 import java.util.List;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Class descriptor for the user object serialization.
  */
 public class ClassDescriptor {
     /**
-     * Name of the class.
-     */
-    @NotNull
-    private final String className;
-
-    /**
      * Class.
      */
     @NotNull
@@ -43,7 +38,13 @@ public class ClassDescriptor {
     private final int descriptorId;
 
     /**
-     * List of the class fields' descriptors.
+     * Superclass descriptor (might be missing).
+     */
+    @Nullable
+    private final ClassDescriptor superClassDescriptor;
+
+    /**
+     * List of the declared class fields' descriptors.
      */
     @NotNull
     private final List<FieldDescriptor> fields;
@@ -63,10 +64,16 @@ public class ClassDescriptor {
     /**
      * Constructor.
      */
-    public ClassDescriptor(@NotNull Class<?> clazz, int descriptorId, @NotNull List<FieldDescriptor> fields, Serialization serialization) {
-        this.className = clazz.getName();
+    public ClassDescriptor(
+            @NotNull Class<?> clazz,
+            int descriptorId,
+            @Nullable ClassDescriptor superClassDescriptor,
+            @NotNull List<FieldDescriptor> fields,
+            Serialization serialization
+    ) {
         this.clazz = clazz;
         this.descriptorId = descriptorId;
+        this.superClassDescriptor = superClassDescriptor;
         this.fields = List.copyOf(fields);
         this.serialization = serialization;
         this.isFinal = Modifier.isFinal(clazz.getModifiers());
@@ -84,7 +91,37 @@ public class ClassDescriptor {
     }
 
     /**
-     * Returns fields' descriptors.
+     * Returns descriptor of the superclass of the described class (might be {@code null}).
+     *
+     * @return descriptor of the superclass of the described class (might be {@code null})
+     */
+    @Nullable
+    public ClassDescriptor superClassDescriptor() {
+        return superClassDescriptor;
+    }
+
+    /**
+     * Returns ID of the superclass descriptor (might be {@code null}).
+     *
+     * @return ID of the superclass descriptor (might be {@code null})
+     */
+    @Nullable
+    public Integer superClassDescriptorId() {
+        return superClassDescriptor == null ? null : superClassDescriptor.descriptorId();
+    }
+
+    /**
+     * Returns name of the superclass (might be {@code null}).
+     *
+     * @return name of the superclass (might be {@code null})
+     */
+    @Nullable
+    public String superClassName() {
+        return superClassDescriptor == null ? null : superClassDescriptor.className();
+    }
+
+    /**
+     * Returns declared fields' descriptors.
      *
      * @return Fields' descriptors.
      */
@@ -100,7 +137,7 @@ public class ClassDescriptor {
      */
     @NotNull
     public String className() {
-        return className;
+        return clazz.getName();
     }
 
     /**
@@ -169,6 +206,15 @@ public class ClassDescriptor {
     }
 
     /**
+     * Returns {@code true} if the described class has writeObject() and readObject() methods.
+     *
+     * @return {@code true} if the described class has writeObject() and readObject() methods
+     */
+    public boolean hasSerializationOverride() {
+        return serialization.hasSerializationOverride();
+    }
+
+    /**
      * Returns {@code true} if the described class has {@code writeReplace()} method.
      *
      * @return {@code true} if the described class has {@code writeReplace()} method
@@ -227,8 +273,18 @@ public class ClassDescriptor {
     @Override
     public String toString() {
         return "ClassDescriptor{"
-                + "className='" + className + '\''
+                + "className='" + className() + '\''
                 + ", descriptorId=" + descriptorId
                 + '}';
     }
+
+    /**
+     * Returns {@code true} if this descriptor describes same class as the given descriptor.
+     *
+     * @param other a descriptor to match against
+     * @return {@code true} if this descriptor describes same class as the given descriptor
+     */
+    public boolean describesSameClass(ClassDescriptor other) {
+        return other.clazz() == clazz();
+    }
 }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactory.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactory.java
index 0a3a4a9..d8559e3 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactory.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactory.java
@@ -29,12 +29,10 @@ import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.ArrayDeque;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Queue;
-import java.util.stream.Stream;
 import org.apache.ignite.lang.IgniteException;
 import org.jetbrains.annotations.Nullable;
 
@@ -44,6 +42,8 @@ import org.jetbrains.annotations.Nullable;
 public class ClassDescriptorFactory {
     /** Means that no serialization override is present; used for readability instead of {@code false}. */
     private static final boolean NO_SERIALIZATION_OVERRIDE = false;
+    /** Means that no readObjectNoData() method is present; used for readability instead of {@code false}. */
+    private static final boolean NO_READ_OBJECT_NO_DATA = false;
 
     /**
      * Factory context.
@@ -129,10 +129,12 @@ public class ClassDescriptorFactory {
         return new ClassDescriptor(
                 clazz,
                 descriptorId,
+                superClassDescriptor(clazz),
                 Collections.emptyList(),
                 new Serialization(
                         SerializationType.EXTERNALIZABLE,
                         NO_SERIALIZATION_OVERRIDE,
+                        NO_READ_OBJECT_NO_DATA,
                         hasWriteReplace(clazz),
                         hasReadResolve(clazz)
                 )
@@ -140,6 +142,22 @@ public class ClassDescriptorFactory {
     }
 
     /**
+     * If the given class has a super-class (which is not Object), parses the super-class and registers the resulting descriptor.
+     *
+     * @param clazz class which super-class to parse
+     * @return descriptor of the super-class or {@code null} if the class has no super-class or the super-class is Object
+     */
+    private ClassDescriptor superClassDescriptor(Class<?> clazz) {
+        Class<?> superclass = clazz.getSuperclass();
+
+        if (superclass == null || superclass == Object.class) {
+            return null;
+        }
+
+        return create(superclass);
+    }
+
+    /**
      * Checks if a class has a public no-arg constructor.
      *
      * @param clazz Class.
@@ -171,19 +189,33 @@ public class ClassDescriptorFactory {
      * @return Class descriptor.
      */
     private ClassDescriptor serializable(int descriptorId, Class<? extends Serializable> clazz) {
+        validateSerializationOverride(clazz);
+
         return new ClassDescriptor(
                 clazz,
                 descriptorId,
+                superClassDescriptor(clazz),
                 fields(clazz),
                 new Serialization(
                         SerializationType.SERIALIZABLE,
                         hasOverrideSerialization(clazz),
+                        hasReadObjectNoData(clazz),
                         hasWriteReplace(clazz),
                         hasReadResolve(clazz)
                 )
         );
     }
 
+    private void validateSerializationOverride(Class<? extends Serializable> clazz) {
+        boolean hasReadObject = hasReadObject(clazz);
+        boolean hasWriteObject = hasWriteObject(clazz);
+
+        if (hasWriteObject != hasReadObject) {
+            throw new IllegalArgumentException("Class must either have both writeObject() and readObject() methods or neither of them: "
+                    + clazz.getName());
+        }
+    }
+
     private boolean hasReadResolve(Class<? extends Serializable> clazz) {
         return getReadResolve(clazz) != null;
     }
@@ -193,11 +225,19 @@ public class ClassDescriptorFactory {
     }
 
     private boolean hasOverrideSerialization(Class<? extends Serializable> clazz) {
-        Method writeObject = getWriteObject(clazz);
-        Method readObject = getReadObject(clazz);
-        Method readObjectNoData = getReadObjectNoData(clazz);
+        return hasWriteObject(clazz) && hasReadObject(clazz);
+    }
+
+    private boolean hasReadObject(Class<? extends Serializable> clazz) {
+        return getReadObject(clazz) != null;
+    }
 
-        return writeObject != null && readObject != null && readObjectNoData != null;
+    private boolean hasWriteObject(Class<? extends Serializable> clazz) {
+        return getWriteObject(clazz) != null;
+    }
+
+    private boolean hasReadObjectNoData(Class<? extends Serializable> clazz) {
+        return getReadObjectNoData(clazz) != null;
     }
 
     /**
@@ -208,50 +248,23 @@ public class ClassDescriptorFactory {
      * @return Class descriptor.
      */
     private ClassDescriptor arbitrary(int descriptorId, Class<?> clazz) {
-        return new ClassDescriptor(clazz, descriptorId, fields(clazz), new Serialization(SerializationType.ARBITRARY));
-    }
-
-    /**
-     * Gets field descriptors of the class in the correct order (see {@link #classFields(Class)}. If a field's type
-     * doesn't have an id yet, generates it.
-     *
-     * @param clazz Class.
-     * @return List of field descriptor.
-     */
-    private List<FieldDescriptor> fields(Class<?> clazz) {
-        List<Class<?>> lineage = lineage(clazz);
-
-        return lineage.stream()
-                .flatMap(this::classFields)
-                .collect(toList());
-    }
-
-    /**
-     * Returns the lineage (all the ancestors, from Object down the line, including the given class).
-     *
-     * @param clazz class from which to obtain lineage
-     * @return ancestors from Object down the line, plus the given class itself
-     */
-    private List<Class<?>> lineage(Class<?> clazz) {
-        List<Class<?>> classes = new ArrayList<>();
-
-        Class<?> currentClass = clazz;
-        while (currentClass != null) {
-            classes.add(currentClass);
-            currentClass = currentClass.getSuperclass();
-        }
-
-        Collections.reverse(classes);
-        return classes;
+        return new ClassDescriptor(
+                clazz,
+                descriptorId,
+                superClassDescriptor(clazz),
+                fields(clazz),
+                new Serialization(SerializationType.ARBITRARY)
+        );
     }
 
     /**
-     * Returns 'serializable' (i.e. non-static non-transient) declared fields of the given class sorted lexicographically by their names.
+     * Returns descriptors of 'serializable' (i.e. non-static non-transient) declared fields of the given class
+     * sorted lexicographically by their names.
      *
      * @param clazz class
-     * @return properly sorted fields
+     * @return properly sorted field descriptors
      */
-    private Stream<FieldDescriptor> classFields(Class<?> clazz) {
+    private List<FieldDescriptor> fields(Class<?> clazz) {
         return Arrays.stream(clazz.getDeclaredFields())
                 .sorted(comparing(Field::getName))
                 .filter(field -> {
@@ -260,7 +273,8 @@ public class ClassDescriptorFactory {
                     // Ignore static and transient fields.
                     return !Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers);
                 })
-                .map(field -> new FieldDescriptor(field, context.getId(field.getType())));
+                .map(field -> new FieldDescriptor(field, context.getId(field.getType())))
+                .collect(toList());
     }
 
     /**
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/PerSessionSerializationService.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/PerSessionSerializationService.java
index d8a546c..fb92bd0 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/PerSessionSerializationService.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/PerSessionSerializationService.java
@@ -31,6 +31,7 @@ import org.apache.ignite.network.NetworkMessage;
 import org.apache.ignite.network.serialization.MessageDeserializer;
 import org.apache.ignite.network.serialization.MessageSerializer;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.jetbrains.annotations.TestOnly;
 
 /**
@@ -41,6 +42,9 @@ public class PerSessionSerializationService {
     /** Network messages factory. */
     private static final NetworkMessagesFactory MSG_FACTORY = new NetworkMessagesFactory();
 
+    /** Integer value that is sent when there is no descriptor. */
+    private static final int NO_DESCRIPTOR_ID = Integer.MIN_VALUE;
+
     /** Global serialization service. */
     @NotNull
     private final SerializationService serializationService;
@@ -114,7 +118,6 @@ public class PerSessionSerializationService {
                                 .name(d.name())
                                 .typeDescriptorId(d.typeDescriptorId())
                                 .className(d.clazz().getName())
-                                .declaringClassName(d.declaringClass().getName())
                                 .build();
                     })
                     .collect(toList());
@@ -126,14 +129,27 @@ public class PerSessionSerializationService {
                     .isFinal(descriptor.isFinal())
                     .serializationType(serialization.type().value())
                     .hasSerializationOverride(serialization.hasSerializationOverride())
+                    .hasReadObjectNoData(serialization.hasReadObjectNoData())
                     .hasWriteReplace(serialization.hasWriteReplace())
                     .hasReadResolve(serialization.hasReadResolve())
                     .descriptorId(descriptor.descriptorId())
                     .className(descriptor.className())
+                    .superClassDescriptorId(superClassDescriptorIdForMessage(descriptor))
+                    .superClassName(descriptor.superClassName())
                     .build();
         }).collect(toList());
     }
 
+    private int superClassDescriptorIdForMessage(ClassDescriptor descriptor) {
+        Integer id = descriptor.superClassDescriptorId();
+
+        if (id == null) {
+            return NO_DESCRIPTOR_ID;
+        }
+
+        return id;
+    }
+
     private void mergeDescriptors(List<ClassDescriptorMessage> remoteDescriptors) {
         for (ClassDescriptorMessage clsMsg : remoteDescriptors) {
             int clsDescriptorId = clsMsg.descriptorId();
@@ -164,7 +180,7 @@ public class PerSessionSerializationService {
         ClassDescriptor localDescriptor = serializationService.getClassDescriptor(clsMsg.className());
 
         List<FieldDescriptor> remoteFields = clsMsg.fields().stream()
-                .map(this::fieldDescriptorFromMessage)
+                .map(fieldMsg -> fieldDescriptorFromMessage(fieldMsg, localDescriptor.clazz()))
                 .collect(toList());
 
         SerializationType serializationType = SerializationType.getByValue(clsMsg.serializationType());
@@ -172,6 +188,7 @@ public class PerSessionSerializationService {
         var serialization = new Serialization(
                 serializationType,
                 clsMsg.hasSerializationOverride(),
+                clsMsg.hasReadObjectNoData(),
                 clsMsg.hasWriteReplace(),
                 clsMsg.hasReadResolve()
         );
@@ -179,6 +196,7 @@ public class PerSessionSerializationService {
         ClassDescriptor remoteDescriptor = new ClassDescriptor(
                 localDescriptor.clazz(),
                 clsMsg.descriptorId(),
+                superClassDescriptor(clsMsg),
                 remoteFields,
                 serialization
         );
@@ -186,9 +204,16 @@ public class PerSessionSerializationService {
         return mergeDescriptor(localDescriptor, remoteDescriptor);
     }
 
-    private FieldDescriptor fieldDescriptorFromMessage(FieldDescriptorMessage fieldMsg) {
+    @Nullable
+    private ClassDescriptor superClassDescriptor(ClassDescriptorMessage clsMsg) {
+        if (clsMsg.superClassDescriptorId() == NO_DESCRIPTOR_ID) {
+            return null;
+        }
+        return getClassDescriptor(clsMsg.superClassDescriptorId(), clsMsg.superClassName());
+    }
+
+    private FieldDescriptor fieldDescriptorFromMessage(FieldDescriptorMessage fieldMsg, Class<?> declaringClass) {
         int typeDescriptorId = fieldMsg.typeDescriptorId();
-        Class<?> declaringClass = serializationService.getClassDescriptor(fieldMsg.declaringClassName()).clazz();
         return new FieldDescriptor(fieldMsg.name(), getClass(typeDescriptorId, fieldMsg.className()), typeDescriptorId, declaringClass);
     }
 
@@ -197,14 +222,18 @@ public class PerSessionSerializationService {
         return remoteDescriptor;
     }
 
-    private Class<?> getClass(int descriptorId, String typeName) {
+    private ClassDescriptor getClassDescriptor(int descriptorId, String typeName) {
         if (serializationService.shouldBeBuiltIn(descriptorId)) {
-            return serializationService.getClassDescriptor(descriptorId).clazz();
+            return serializationService.getClassDescriptor(descriptorId);
         } else {
-            return serializationService.getClassDescriptor(typeName).clazz();
+            return serializationService.getClassDescriptor(typeName);
         }
     }
 
+    private Class<?> getClass(int descriptorId, String typeName) {
+        return getClassDescriptor(descriptorId, typeName).getClass();
+    }
+
     @TestOnly
     Map<Integer, ClassDescriptor> getDescriptorMapView() {
         return descriptorMapView;
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/Serialization.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/Serialization.java
index 0f58c70..19e247a 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/Serialization.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/Serialization.java
@@ -24,8 +24,10 @@ public class Serialization {
     /** Serialization type. */
     private final SerializationType type;
 
-    /** Whether a Serializable has writeObject()/readObject()/readObjectNoData() methods. */
+    /** Whether a Serializable has writeObject() + readObject() methods. */
     private final boolean hasSerializationOverride;
+    /** Whether a Serializable has readObjectNoData() method. */
+    private final boolean hasReadObjectNoData;
     /** Whether a Serializable/Externalizable has writeReplace() method. */
     private final boolean hasWriteReplace;
     /** Whether a Serializable/Externalizable has readResolve() method. */
@@ -35,17 +37,25 @@ public class Serialization {
      * Creates a new Serialization.
      *
      * @param type                     type
-     * @param hasSerializationOverride whether a Serializable has writeObject()/readObject()/readObjectNoData() methods
+     * @param hasSerializationOverride whether a Serializable has writeObject() + readObject() methods
+     * @param hasReadObjectNoData      whether a Serializable has readObjectNoData() method
      * @param hasWriteReplace          whether a Serializable/Externalizable has writeReplace() method
      * @param hasReadResolve           whether a Serializable/Externalizable has readResolve() method
      */
-    public Serialization(SerializationType type, boolean hasSerializationOverride, boolean hasWriteReplace, boolean hasReadResolve) {
+    public Serialization(
+            SerializationType type,
+            boolean hasSerializationOverride,
+            boolean hasReadObjectNoData,
+            boolean hasWriteReplace,
+            boolean hasReadResolve
+    ) {
         assert type == SerializationType.SERIALIZABLE
-                || (type == SerializationType.EXTERNALIZABLE && !hasSerializationOverride)
+                || (type == SerializationType.EXTERNALIZABLE && !hasSerializationOverride && !hasReadObjectNoData)
                 || (!hasSerializationOverride && !hasWriteReplace && !hasReadResolve);
 
         this.type = type;
         this.hasSerializationOverride = hasSerializationOverride;
+        this.hasReadObjectNoData = hasReadObjectNoData;
         this.hasWriteReplace = hasWriteReplace;
         this.hasReadResolve = hasReadResolve;
     }
@@ -56,7 +66,7 @@ public class Serialization {
      * @param type serialization type
      */
     public Serialization(SerializationType type) {
-        this(type, false, false, false);
+        this(type, false, false, false, false);
     }
 
     /**
@@ -69,15 +79,24 @@ public class Serialization {
     }
 
     /**
-     * Returns whether serialization override (writeObject()/readObject()/readObjectNoData()) is present.
+     * Returns whether serialization override (writeObject() + readObject()) is present.
      *
-     * @return whether serialization override (writeObject()/readObject()/readObjectNoData()) is present
+     * @return whether serialization override (writeObject() + readObject()) is present
      */
     public boolean hasSerializationOverride() {
         return hasSerializationOverride;
     }
 
     /**
+     * Returns whether readObjectNoData() method is present.
+     *
+     * @return whether readObjectNoData() method is present
+     */
+    public boolean hasReadObjectNoData() {
+        return hasReadObjectNoData;
+    }
+
+    /**
      * Returns whether writeReplace() method is present.
      *
      * @return whether writeReplace() method is present
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SerializedStreamCommands.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SerializedStreamCommands.java
index 1299519..b5f1af3 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SerializedStreamCommands.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SerializedStreamCommands.java
@@ -25,7 +25,7 @@ public class SerializedStreamCommands {
     /**
      * Reference: an object that was already seen in the graph, so we relate to it by its ID instead of serializing it again.
      */
-    public static final int REFERENCE = 45;
+    public static final int REFERENCE = 44;
 
     private SerializedStreamCommands() {
     }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SpecialSerializationMethods.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SpecialSerializationMethods.java
index 66ac74b..b9e6453 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SpecialSerializationMethods.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SpecialSerializationMethods.java
@@ -17,6 +17,9 @@
 
 package org.apache.ignite.internal.network.serialization;
 
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
 /**
  * Encapsulates special serialization methods like writeReplace()/readResolve() for convenient invocation.
  */
@@ -48,4 +51,30 @@ public interface SpecialSerializationMethods {
      * @throws SpecialMethodInvocationException if the invocation fails
      */
     Object readResolve(Object object) throws SpecialMethodInvocationException;
+
+    /**
+     * Invokes {@code writeObject()} on the target object. Should only be used if the target descriptor supports
+     * the method (that is, its serialization type is
+     * {@link org.apache.ignite.internal.network.serialization.SerializationType#SERIALIZABLE} and the class
+     * actually has writeObject() method).
+     * If any of these conditions fail, a {@link NullPointerException} will be thrown.
+     *
+     * @param object target object on which to invoke the method
+     * @param stream stream to pass to the method
+     * @throws SpecialMethodInvocationException if the invocation fails
+     */
+    void writeObject(Object object, ObjectOutputStream stream) throws SpecialMethodInvocationException;
+
+    /**
+     * Invokes {@code readObject()} on the target object. Should only be used if the target descriptor supports
+     * the method (that is, its serialization type is
+     * {@link org.apache.ignite.internal.network.serialization.SerializationType#SERIALIZABLE} and the class
+     * actually has readObject() method).
+     * If any of these conditions fail, a {@link NullPointerException} will be thrown.
+     *
+     * @param object target object on which to invoke the method
+     * @param stream stream to pass to the method
+     * @throws SpecialMethodInvocationException if the invocation fails
+     */
+    void readObject(Object object, ObjectInputStream stream) throws SpecialMethodInvocationException;
 }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SpecialSerializationMethodsImpl.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SpecialSerializationMethodsImpl.java
index 553c9dc..def5ff4 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SpecialSerializationMethodsImpl.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SpecialSerializationMethodsImpl.java
@@ -17,10 +17,11 @@
 
 package org.apache.ignite.internal.network.serialization;
 
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
-import java.lang.reflect.Method;
 import java.util.Objects;
 import org.jetbrains.annotations.Nullable;
 
@@ -36,6 +37,14 @@ class SpecialSerializationMethodsImpl implements SpecialSerializationMethods {
     @Nullable
     private final MethodHandle readResolveHandle;
 
+    /** MethodHandle that can be used to invoke writeObject() on the target class. */
+    @Nullable
+    private final MethodHandle writeObjectHandle;
+
+    /** MethodHandle that can be used to invoke readObject() on the target class. */
+    @Nullable
+    private final MethodHandle readObjectHandle;
+
     /**
      * Creates a new instance from the provided descriptor.
      *
@@ -44,45 +53,47 @@ class SpecialSerializationMethodsImpl implements SpecialSerializationMethods {
     public SpecialSerializationMethodsImpl(ClassDescriptor descriptor) {
         writeReplaceHandle = descriptor.hasWriteReplace() ? writeReplaceHandle(descriptor) : null;
         readResolveHandle = descriptor.hasReadResolve() ? readResolveHandle(descriptor) : null;
+        writeObjectHandle = descriptor.hasSerializationOverride() ? writeObjectHandle(descriptor) : null;
+        readObjectHandle = descriptor.hasSerializationOverride() ? readObjectHandle(descriptor) : null;
     }
 
     private static MethodHandle writeReplaceHandle(ClassDescriptor descriptor) {
-        Method writeReplaceMethod = findWriteReplaceMethod(descriptor);
-
-        return unreflect(writeReplaceMethod, MethodType.methodType(Object.class, Object.class), descriptor);
-    }
-
-    private static MethodHandle unreflect(Method method, MethodType methodType, ClassDescriptor descriptor) {
         try {
             return MethodHandles.privateLookupIn(descriptor.clazz(), MethodHandles.lookup())
-                        .unreflect(method)
-                        .asType(methodType);
-        } catch (IllegalAccessException e) {
-            throw new ReflectionException("Cannot unreflect", e);
+                    .findVirtual(descriptor.clazz(), "writeReplace", MethodType.methodType(Object.class))
+                    .asType(MethodType.methodType(Object.class, Object.class));
+        } catch (ReflectiveOperationException e) {
+            throw new ReflectionException("Cannot find writeReplace() in " + descriptor.clazz(), e);
         }
     }
 
-    private static Method findWriteReplaceMethod(ClassDescriptor descriptor) {
+    private static MethodHandle readResolveHandle(ClassDescriptor descriptor) {
         try {
-            return descriptor.clazz().getDeclaredMethod("writeReplace");
-        } catch (NoSuchMethodException e) {
-            throw new ReflectionException("writeReplace() was not found on " + descriptor.clazz()
-                    + " even though the descriptor says the class has the method", e);
+            return MethodHandles.privateLookupIn(descriptor.clazz(), MethodHandles.lookup())
+                    .findVirtual(descriptor.clazz(), "readResolve", MethodType.methodType(Object.class))
+                    .asType(MethodType.methodType(Object.class, Object.class));
+        } catch (ReflectiveOperationException e) {
+            throw new ReflectionException("Cannot find readResolve() in " + descriptor.clazz(), e);
         }
     }
 
-    private static MethodHandle readResolveHandle(ClassDescriptor descriptor) {
-        Method readResolveMethod = findReadResolveMethod(descriptor);
-
-        return unreflect(readResolveMethod, MethodType.methodType(Object.class, Object.class), descriptor);
+    private static MethodHandle writeObjectHandle(ClassDescriptor descriptor) {
+        try {
+            return MethodHandles.privateLookupIn(descriptor.clazz(), MethodHandles.lookup())
+                    .findVirtual(descriptor.clazz(), "writeObject", MethodType.methodType(void.class, ObjectOutputStream.class))
+                    .asType(MethodType.methodType(void.class, Object.class, ObjectOutputStream.class));
+        } catch (ReflectiveOperationException e) {
+            throw new ReflectionException("Cannot find writeObject() in " + descriptor.clazz(), e);
+        }
     }
 
-    private static Method findReadResolveMethod(ClassDescriptor descriptor) {
+    private static MethodHandle readObjectHandle(ClassDescriptor descriptor) {
         try {
-            return descriptor.clazz().getDeclaredMethod("readResolve");
-        } catch (NoSuchMethodException e) {
-            throw new ReflectionException("readResolve() was not found on " + descriptor.clazz()
-                    + " even though the descriptor says the class has the method", e);
+            return MethodHandles.privateLookupIn(descriptor.clazz(), MethodHandles.lookup())
+                    .findVirtual(descriptor.clazz(), "readObject", MethodType.methodType(void.class, ObjectInputStream.class))
+                    .asType(MethodType.methodType(void.class, Object.class, ObjectInputStream.class));
+        } catch (ReflectiveOperationException e) {
+            throw new ReflectionException("Cannot find readObject() in " + descriptor.clazz(), e);
         }
     }
 
@@ -113,4 +124,32 @@ class SpecialSerializationMethodsImpl implements SpecialSerializationMethods {
             throw new SpecialMethodInvocationException("readResolve() invocation failed on " + object, e);
         }
     }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeObject(Object object, ObjectOutputStream stream) throws SpecialMethodInvocationException {
+        Objects.requireNonNull(writeObjectHandle);
+
+        try {
+            writeObjectHandle.invokeExact(object, stream);
+        } catch (Error e) {
+            throw e;
+        } catch (Throwable e) {
+            throw new SpecialMethodInvocationException("writeObject() invocation failed on " + object, e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void readObject(Object object, ObjectInputStream stream) throws SpecialMethodInvocationException {
+        Objects.requireNonNull(readObjectHandle);
+
+        try {
+            readObjectHandle.invokeExact(object, stream);
+        } catch (Error e) {
+            throw e;
+        } catch (Throwable e) {
+            throw new SpecialMethodInvocationException("readObject() invocation failed on " + object, e);
+        }
+    }
 }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ArbitraryObjectMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ArbitraryObjectMarshaller.java
index cddc9cb..3ec8eeb 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ArbitraryObjectMarshaller.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ArbitraryObjectMarshaller.java
@@ -17,17 +17,22 @@
 
 package org.apache.ignite.internal.network.serialization.marshal;
 
-import java.io.DataInput;
-import java.io.DataOutput;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
 import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import org.apache.ignite.internal.network.serialization.ClassDescriptor;
 import org.apache.ignite.internal.network.serialization.ClassIndexedDescriptors;
 import org.apache.ignite.internal.network.serialization.FieldDescriptor;
+import org.apache.ignite.internal.network.serialization.SpecialMethodInvocationException;
 
 /**
  * (Un)marshals arbitrary objects (that is, objects that are not built-in nor serializable/externalizable).
  */
-class ArbitraryObjectMarshaller {
+class ArbitraryObjectMarshaller implements DefaultFieldsReaderWriter {
     private final TypedValueWriter valueWriter;
     private final ValueReader<Object> valueReader;
 
@@ -44,16 +49,71 @@ class ArbitraryObjectMarshaller {
         );
     }
 
-    void writeArbitraryObject(Object object, ClassDescriptor descriptor, DataOutput output, MarshallingContext context)
+    void writeArbitraryObject(Object object, ClassDescriptor descriptor, DataOutputStream output, MarshallingContext context)
             throws MarshalException, IOException {
-        context.addUsedDescriptor(descriptor);
+        for (ClassDescriptor layer : lineage(descriptor)) {
+            writeArbitraryObjectLayer(object, layer, output, context);
+        }
+    }
+
+    /**
+     * Returns the lineage (all the ancestors, from the progenitor (excluding Object) down the line, including the given class).
+     *
+     * @param descriptor class from which to obtain lineage
+     * @return ancestors from the progenitor (excluding Object) down the line, plus the given class itself
+     */
+    private List<ClassDescriptor> lineage(ClassDescriptor descriptor) {
+        List<ClassDescriptor> descriptors = new ArrayList<>();
+
+        ClassDescriptor currentDesc = descriptor;
+        while (currentDesc != null) {
+            descriptors.add(currentDesc);
+            currentDesc = currentDesc.superClassDescriptor();
+        }
 
+        Collections.reverse(descriptors);
+
+        return descriptors;
+    }
+
+    private void writeArbitraryObjectLayer(Object object, ClassDescriptor layer, DataOutputStream output, MarshallingContext context)
+            throws IOException, MarshalException {
+        if (layer.hasSerializationOverride()) {
+            writeWithWriteObject(object, layer, output, context);
+        } else {
+            defaultWriteFields(object, layer, output, context);
+        }
+
+        context.addUsedDescriptor(layer);
+    }
+
+
+    private void writeWithWriteObject(Object object, ClassDescriptor descriptor, DataOutputStream output, MarshallingContext context)
+            throws IOException, MarshalException {
+        context.startWritingWithWriteObject(object, descriptor);
+
+        try {
+            // Do not close the stream yet!
+            UosObjectOutputStream oos = context.objectOutputStream(output, valueWriter, this);
+            descriptor.serializationMethods().writeObject(object, oos);
+            oos.flush();
+        } catch (SpecialMethodInvocationException e) {
+                throw new MarshalException("Cannot invoke writeObject()", e);
+        } finally {
+            context.endWritingWithWriteObject();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void defaultWriteFields(Object object, ClassDescriptor descriptor, DataOutputStream output, MarshallingContext context)
+            throws MarshalException, IOException {
         for (FieldDescriptor fieldDescriptor : descriptor.fields()) {
             writeField(object, fieldDescriptor, output, context);
         }
     }
 
-    private void writeField(Object object, FieldDescriptor fieldDescriptor, DataOutput output, MarshallingContext context)
+    private void writeField(Object object, FieldDescriptor fieldDescriptor, DataOutputStream output, MarshallingContext context)
             throws MarshalException, IOException {
         Object fieldValue = fieldDescriptor.accessor().get(object);
 
@@ -68,7 +128,44 @@ class ArbitraryObjectMarshaller {
         }
     }
 
-    void fillArbitraryObjectFrom(DataInput input, Object object, ClassDescriptor descriptor, UnmarshallingContext context)
+    void fillArbitraryObjectFrom(DataInputStream input, Object object, ClassDescriptor descriptor, UnmarshallingContext context)
+            throws IOException, UnmarshalException {
+        for (ClassDescriptor layer : lineage(descriptor)) {
+            fillArbitraryObjectLayerFrom(input, layer, object, context);
+        }
+    }
+
+    private void fillArbitraryObjectLayerFrom(DataInputStream input, ClassDescriptor layer, Object object, UnmarshallingContext context)
+            throws IOException, UnmarshalException {
+        if (layer.hasSerializationOverride()) {
+            fillObjectWithReadObjectFrom(input, object, layer, context);
+        } else {
+            defaultFillFieldsFrom(input, object, layer, context);
+        }
+    }
+
+    private void fillObjectWithReadObjectFrom(
+            DataInputStream input,
+            Object object,
+            ClassDescriptor descriptor,
+            UnmarshallingContext context
+    ) throws IOException, UnmarshalException {
+        context.startReadingWithReadObject(object, descriptor);
+
+        // Do not close the stream yet!
+        ObjectInputStream ois = context.objectInputStream(input, valueReader, this);
+        try {
+            descriptor.serializationMethods().readObject(object, ois);
+        } catch (SpecialMethodInvocationException e) {
+            throw new UnmarshalException("Cannot invoke readObject()", e);
+        } finally {
+            context.endReadingWithReadObject();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void defaultFillFieldsFrom(DataInputStream input, Object object, ClassDescriptor descriptor, UnmarshallingContext context)
             throws IOException, UnmarshalException {
         for (FieldDescriptor fieldDescriptor : descriptor.fields()) {
             Object fieldValue = valueReader.read(input, context);
@@ -79,4 +176,5 @@ class ArbitraryObjectMarshaller {
     private void setFieldValue(Object target, FieldDescriptor fieldDescriptor, Object value) {
         fieldDescriptor.accessor().set(target, value);
     }
+
 }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java
index 2f9a8d7..426e46a 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java
@@ -20,7 +20,8 @@ package org.apache.ignite.internal.network.serialization.marshal;
 import static java.util.Collections.singletonList;
 
 import java.io.DataInput;
-import java.io.DataOutput;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -70,7 +71,7 @@ class BuiltInContainerMarshallers {
         this.elementWriter = elementWriter;
     }
 
-    void writeGenericRefArray(Object[] array, ClassDescriptor arrayDescriptor, DataOutput output, MarshallingContext context)
+    void writeGenericRefArray(Object[] array, ClassDescriptor arrayDescriptor, DataOutputStream output, MarshallingContext context)
             throws IOException, MarshalException {
         output.writeUTF(array.getClass().getComponentType().getName());
         writeCollection(Arrays.asList(array), arrayDescriptor, output, context);
@@ -80,12 +81,12 @@ class BuiltInContainerMarshallers {
         return BuiltInMarshalling.preInstantiateGenericRefArray(input);
     }
 
-    <T> void fillGenericRefArray(DataInput input, T[] array, ValueReader<T> elementReader, UnmarshallingContext context)
+    <T> void fillGenericRefArray(DataInputStream input, T[] array, ValueReader<T> elementReader, UnmarshallingContext context)
             throws IOException, UnmarshalException {
         BuiltInMarshalling.fillGenericRefArray(input, array, elementReader, context);
     }
 
-    void writeBuiltInCollection(Collection<?> object, ClassDescriptor descriptor, DataOutput output, MarshallingContext context)
+    void writeBuiltInCollection(Collection<?> object, ClassDescriptor descriptor, DataOutputStream output, MarshallingContext context)
             throws IOException, MarshalException {
         if (supportsAsMutableBuiltInCollection(descriptor)) {
             writeCollection(object, descriptor, output, context);
@@ -111,7 +112,7 @@ class BuiltInContainerMarshallers {
     private void writeCollection(
             Collection<?> collection,
             ClassDescriptor collectionDescriptor,
-            DataOutput output,
+            DataOutputStream output,
             MarshallingContext context
     ) throws IOException, MarshalException {
         context.addUsedDescriptor(collectionDescriptor);
@@ -124,7 +125,7 @@ class BuiltInContainerMarshallers {
         return (ValueWriter<T>) elementWriter;
     }
 
-    private void writeSingletonList(List<?> list, ClassDescriptor listDescriptor, DataOutput output, MarshallingContext context)
+    private void writeSingletonList(List<?> list, ClassDescriptor listDescriptor, DataOutputStream output, MarshallingContext context)
             throws MarshalException, IOException {
         assert list.size() == 1;
 
@@ -174,7 +175,7 @@ class BuiltInContainerMarshallers {
     }
 
     <T, C extends Collection<T>> void fillBuiltInCollectionFrom(
-            DataInput input,
+            DataInputStream input,
             C collection,
             ClassDescriptor collectionDescriptor,
             ValueReader<T> elementReader,
@@ -189,7 +190,7 @@ class BuiltInContainerMarshallers {
         BuiltInMarshalling.fillCollectionFrom(input, collection, elementReader, context);
     }
 
-    void writeBuiltInMap(Map<?, ?> map, ClassDescriptor mapDescriptor, DataOutput output, MarshallingContext context)
+    void writeBuiltInMap(Map<?, ?> map, ClassDescriptor mapDescriptor, DataOutputStream output, MarshallingContext context)
             throws MarshalException, IOException {
         if (!supportsAsBuiltInMap(mapDescriptor)) {
             throw new IllegalStateException("Marshalling of " + mapDescriptor.clazz() + " is not supported, but it's marked as a built-in");
@@ -197,13 +198,7 @@ class BuiltInContainerMarshallers {
 
         context.addUsedDescriptor(mapDescriptor);
 
-        BuiltInMarshalling.writeMap(
-                map,
-                output,
-                valueWriter(),
-                valueWriter(),
-                context
-        );
+        BuiltInMarshalling.writeMap(map, output, valueWriter(), valueWriter(), context);
     }
 
     private boolean supportsAsBuiltInMap(ClassDescriptor mapDescriptor) {
@@ -238,7 +233,7 @@ class BuiltInContainerMarshallers {
     }
 
     <K, V, M extends Map<K, V>> void fillBuiltInMapFrom(
-            DataInput input,
+            DataInputStream input,
             M map,
             ValueReader<K> keyReader,
             ValueReader<V> valueReader,
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java
index cb12b8a..3aebf83 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java
@@ -18,9 +18,13 @@
 package org.apache.ignite.internal.network.serialization.marshal;
 
 import static java.util.Collections.singletonList;
+import static org.apache.ignite.internal.network.serialization.marshal.ProtocolMarshalling.readLength;
+import static org.apache.ignite.internal.network.serialization.marshal.ProtocolMarshalling.writeLength;
 
 import java.io.DataInput;
+import java.io.DataInputStream;
 import java.io.DataOutput;
+import java.io.DataOutputStream;
 import java.io.IOException;
 import java.lang.reflect.Array;
 import java.lang.reflect.Field;
@@ -101,30 +105,26 @@ class BuiltInMarshalling {
     }
 
     static void writeByteArray(byte[] array, DataOutput output) throws IOException {
-        output.writeInt(array.length);
-        for (byte b : array) {
-            output.writeByte(b);
-        }
+        writeLength(array.length, output);
+        output.write(array);
     }
 
     static byte[] readByteArray(DataInput input) throws IOException {
-        int length = input.readInt();
+        int length = readLength(input);
         byte[] array = new byte[length];
-        for (int i = 0; i < length; i++) {
-            array[i] = input.readByte();
-        }
+        input.readFully(array);
         return array;
     }
 
     static void writeShortArray(short[] array, DataOutput output) throws IOException {
-        output.writeInt(array.length);
+        writeLength(array.length, output);
         for (short sh : array) {
             output.writeShort(sh);
         }
     }
 
     static short[] readShortArray(DataInput input) throws IOException {
-        int length = input.readInt();
+        int length = readLength(input);
         short[] array = new short[length];
         for (int i = 0; i < length; i++) {
             array[i] = input.readShort();
@@ -133,14 +133,14 @@ class BuiltInMarshalling {
     }
 
     static void writeIntArray(int[] array, DataOutput output) throws IOException {
-        output.writeInt(array.length);
+        writeLength(array.length, output);
         for (int sh : array) {
             output.writeInt(sh);
         }
     }
 
     static int[] readIntArray(DataInput input) throws IOException {
-        int length = input.readInt();
+        int length = readLength(input);
         int[] array = new int[length];
         for (int i = 0; i < length; i++) {
             array[i] = input.readInt();
@@ -149,14 +149,14 @@ class BuiltInMarshalling {
     }
 
     static void writeFloatArray(float[] array, DataOutput output) throws IOException {
-        output.writeInt(array.length);
+        writeLength(array.length, output);
         for (float sh : array) {
             output.writeFloat(sh);
         }
     }
 
     static float[] readFloatArray(DataInput input) throws IOException {
-        int length = input.readInt();
+        int length = readLength(input);
         float[] array = new float[length];
         for (int i = 0; i < length; i++) {
             array[i] = input.readFloat();
@@ -165,14 +165,14 @@ class BuiltInMarshalling {
     }
 
     static void writeLongArray(long[] array, DataOutput output) throws IOException {
-        output.writeInt(array.length);
+        writeLength(array.length, output);
         for (long sh : array) {
             output.writeLong(sh);
         }
     }
 
     static long[] readLongArray(DataInput input) throws IOException {
-        int length = input.readInt();
+        int length = readLength(input);
         long[] array = new long[length];
         for (int i = 0; i < length; i++) {
             array[i] = input.readLong();
@@ -181,14 +181,14 @@ class BuiltInMarshalling {
     }
 
     static void writeDoubleArray(double[] array, DataOutput output) throws IOException {
-        output.writeInt(array.length);
+        writeLength(array.length, output);
         for (double sh : array) {
             output.writeDouble(sh);
         }
     }
 
     static double[] readDoubleArray(DataInput input) throws IOException {
-        int length = input.readInt();
+        int length = readLength(input);
         double[] array = new double[length];
         for (int i = 0; i < length; i++) {
             array[i] = input.readDouble();
@@ -197,14 +197,14 @@ class BuiltInMarshalling {
     }
 
     static void writeBooleanArray(boolean[] array, DataOutput output) throws IOException {
-        output.writeInt(array.length);
+        writeLength(array.length, output);
         for (boolean sh : array) {
             output.writeBoolean(sh);
         }
     }
 
     static boolean[] readBooleanArray(DataInput input) throws IOException {
-        int length = input.readInt();
+        int length = readLength(input);
         boolean[] array = new boolean[length];
         for (int i = 0; i < length; i++) {
             array[i] = input.readBoolean();
@@ -213,14 +213,14 @@ class BuiltInMarshalling {
     }
 
     static void writeCharArray(char[] array, DataOutput output) throws IOException {
-        output.writeInt(array.length);
+        writeLength(array.length, output);
         for (char sh : array) {
             output.writeChar(sh);
         }
     }
 
     static char[] readCharArray(DataInput input) throws IOException {
-        int length = input.readInt();
+        int length = readLength(input);
         char[] array = new char[length];
         for (int i = 0; i < length; i++) {
             array[i] = input.readChar();
@@ -270,17 +270,17 @@ class BuiltInMarshalling {
         }
     }
 
-    static <T> void writeRefArray(T[] array, DataOutput output, ValueWriter<T> valueWriter, MarshallingContext context)
+    static <T> void writeRefArray(T[] array, DataOutputStream output, ValueWriter<T> valueWriter, MarshallingContext context)
             throws IOException, MarshalException {
-        output.writeInt(array.length);
+        writeLength(array.length, output);
         for (T object : array) {
             valueWriter.write(object, output, context);
         }
     }
 
-    static <T> T[] readRefArray(DataInput input, IntFunction<T[]> arrayFactory, ValueReader<T> valueReader, UnmarshallingContext context)
+    static <T> T[] readRefArray(DataInputStream input, IntFunction<T[]> arrayFactory, ValueReader<T> valueReader, UnmarshallingContext context)
             throws IOException, UnmarshalException {
-        int length = input.readInt();
+        int length = readLength(input);
 
         T[] array = arrayFactory.apply(length);
         fillRefArrayFrom(input, array, valueReader, context);
@@ -288,7 +288,7 @@ class BuiltInMarshalling {
         return array;
     }
 
-    private static <T> void fillRefArrayFrom(DataInput input, T[] array, ValueReader<T> valueReader, UnmarshallingContext context)
+    private static <T> void fillRefArrayFrom(DataInputStream input, T[] array, ValueReader<T> valueReader, UnmarshallingContext context)
             throws IOException, UnmarshalException {
         for (int i = 0; i < array.length; i++) {
             array[i] = valueReader.read(input, context);
@@ -308,42 +308,42 @@ class BuiltInMarshalling {
         return arrayFactory.apply(length);
     }
 
-    static <T> void fillGenericRefArray(DataInput input, T[] array, ValueReader<T> elementReader, UnmarshallingContext context)
+    static <T> void fillGenericRefArray(DataInputStream input, T[] array, ValueReader<T> elementReader, UnmarshallingContext context)
             throws IOException, UnmarshalException {
         fillRefArrayFrom(input, array, elementReader, context);
     }
 
-    static void writeStringArray(String[] array, DataOutput output, MarshallingContext context) throws IOException, MarshalException {
+    static void writeStringArray(String[] array, DataOutputStream output, MarshallingContext context) throws IOException, MarshalException {
         writeRefArray(array, output, stringWriter, context);
     }
 
-    static String[] readStringArray(DataInput input, UnmarshallingContext context) throws IOException, UnmarshalException {
+    static String[] readStringArray(DataInputStream input, UnmarshallingContext context) throws IOException, UnmarshalException {
         return readRefArray(input, stringArrayFactory, stringReader, context);
     }
 
-    static void writeBigDecimalArray(BigDecimal[] array, DataOutput output, MarshallingContext context)
+    static void writeBigDecimalArray(BigDecimal[] array, DataOutputStream output, MarshallingContext context)
             throws IOException, MarshalException {
         writeRefArray(array, output, bigDecimalWriter, context);
     }
 
-    static BigDecimal[] readBigDecimalArray(DataInput input, UnmarshallingContext context) throws IOException, UnmarshalException {
+    static BigDecimal[] readBigDecimalArray(DataInputStream input, UnmarshallingContext context) throws IOException, UnmarshalException {
         return readRefArray(input, bigDecimalArrayFactory, bigDecimalReader, context);
     }
 
-    static void writeEnumArray(Enum<?>[] array, DataOutput output, MarshallingContext context) throws IOException, MarshalException {
+    static void writeEnumArray(Enum<?>[] array, DataOutputStream output, MarshallingContext context) throws IOException, MarshalException {
         output.writeUTF(array.getClass().getComponentType().getName());
         writeRefArray(array, output, enumWriter, context);
     }
 
-    static Enum<?>[] readEnumArray(DataInput input, UnmarshallingContext context) throws IOException, UnmarshalException {
+    static Enum<?>[] readEnumArray(DataInputStream input, UnmarshallingContext context) throws IOException, UnmarshalException {
         String enumClassName = input.readUTF();
         Class<? extends Enum<?>> enumClass = enumClass(enumClassName);
         return readRefArray(input, len -> (Enum<?>[]) Array.newInstance(enumClass, len), enumReader, context);
     }
 
-    static <T> void writeCollection(Collection<T> collection, DataOutput output, ValueWriter<T> valueWriter, MarshallingContext context)
+    static <T> void writeCollection(Collection<T> collection, DataOutputStream output, ValueWriter<T> valueWriter, MarshallingContext context)
             throws IOException, MarshalException {
-        output.writeInt(collection.size());
+        writeLength(collection.size(), output);
 
         for (T object : collection) {
             valueWriter.write(object, output, context);
@@ -351,12 +351,12 @@ class BuiltInMarshalling {
     }
 
     static <T, C extends Collection<T>> void fillCollectionFrom(
-            DataInput input,
+            DataInputStream input,
             C collection,
             ValueReader<T> valueReader,
             UnmarshallingContext context
     ) throws IOException, UnmarshalException {
-        int length = input.readInt();
+        int length = readLength(input);
 
         for (int i = 0; i < length; i++) {
             collection.add(valueReader.read(input, context));
@@ -369,7 +369,7 @@ class BuiltInMarshalling {
     }
 
     static <T, C extends Collection<T>> void fillSingletonCollectionFrom(
-            DataInput input,
+            DataInputStream input,
             C collection,
             ValueReader<T> elementReader,
             UnmarshallingContext context
@@ -385,12 +385,12 @@ class BuiltInMarshalling {
 
     static <K, V> void writeMap(
             Map<K, V> map,
-            DataOutput output,
+            DataOutputStream output,
             ValueWriter<K> keyWriter,
             ValueWriter<V> valueWriter,
             MarshallingContext context
     ) throws IOException, MarshalException {
-        output.writeInt(map.size());
+        writeLength(map.size(), output);
 
         for (Map.Entry<K, V> entry : map.entrySet()) {
             keyWriter.write(entry.getKey(), output, context);
@@ -399,13 +399,14 @@ class BuiltInMarshalling {
     }
 
     static <K, V, M extends Map<K, V>> void fillMapFrom(
-            DataInput input,
+            DataInputStream input,
             M map,
             ValueReader<K> keyReader,
             ValueReader<V> valueReader,
             UnmarshallingContext context
     ) throws IOException, UnmarshalException {
-        int length = input.readInt();
+        int length = readLength(input);
+
         for (int i = 0; i < length; i++) {
             map.put(keyReader.read(input, context), valueReader.read(input, context));
         }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInNonContainerMarshallers.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInNonContainerMarshallers.java
index 7cd0758..ada6102 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInNonContainerMarshallers.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInNonContainerMarshallers.java
@@ -18,7 +18,9 @@
 package org.apache.ignite.internal.network.serialization.marshal;
 
 import java.io.DataInput;
+import java.io.DataInputStream;
 import java.io.DataOutput;
+import java.io.DataOutputStream;
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.BitSet;
@@ -67,7 +69,6 @@ class BuiltInNonContainerMarshallers {
         addSingle(map, Enum[].class, BuiltInMarshalling::writeEnumArray, BuiltInMarshalling::readEnumArray);
         addSingle(map, BitSet.class, BuiltInMarshalling::writeBitSet, BuiltInMarshalling::readBitSet);
         addSingle(map, Null.class, (obj, output) -> {}, input -> null);
-        addSingle(map, Void.class, (obj, output) -> {}, input -> null);
 
         return Map.copyOf(map);
     }
@@ -127,7 +128,7 @@ class BuiltInNonContainerMarshallers {
         return builtInMarshallers.containsKey(classToCheck);
     }
 
-    void writeBuiltIn(Object object, ClassDescriptor descriptor, DataOutput output, MarshallingContext context)
+    void writeBuiltIn(Object object, ClassDescriptor descriptor, DataOutputStream output, MarshallingContext context)
             throws IOException, MarshalException {
         BuiltInMarshaller<?> builtInMarshaller = findBuiltInMarshaller(descriptor);
 
@@ -136,7 +137,7 @@ class BuiltInNonContainerMarshallers {
         context.addUsedDescriptor(descriptor);
     }
 
-    Object readBuiltIn(ClassDescriptor descriptor, DataInput input, UnmarshallingContext context) throws IOException, UnmarshalException {
+    Object readBuiltIn(ClassDescriptor descriptor, DataInputStream input, UnmarshallingContext context) throws IOException, UnmarshalException {
         BuiltInMarshaller<?> builtinMarshaller = findBuiltInMarshaller(descriptor);
         return builtinMarshaller.unmarshal(input, context);
     }
@@ -160,11 +161,11 @@ class BuiltInNonContainerMarshallers {
             this.reader = reader;
         }
 
-        private void marshal(Object object, DataOutput output, MarshallingContext context) throws IOException, MarshalException {
+        private void marshal(Object object, DataOutputStream output, MarshallingContext context) throws IOException, MarshalException {
             writer.write(valueRefClass.cast(object), output, context);
         }
 
-        private Object unmarshal(DataInput input, UnmarshallingContext context) throws IOException, UnmarshalException {
+        private Object unmarshal(DataInputStream input, UnmarshallingContext context) throws IOException, UnmarshalException {
             return reader.read(input, context);
         }
     }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultFieldsReaderWriter.java
similarity index 62%
copy from modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
copy to modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultFieldsReaderWriter.java
index fe18d79..d0ec6b3 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultFieldsReaderWriter.java
@@ -17,21 +17,18 @@
 
 package org.apache.ignite.internal.network.serialization.marshal;
 
-import java.io.DataOutput;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
 import java.io.IOException;
+import org.apache.ignite.internal.network.serialization.ClassDescriptor;
 
 /**
- * Knows how to write a value to a {@link DataOutput}.
+ *
  */
-interface ValueWriter<T> {
-    /**
-     * Writes the given value to a {@link DataOutput}.
-     *
-     * @param value     value to write
-     * @param output    where to write to
-     * @param context   marshalling context
-     * @throws IOException      if an I/O problem occurs
-     * @throws MarshalException if another problem occurs
-     */
-    void write(T value, DataOutput output, MarshallingContext context) throws IOException, MarshalException;
+interface DefaultFieldsReaderWriter {
+    void defaultWriteFields(Object object, ClassDescriptor descriptor, DataOutputStream output, MarshallingContext context)
+            throws MarshalException, IOException;
+
+    void defaultFillFieldsFrom(DataInputStream input, Object object, ClassDescriptor descriptor, UnmarshallingContext context)
+            throws IOException, UnmarshalException;
 }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
index 146ada9..3a16ff7 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
@@ -52,8 +52,8 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
     private final BuiltInContainerMarshallers builtInContainerMarshallers = new BuiltInContainerMarshallers(
             (obj, out, ctx) -> marshalToOutput(obj, objectClass(obj), out, ctx)
     );
-    private final ExternalizableMarshaller externalizableMarshaller = new ExternalizableMarshaller();
     private final ArbitraryObjectMarshaller arbitraryObjectMarshaller;
+    private final ExternalizableMarshaller externalizableMarshaller;
 
     /**
      * Constructor.
@@ -70,6 +70,8 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
                 this::marshalToOutput,
                 this::unmarshalFromInput
         );
+
+        externalizableMarshaller = new ExternalizableMarshaller(this::unmarshalFromInput, this::marshalToOutput, arbitraryObjectMarshaller);
     }
 
     public MarshalledObject marshal(@Nullable Object object) throws MarshalException {
@@ -91,7 +93,7 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
         return new MarshalledObject(baos.toByteArray(), context.usedDescriptors());
     }
 
-    private void marshalToOutput(@Nullable Object object, Class<?> declaredClass, DataOutput output, MarshallingContext context)
+    private void marshalToOutput(@Nullable Object object, Class<?> declaredClass, DataOutputStream output, MarshallingContext context)
             throws MarshalException, IOException {
         assert declaredClass != null;
         assert object == null
@@ -102,7 +104,9 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
 
         throwIfMarshallingNotSupported(object);
 
-        DescribedObject writeReplaced = applyWriteReplaceIfNeeded(object, declaredClass);
+        ClassDescriptor originalDescriptor = obtainOriginalDescriptor(object, declaredClass);
+
+        DescribedObject writeReplaced = applyWriteReplaceIfNeeded(object, originalDescriptor);
 
         if (canParticipateInCycles(writeReplaced.descriptor)) {
             Integer maybeRefId = context.rememberAsSeen(writeReplaced.object);
@@ -126,6 +130,16 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
         return !builtInNonContainerMarshallers.supports(descriptor.clazz());
     }
 
+    private ClassDescriptor obtainOriginalDescriptor(@Nullable Object object, Class<?> declaredClass) {
+        // object class is not a subclass of the declared class for primitives
+        // for enums we don't need the specific classes at all
+        Class<?> classToQueryForOriginalDescriptor = isInstanceOfSubclass(object, declaredClass)
+                && !(object instanceof Enum)
+                ? object.getClass() : declaredClass;
+
+        return getOrCreateDescriptor(classToQueryForOriginalDescriptor);
+    }
+
     private boolean objectIsMemberOfEnumWithAnonymousClassesForMembers(Object object, Class<?> declaredClass) {
         return declaredClass.isEnum() && object.getClass().getSuperclass() == declaredClass;
     }
@@ -162,22 +176,19 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
         return false;
     }
 
-    private DescribedObject applyWriteReplaceIfNeeded(@Nullable Object originalObject, Class<?> declaredClass) throws MarshalException {
-        // object class is not a subclass of the declared class for primitives
-        // for enums we don't need the specific classes at all
-        Class<?> classToQueryForOriginalDescriptor = isInstanceOfSubclass(originalObject, declaredClass)
-                && !(originalObject instanceof Enum)
-                ? originalObject.getClass() : declaredClass;
-
-        final ClassDescriptor originalDescriptor = getOrCreateDescriptor(classToQueryForOriginalDescriptor);
+    private DescribedObject applyWriteReplaceIfNeeded(@Nullable Object objectBefore, ClassDescriptor descriptorBefore)
+            throws MarshalException {
+        if (!descriptorBefore.supportsWriteReplace()) {
+            return new DescribedObject(objectBefore, descriptorBefore);
+        }
 
-        if (originalDescriptor.supportsWriteReplace()) {
-            Object objectToWrite = applyWriteReplace(originalObject, originalDescriptor);
-            ClassDescriptor descriptorToUse = getOrCreateDescriptor(objectToWrite, objectClass(objectToWrite));
+        Object replacedObject = applyWriteReplace(objectBefore, descriptorBefore);
+        ClassDescriptor replacementDescriptor = getOrCreateDescriptor(replacedObject, objectClass(replacedObject));
 
-            return new DescribedObject(objectToWrite, descriptorToUse);
+        if (descriptorBefore.describesSameClass(replacementDescriptor)) {
+            return new DescribedObject(replacedObject, replacementDescriptor);
         } else {
-            return new DescribedObject(originalObject, originalDescriptor);
+            return applyWriteReplaceIfNeeded(replacedObject, replacementDescriptor);
         }
     }
 
@@ -195,11 +206,7 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
     }
 
     private ClassDescriptor getOrCreateDescriptor(@Nullable Object object, Class<?> declaredClass) {
-        assert object != null || declaredClass == Void.class || declaredClass == Null.class;
-
-        if (declaredClass == Void.class) {
-            return descriptorRegistry.getRequiredDescriptor(Void.class);
-        }
+        assert object != null || declaredClass == Null.class;
 
         if (object == null) {
             return descriptorRegistry.getNullDescriptor();
@@ -237,18 +244,18 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
 
     private void writeReference(int referenceId, DataOutput output) throws IOException {
         writeDescriptorOrCommandId(SerializedStreamCommands.REFERENCE, output);
-        writeReferenceId(referenceId, output);
+        ProtocolMarshalling.writeObjectId(referenceId, output);
     }
 
-    private void marshalCycleable(DescribedObject describedObject, DataOutput output, MarshallingContext context)
+    private void marshalCycleable(DescribedObject describedObject, DataOutputStream output, MarshallingContext context)
             throws IOException, MarshalException {
         writeDescriptorId(describedObject.descriptor, output);
-        writeReferenceId(context.referenceId(describedObject.object), output);
+        ProtocolMarshalling.writeObjectId(context.referenceId(describedObject.object), output);
 
         writeObject(describedObject.object, describedObject.descriptor, output, context);
     }
 
-    private void marshalNonCycleable(DescribedObject describedObject, DataOutput output, MarshallingContext context)
+    private void marshalNonCycleable(DescribedObject describedObject, DataOutputStream output, MarshallingContext context)
             throws IOException, MarshalException {
         writeDescriptorId(describedObject.descriptor, output);
 
@@ -260,14 +267,10 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
     }
 
     private void writeDescriptorOrCommandId(int id, DataOutput output) throws IOException {
-        output.writeInt(id);
-    }
-
-    private void writeReferenceId(int referenceId, DataOutput output) throws IOException {
-        output.writeInt(referenceId);
+        ProtocolMarshalling.writeDescriptorOrCommandId(id, output);
     }
 
-    private void writeObject(@Nullable Object object, ClassDescriptor descriptor, DataOutput output, MarshallingContext context)
+    private void writeObject(@Nullable Object object, ClassDescriptor descriptor, DataOutputStream output, MarshallingContext context)
             throws IOException, MarshalException {
         if (isBuiltInNonContainer(descriptor)) {
             builtInNonContainerMarshallers.writeBuiltIn(object, descriptor, output, context);
@@ -307,14 +310,18 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
     public <T> T unmarshal(byte[] bytes, IdIndexedDescriptors mergedDescriptors) throws UnmarshalException {
         try (var bais = new ByteArrayInputStream(bytes); var dis = new DataInputStream(bais)) {
             UnmarshallingContext context = new UnmarshallingContext(bais, mergedDescriptors);
-            return unmarshalFromInput(dis, context);
+            T result = unmarshalFromInput(dis, context);
+
+            throwIfExcessiveBytesRemain(dis);
+
+            return result;
         } catch (IOException e) {
             throw new UnmarshalException("Cannot unmarshal", e);
         }
     }
 
-    private <T> T unmarshalFromInput(DataInput input, UnmarshallingContext context) throws IOException, UnmarshalException {
-        int commandOrDescriptorId = readDescriptorOrCommandId(input);
+    private <T> T unmarshalFromInput(DataInputStream input, UnmarshallingContext context) throws IOException, UnmarshalException {
+        int commandOrDescriptorId = ProtocolMarshalling.readDescriptorOrCommandId(input);
         if (commandOrDescriptorId == SerializedStreamCommands.REFERENCE) {
             // TODO: IGNITE-16165 - make sure readResolve() is applied correctly when we exit early due to reading a reference
             return unmarshalReference(input, context);
@@ -332,31 +339,23 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
         return resolvedObject;
     }
 
-    private int readDescriptorOrCommandId(DataInput input) throws IOException {
-        return input.readInt();
-    }
-
     private <T> T unmarshalReference(DataInput input, UnmarshallingContext context) throws IOException {
-        int referenceId = input.readInt();
-        return context.dereference(referenceId);
+        int objectId = ProtocolMarshalling.readObjectId(input);
+        return context.dereference(objectId);
     }
 
-    private Object readCycleable(DataInput input, UnmarshallingContext context, ClassDescriptor descriptor)
+    private Object readCycleable(DataInputStream input, UnmarshallingContext context, ClassDescriptor descriptor)
             throws IOException, UnmarshalException {
-        int referenceId = readReferenceId(input);
+        int objectId = ProtocolMarshalling.readObjectId(input);
 
         Object preInstantiatedObject = preInstantiate(descriptor, input, context);
-        context.registerReference(referenceId, preInstantiatedObject);
+        context.registerReference(objectId, preInstantiatedObject);
 
         fillObjectFrom(input, preInstantiatedObject, descriptor, context);
 
         return preInstantiatedObject;
     }
 
-    private int readReferenceId(DataInput input) throws IOException {
-        return input.readInt();
-    }
-
     private Object preInstantiate(ClassDescriptor descriptor, DataInput input, UnmarshallingContext context)
             throws IOException, UnmarshalException {
         if (isBuiltInNonContainer(descriptor)) {
@@ -378,7 +377,7 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
         return builtInContainerMarshallers.preInstantiateGenericRefArray(input);
     }
 
-    private void fillObjectFrom(DataInput input, Object preInstantiatedObject, ClassDescriptor descriptor, UnmarshallingContext context)
+    private void fillObjectFrom(DataInputStream input, Object preInstantiatedObject, ClassDescriptor descriptor, UnmarshallingContext context)
             throws UnmarshalException, IOException {
         if (isBuiltInNonContainer(descriptor)) {
             throw new IllegalStateException("Cannot fill " + descriptor.clazz() + ", this is a programmatic error");
@@ -389,14 +388,14 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
         } else if (isArray(descriptor)) {
             fillGenericRefArrayFrom(input, (Object[]) preInstantiatedObject, context);
         } else if (descriptor.isExternalizable()) {
-            externalizableMarshaller.fillExternalizableFrom(input, (Externalizable) preInstantiatedObject);
+            externalizableMarshaller.fillExternalizableFrom(input, (Externalizable) preInstantiatedObject, context);
         } else {
             arbitraryObjectMarshaller.fillArbitraryObjectFrom(input, preInstantiatedObject, descriptor, context);
         }
     }
 
     private void fillBuiltInCollectionFrom(
-            DataInput input,
+            DataInputStream input,
             Collection<?> preInstantiatedObject,
             ClassDescriptor descriptor,
             UnmarshallingContext context
@@ -405,7 +404,7 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
     }
 
     private void fillBuiltInMapFrom(
-            DataInput input,
+            DataInputStream input,
             Map<?, ?> preInstantiatedObject,
             UnmarshallingContext context
     ) throws UnmarshalException, IOException {
@@ -417,13 +416,13 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
         );
     }
 
-    private void fillGenericRefArrayFrom(DataInput input, Object[] array, UnmarshallingContext context)
+    private void fillGenericRefArrayFrom(DataInputStream input, Object[] array, UnmarshallingContext context)
             throws IOException, UnmarshalException {
         builtInContainerMarshallers.fillGenericRefArray(input, array, this::unmarshalFromInput, context);
     }
 
     @Nullable
-    private Object readObject(DataInput input, ClassDescriptor descriptor, UnmarshallingContext context)
+    private Object readObject(DataInputStream input, ClassDescriptor descriptor, UnmarshallingContext context)
             throws IOException, UnmarshalException {
         if (isBuiltInNonContainer(descriptor)) {
             return builtInNonContainerMarshallers.readBuiltIn(descriptor, input, context);
@@ -448,6 +447,12 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
         }
     }
 
+    private void throwIfExcessiveBytesRemain(DataInputStream dis) throws IOException, UnmarshalException {
+        if (dis.available() > 0) {
+            throw new UnmarshalException("After reading a value, " + dis.available() + " excessive byte(s) still remain");
+        }
+    }
+
     private static class DescribedObject {
         @Nullable
         private final Object object;
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ExternalizableMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ExternalizableMarshaller.java
index 6ace075..f52d6bc 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ExternalizableMarshaller.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ExternalizableMarshaller.java
@@ -17,39 +17,44 @@
 
 package org.apache.ignite.internal.network.serialization.marshal;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.DataInput;
-import java.io.DataOutput;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
 import java.io.Externalizable;
 import java.io.IOException;
 import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
 import org.apache.ignite.internal.network.serialization.ClassDescriptor;
 
 /**
  * (Um)marshalling specific to EXTERNALIZABLE serialization type.
  */
 class ExternalizableMarshaller {
+    private final ValueReader<Object> valueReader;
+    private final TypedValueWriter typedValueWriter;
+    private final DefaultFieldsReaderWriter defaultFieldsReaderWriter;
+
     private final NoArgConstructorInstantiation instantiation = new NoArgConstructorInstantiation();
 
-    void writeExternalizable(Externalizable externalizable, ClassDescriptor descriptor, DataOutput output, MarshallingContext context)
-            throws IOException {
-        byte[] externalizableBytes = externalize(externalizable);
+    ExternalizableMarshaller(ValueReader<Object> valueReader, TypedValueWriter typedValueWriter, DefaultFieldsReaderWriter defaultFieldsReaderWriter) {
+        this.valueReader = valueReader;
+        this.typedValueWriter = typedValueWriter;
+        this.defaultFieldsReaderWriter = defaultFieldsReaderWriter;
+    }
 
-        output.writeInt(externalizableBytes.length);
-        output.write(externalizableBytes);
+    void writeExternalizable(Externalizable externalizable, ClassDescriptor descriptor, DataOutputStream output, MarshallingContext context)
+            throws IOException {
+        externalizeTo(externalizable, output, context);
 
         context.addUsedDescriptor(descriptor);
     }
 
-    private byte[] externalize(Externalizable externalizable) throws IOException {
-        var baos = new ByteArrayOutputStream();
-        try (var oos = new ObjectOutputStream(baos)) {
-            externalizable.writeExternal(oos);
-        }
+    private void externalizeTo(Externalizable externalizable, DataOutputStream output, MarshallingContext context)
+            throws IOException {
+        context.endWritingWithWriteObject();
 
-        return baos.toByteArray();
+        // Do not close the stream yet!
+        UosObjectOutputStream oos = context.objectOutputStream(output, typedValueWriter, defaultFieldsReaderWriter);
+        externalizable.writeExternal(oos);
+        oos.flush();
     }
 
     @SuppressWarnings("unchecked")
@@ -61,12 +66,13 @@ class ExternalizableMarshaller {
         }
     }
 
-    <T extends Externalizable> void fillExternalizableFrom(DataInput input, T object) throws IOException, UnmarshalException {
-        int length = input.readInt();
-        byte[] bytes = new byte[length];
-        input.readFully(bytes);
+    <T extends Externalizable> void fillExternalizableFrom(DataInputStream input, T object, UnmarshallingContext context)
+            throws IOException, UnmarshalException {
+        context.endReadingWithReadObject();
 
-        try (var ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
+        // Do not close the stream yet!
+        ObjectInputStream ois = context.objectInputStream(input, valueReader, defaultFieldsReaderWriter);
+        try {
             object.readExternal(ois);
         } catch (ClassNotFoundException e) {
             throw new UnmarshalException("Cannot unmarshal due to a missing class", e);
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshallingContext.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshallingContext.java
index 5989190..30683f9 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshallingContext.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshallingContext.java
@@ -19,6 +19,9 @@ package org.apache.ignite.internal.network.serialization.marshal;
 
 import static java.util.Collections.unmodifiableSet;
 
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.NotActiveException;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -36,6 +39,11 @@ class MarshallingContext {
 
     private int nextRefId = 0;
 
+    private Object objectCurrentlyWrittenWithWriteObject;
+    private ClassDescriptor descriptorOfObjectCurrentlyWrittenWithWriteObject;
+
+    private UosObjectOutputStream objectOutputStream;
+
     public void addUsedDescriptor(ClassDescriptor descriptor) {
         usedDescriptors.add(descriptor);
     }
@@ -87,4 +95,42 @@ class MarshallingContext {
 
         return refId;
     }
+
+    public Object objectCurrentlyWrittenWithWriteObject() throws NotActiveException {
+        if (objectCurrentlyWrittenWithWriteObject == null) {
+            throw new NotActiveException("not in call to writeObject");
+        }
+
+        return objectCurrentlyWrittenWithWriteObject;
+    }
+
+    public ClassDescriptor descriptorOfObjectCurrentlyWrittenWithWriteObject() {
+        if (descriptorOfObjectCurrentlyWrittenWithWriteObject == null) {
+            throw new IllegalStateException("No object is currently being written");
+        }
+
+        return descriptorOfObjectCurrentlyWrittenWithWriteObject;
+    }
+
+    public void startWritingWithWriteObject(Object object, ClassDescriptor descriptor) {
+        objectCurrentlyWrittenWithWriteObject = object;
+        descriptorOfObjectCurrentlyWrittenWithWriteObject = descriptor;
+    }
+
+    public void endWritingWithWriteObject() {
+        objectCurrentlyWrittenWithWriteObject = null;
+        descriptorOfObjectCurrentlyWrittenWithWriteObject = null;
+    }
+
+    UosObjectOutputStream objectOutputStream(
+            DataOutputStream output,
+            TypedValueWriter valueWriter,
+            DefaultFieldsReaderWriter defaultFieldsReaderWriter
+    ) throws IOException {
+        if (objectOutputStream == null) {
+            objectOutputStream = new UosObjectOutputStream(output, valueWriter, defaultFieldsReaderWriter, this);
+        }
+
+        return objectOutputStream;
+    }
 }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ProtocolMarshalling.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ProtocolMarshalling.java
new file mode 100644
index 0000000..c4bc2f3
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ProtocolMarshalling.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Protocol-wide elements marshalling.
+ */
+class ProtocolMarshalling {
+    static void writeDescriptorOrCommandId(int id, DataOutput output) throws IOException {
+        output.writeInt(id);
+    }
+
+    static int readDescriptorOrCommandId(DataInput input) throws IOException {
+        return input.readInt();
+    }
+
+    static void writeObjectId(int id, DataOutput output) throws IOException {
+        output.writeInt(id);
+    }
+
+    static int readObjectId(DataInput input) throws IOException {
+        return input.readInt();
+    }
+
+
+    static void writeLength(int length, DataOutput output) throws IOException {
+        writeUnsignedInt(length, output);
+    }
+
+    private static void writeUnsignedInt(int value, DataOutput output) throws IOException {
+        if (value < 0) {
+            throw new IllegalArgumentException(value + " is negative" );
+        }
+
+        output.writeInt(value);
+    }
+
+    static int readLength(DataInput input) throws IOException {
+        return readUnsignedInt(input);
+    }
+
+    private static int readUnsignedInt(DataInput input) throws IOException {
+        int value = input.readInt();
+
+        if (value < 0) {
+            throw new IllegalStateException(value + " is negative");
+        }
+
+        return value;
+    }
+
+    private ProtocolMarshalling() {
+    }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/TypedValueWriter.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/TypedValueWriter.java
index 294bc4e..2edaa3c 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/TypedValueWriter.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/TypedValueWriter.java
@@ -17,15 +17,15 @@
 
 package org.apache.ignite.internal.network.serialization.marshal;
 
-import java.io.DataOutput;
+import java.io.DataOutputStream;
 import java.io.IOException;
 
 /**
- * Writes objects to a {@link DataOutput} taking their original (for example, declared) types into consideration.
+ * Writes objects to a {@link DataOutputStream} taking their original (for example, declared) types into consideration.
  */
 interface TypedValueWriter {
     /**
-     * Writes the given object to the {@link DataOutput}.
+     * Writes the given object to the {@link DataOutputStream}.
      *
      * @param object        object to write
      * @param declaredClass the original class of the object (i.e. {@code byte.class} for {@code byte})
@@ -34,6 +34,6 @@ interface TypedValueWriter {
      * @throws IOException      if an I/O problem occurs
      * @throws MarshalException if another problem occurs
      */
-    void write(Object object, Class<?> declaredClass, DataOutput output, MarshallingContext context)
+    void write(Object object, Class<?> declaredClass, DataOutputStream output, MarshallingContext context)
             throws IOException, MarshalException;
 }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshalException.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshalException.java
index 9e7b624..19dec95 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshalException.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshalException.java
@@ -23,6 +23,10 @@ import org.apache.ignite.lang.IgniteInternalCheckedException;
  * Thrown when unmarshalling fails.
  */
 public class UnmarshalException extends IgniteInternalCheckedException {
+    public UnmarshalException(String msg) {
+        super(msg);
+    }
+
     public UnmarshalException(String message, Throwable cause) {
         super(message, cause);
     }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshallingContext.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshallingContext.java
index 94d7325..9dd093d 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshallingContext.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshallingContext.java
@@ -18,6 +18,9 @@
 package org.apache.ignite.internal.network.serialization.marshal;
 
 import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.NotActiveException;
 import java.util.HashMap;
 import java.util.Map;
 import org.apache.ignite.internal.network.serialization.ClassDescriptor;
@@ -33,6 +36,11 @@ class UnmarshallingContext implements IdIndexedDescriptors {
 
     private final Map<Integer, Object> refsToObjects = new HashMap<>();
 
+    private Object objectCurrentlyReadWithReadObject;
+    private ClassDescriptor descriptorOfObjectCurrentlyReadWithReadObject;
+
+    private UosObjectInputStream objectInputStream;
+
     public UnmarshallingContext(ByteArrayInputStream source, IdIndexedDescriptors descriptors) {
         this.source = source;
         this.descriptors = descriptors;
@@ -66,4 +74,42 @@ class UnmarshallingContext implements IdIndexedDescriptors {
     public void resetSourceToMark() {
         source.reset();
     }
+
+    public Object objectCurrentlyReadWithReadObject() throws NotActiveException {
+        if (objectCurrentlyReadWithReadObject == null) {
+            throw new NotActiveException("not in call to readObject");
+        }
+
+        return objectCurrentlyReadWithReadObject;
+    }
+
+    public ClassDescriptor descriptorOfObjectCurrentlyReadWithReadObject() {
+        if (descriptorOfObjectCurrentlyReadWithReadObject == null) {
+            throw new IllegalStateException("No object is currently being read with readObject()");
+        }
+
+        return descriptorOfObjectCurrentlyReadWithReadObject;
+    }
+
+    public void startReadingWithReadObject(Object object, ClassDescriptor descriptor) {
+        objectCurrentlyReadWithReadObject = object;
+        descriptorOfObjectCurrentlyReadWithReadObject = descriptor;
+    }
+
+    public void endReadingWithReadObject() {
+        objectCurrentlyReadWithReadObject = null;
+        descriptorOfObjectCurrentlyReadWithReadObject = null;
+    }
+
+    UosObjectInputStream objectInputStream(
+            DataInputStream input,
+            ValueReader<Object> valueReader,
+            DefaultFieldsReaderWriter defaultFieldsReaderWriter
+    ) throws IOException {
+        if (objectInputStream == null) {
+            objectInputStream = new UosObjectInputStream(input, valueReader, defaultFieldsReaderWriter, this);
+        }
+
+        return objectInputStream;
+    }
 }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectInputStream.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectInputStream.java
new file mode 100644
index 0000000..853562e
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectInputStream.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+/**
+ *
+ */
+class UosObjectInputStream extends ObjectInputStream {
+    private final DataInputStream input;
+    private final ValueReader<Object> valueReader;
+    private final DefaultFieldsReaderWriter defaultFieldsReaderWriter;
+    private final UnmarshallingContext context;
+
+    UosObjectInputStream(DataInputStream input, ValueReader<Object> valueReader, DefaultFieldsReaderWriter defaultFieldsReaderWriter, UnmarshallingContext context) throws IOException {
+        this.input = input;
+        this.valueReader = valueReader;
+        this.defaultFieldsReaderWriter = defaultFieldsReaderWriter;
+        this.context = context;
+    }
+
+    @Override
+    public int read() throws IOException {
+        return input.read();
+    }
+
+    @SuppressWarnings("NullableProblems")
+    @Override
+    public int read(byte[] buf) throws IOException {
+        return input.read(buf);
+    }
+
+    @Override
+    public int read(byte[] buf, int off, int len) throws IOException {
+        return input.read(buf, off, len);
+    }
+
+    @Override
+    public byte readByte() throws IOException {
+        return input.readByte();
+    }
+
+    @Override
+    public short readShort() throws IOException {
+        return input.readShort();
+    }
+
+    @Override
+    public int readInt() throws IOException {
+        return input.readInt();
+    }
+
+    @Override
+    public long readLong() throws IOException {
+        return input.readLong();
+    }
+
+    @Override
+    public float readFloat() throws IOException {
+        return input.readFloat();
+    }
+
+    @Override
+    public double readDouble() throws IOException {
+        return input.readDouble();
+    }
+
+    @Override
+    public char readChar() throws IOException {
+        return input.readChar();
+    }
+
+    @Override
+    public boolean readBoolean() throws IOException {
+        return input.readBoolean();
+    }
+
+    @Override
+    public String readUTF() throws IOException {
+        return input.readUTF();
+    }
+
+    @Override
+    public int readUnsignedByte() throws IOException {
+        return input.readUnsignedByte();
+    }
+
+    @Override
+    public int readUnsignedShort() throws IOException {
+        return input.readUnsignedShort();
+    }
+
+    @Override
+    public void readFully(byte[] buf) throws IOException {
+        input.readFully(buf);
+    }
+
+    @Override
+    public void readFully(byte[] buf, int off, int len) throws IOException {
+        input.readFully(buf, off, len);
+    }
+
+    @Override
+    public String readLine() throws IOException {
+        return input.readLine();
+    }
+
+    @Override
+    protected Object readObjectOverride() throws IOException {
+        return doReadObject();
+    }
+
+    private Object doReadObject() throws IOException {
+        try {
+            return valueReader.read(input, context);
+        } catch (UnmarshalException e) {
+            // TODO: IGNITE-16165 -  pass exception correctly
+            throw new RuntimeException("Cannot read", e);
+        }
+    }
+
+    @Override
+    public Object readUnshared() throws IOException {
+        // TODO: IGNITE-16165 - implement 'unshared' logic?
+        return doReadObject();
+    }
+
+    @Override
+    public void defaultReadObject() throws IOException {
+        try {
+            defaultFieldsReaderWriter.defaultFillFieldsFrom(
+                    input,
+                    context.objectCurrentlyReadWithReadObject(),
+                    context.descriptorOfObjectCurrentlyReadWithReadObject(),
+                    context
+            );
+        } catch (UnmarshalException e) {
+            // TODO: IGNITE-16165 -  pass exception correctly
+            throw new RuntimeException("Cannot read", e);
+        }
+    }
+
+    @Override
+    public int available() throws IOException {
+        return input.available();
+    }
+
+    @Override
+    public int skipBytes(int len) throws IOException {
+        return input.skipBytes(len);
+    }
+
+    @Override
+    public void close() throws IOException {
+    }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectOutputStream.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectOutputStream.java
new file mode 100644
index 0000000..1ce3b28
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectOutputStream.java
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+import static org.apache.ignite.internal.network.serialization.marshal.ObjectClass.objectClass;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
+/**
+ *
+ */
+class UosObjectOutputStream extends ObjectOutputStream {
+    private final DataOutputStream output;
+    private final TypedValueWriter valueWriter;
+    private final DefaultFieldsReaderWriter defaultFieldsReaderWriter;
+    private final MarshallingContext context;
+
+    UosObjectOutputStream(
+            DataOutputStream output,
+            TypedValueWriter valueWriter,
+            DefaultFieldsReaderWriter defaultFieldsReaderWriter,
+            MarshallingContext context
+    ) throws IOException {
+        this.output = output;
+        this.valueWriter = valueWriter;
+        this.defaultFieldsReaderWriter = defaultFieldsReaderWriter;
+        this.context = context;
+    }
+
+    @Override
+    public void write(int val) throws IOException {
+        output.write(val);
+    }
+
+    @Override
+    public void write(byte[] buf) throws IOException {
+        output.write(buf);
+    }
+
+    @Override
+    public void write(byte[] buf, int off, int len) throws IOException {
+        output.write(buf, off, len);
+    }
+
+    @Override
+    public void writeByte(int val) throws IOException {
+        output.writeByte(val);
+    }
+
+    @Override
+    public void writeShort(int val) throws IOException {
+        output.writeShort(val);
+    }
+
+    @Override
+    public void writeInt(int val) throws IOException {
+        output.writeInt(val);
+    }
+
+    @Override
+    public void writeLong(long val) throws IOException {
+        output.writeLong(val);
+    }
+
+    @Override
+    public void writeFloat(float val) throws IOException {
+        output.writeFloat(val);
+    }
+
+    @Override
+    public void writeDouble(double val) throws IOException {
+        output.writeDouble(val);
+    }
+
+    @Override
+    public void writeChar(int val) throws IOException {
+        output.writeChar(val);
+    }
+
+    @Override
+    public void writeBoolean(boolean val) throws IOException {
+        output.writeBoolean(val);
+    }
+
+    @Override
+    public void writeBytes(String str) throws IOException {
+        output.writeBytes(str);
+    }
+
+    @Override
+    public void writeChars(String str) throws IOException {
+        output.writeChars(str);
+    }
+
+    @Override
+    public void writeUTF(String str) throws IOException {
+        output.writeUTF(str);
+    }
+
+    @Override
+    protected void writeObjectOverride(Object obj) throws IOException {
+        writeObject0(obj);
+    }
+
+    private void writeObject0(Object obj) throws IOException {
+        try {
+            valueWriter.write(obj, objectClass(obj), output, context);
+        } catch (MarshalException e) {
+            // TODO: IGNITE-16165 -  pass exception correctly
+            throw new RuntimeException("Cannot write", e);
+        }
+    }
+
+    @Override
+    public void writeUnshared(Object obj) throws IOException {
+        // TODO: IGNITE-16165 - implement 'unshared' logic?
+        writeObject0(obj);
+    }
+
+    @Override
+    public void defaultWriteObject() throws IOException {
+        try {
+            defaultFieldsReaderWriter.defaultWriteFields(
+                    context.objectCurrentlyWrittenWithWriteObject(),
+                    context.descriptorOfObjectCurrentlyWrittenWithWriteObject(),
+                    output,
+                    context
+            );
+        } catch (MarshalException e) {
+            // TODO: IGNITE-16165 -  pass exception correctly
+            throw new RuntimeException("Cannot write", e);
+        }
+    }
+
+    @Override
+    public void useProtocolVersion(int version) {
+        // no op
+    }
+
+    @Override
+    public void reset() throws IOException {
+        // TODO: IGNITE-16165 - erase information about references?
+    }
+
+    @Override
+    public void flush() throws IOException {
+        output.flush();
+    }
+
+    @Override
+    public void close() throws IOException {
+        flush();
+    }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueReader.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueReader.java
index 184f56f..1ab25a1 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueReader.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueReader.java
@@ -17,15 +17,15 @@
 
 package org.apache.ignite.internal.network.serialization.marshal;
 
-import java.io.DataInput;
+import java.io.DataInputStream;
 import java.io.IOException;
 
 /**
- * Knows how to read a value from a {@link DataInput}.
+ * Knows how to read a value from a {@link DataInputStream}.
  */
 interface ValueReader<T> {
     /**
-     * Reads the next value from a {@link DataInput}.
+     * Reads the next value from a {@link DataInputStream}.
      *
      * @param input     from where to read
      * @param context   unmarshalling context
@@ -33,5 +33,5 @@ interface ValueReader<T> {
      * @throws IOException          if an I/O problem occurs
      * @throws UnmarshalException   if another problem (like {@link ClassNotFoundException}) occurs
      */
-    T read(DataInput input, UnmarshallingContext context) throws IOException, UnmarshalException;
+    T read(DataInputStream input, UnmarshallingContext context) throws IOException, UnmarshalException;
 }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
index fe18d79..f7b2ede 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
@@ -17,15 +17,15 @@
 
 package org.apache.ignite.internal.network.serialization.marshal;
 
-import java.io.DataOutput;
+import java.io.DataOutputStream;
 import java.io.IOException;
 
 /**
- * Knows how to write a value to a {@link DataOutput}.
+ * Knows how to write a value to a {@link DataOutputStream}.
  */
 interface ValueWriter<T> {
     /**
-     * Writes the given value to a {@link DataOutput}.
+     * Writes the given value to a {@link DataOutputStream}.
      *
      * @param value     value to write
      * @param output    where to write to
@@ -33,5 +33,5 @@ interface ValueWriter<T> {
      * @throws IOException      if an I/O problem occurs
      * @throws MarshalException if another problem occurs
      */
-    void write(T value, DataOutput output, MarshallingContext context) throws IOException, MarshalException;
+    void write(T value, DataOutputStream output, MarshallingContext context) throws IOException, MarshalException;
 }
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryTest.java
index 332097e..73b651e 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryTest.java
@@ -21,7 +21,10 @@ import static org.apache.ignite.internal.network.serialization.SerializationType
 import static org.apache.ignite.internal.network.serialization.SerializationType.EXTERNALIZABLE;
 import static org.apache.ignite.internal.network.serialization.SerializationType.SERIALIZABLE;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -445,31 +448,94 @@ public class ClassDescriptorFactoryTest {
     }
 
     @Test
-    void shouldSortArbitraryObjectFieldsByClassHierarchyAndLexicographicallyByFieldName() {
-        ClassDescriptor descriptor = factory.create(ArbitraryWithFieldNameClashAndOrderPermutation.class);
+    void shouldOnlyConsiderDeclaredFields() {
+        ClassDescriptor descriptor = factory.create(Child.class);
+
+        assertThat(descriptor.fields(), hasSize(1));
 
         assertThat(descriptor.fields().get(0).clazz(), is(String.class));
-        assertThat(descriptor.fields().get(0).name(), is("value"));
+        assertThat(descriptor.fields().get(0).name(), is("childValue"));
+    }
+
+    @Test
+    void shouldSortArbitraryObjectFieldsLexicographicallyByFieldName() {
+        ClassDescriptor descriptor = factory.create(ClassWithFieldOrderPermutation.class);
+
+        assertThat(descriptor.fields().get(0).clazz(), is(int.class));
+        assertThat(descriptor.fields().get(0).name(), is("apple"));
 
         assertThat(descriptor.fields().get(1).clazz(), is(int.class));
-        assertThat(descriptor.fields().get(1).name(), is("apple"));
+        assertThat(descriptor.fields().get(1).name(), is("banana"));
 
         assertThat(descriptor.fields().get(2).clazz(), is(int.class));
-        assertThat(descriptor.fields().get(2).name(), is("banana"));
+        assertThat(descriptor.fields().get(2).name(), is("value"));
+    }
+
+    @SuppressWarnings("ConstantConditions")
+    @Test
+    void detectsSuperClass() {
+        ClassDescriptor descriptor = factory.create(Child.class);
 
-        assertThat(descriptor.fields().get(3).clazz(), is(int.class));
-        assertThat(descriptor.fields().get(3).name(), is("value"));
+        assertThat(descriptor.superClassDescriptor().clazz(), is(Parent.class));
+    }
+
+    @Test
+    void detectsSuperClassAsNullIfTheSuperClassIsObject() {
+        ClassDescriptor descriptor = factory.create(ExtendsObject.class);
+
+        assertThat(descriptor.superClassDescriptor(), is(nullValue()));
+    }
+
+    @Test
+    void registersSuperClassDescriptorOnParsingSubClass() {
+        factory.create(Child.class);
+
+        assertDoesNotThrow(() -> context.getRequiredDescriptor(Parent.class));
+    }
+
+    @Test
+    void failsWhenTryingToParseSerializableWithWriteObjectButWithoutReadObject() {
+        Throwable ex = assertThrows(IllegalArgumentException.class, () -> factory.create(SerializableWithWriteObjectOnly.class));
+        assertThat(ex.getMessage(), is("Class must either have both writeObject() and readObject() methods or neither of them: "
+                + SerializableWithWriteObjectOnly.class.getName()));
+    }
+
+    @Test
+    void failsWhenTryingToParseSerializableWithReadObjectButWithoutWriteObject() {
+        Throwable ex = assertThrows(IllegalArgumentException.class, () -> factory.create(SerializableWithReadObjectOnly.class));
+        assertThat(ex.getMessage(), is("Class must either have both writeObject() and readObject() methods or neither of them: "
+                + SerializableWithReadObjectOnly.class.getName()));
     }
 
-    @SuppressWarnings("unused")
     private static class Parent {
+        @SuppressWarnings("unused")
         private String value;
     }
 
+    private static class Child extends Parent {
+        @SuppressWarnings("unused")
+        private String childValue;
+    }
+
     @SuppressWarnings("unused")
-    private static class ArbitraryWithFieldNameClashAndOrderPermutation extends Parent {
+    private static class ClassWithFieldOrderPermutation {
         private int value;
         private int banana;
         private int apple;
     }
+
+    private static class SerializableWithWriteObjectOnly implements Serializable {
+        private void writeObject(ObjectOutputStream oos) {
+            // no-op
+        }
+    }
+
+    private static class SerializableWithReadObjectOnly implements Serializable {
+        private void readObject(ObjectInputStream ois) {
+            // no-op
+        }
+    }
+
+    private static class ExtendsObject {
+    }
 }
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/DefaultDescriptorsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/DefaultDescriptorsTest.java
index 5b3bb16..1faf851 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/DefaultDescriptorsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/DefaultDescriptorsTest.java
@@ -61,7 +61,6 @@ import static org.apache.ignite.internal.network.serialization.BuiltinType.SINGL
 import static org.apache.ignite.internal.network.serialization.BuiltinType.STRING;
 import static org.apache.ignite.internal.network.serialization.BuiltinType.STRING_ARRAY;
 import static org.apache.ignite.internal.network.serialization.BuiltinType.UUID;
-import static org.apache.ignite.internal.network.serialization.BuiltinType.VOID;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import org.junit.jupiter.api.Test;
@@ -119,6 +118,5 @@ public class DefaultDescriptorsTest {
         assertEquals(41, LINKED_HASH_MAP.descriptorId());
         assertEquals(42, BIT_SET.descriptorId());
         assertEquals(43, NULL.descriptorId());
-        assertEquals(44, VOID.descriptorId());
     }
 }
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerCommonTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerCommonTest.java
new file mode 100644
index 0000000..78f36f3
--- /dev/null
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerCommonTest.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.Arrays;
+import org.apache.ignite.internal.network.serialization.ClassDescriptorFactory;
+import org.apache.ignite.internal.network.serialization.ClassDescriptorFactoryContext;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Common tests for {@link DefaultUserObjectMarshaller}.
+ */
+class DefaultUserObjectMarshallerCommonTest {
+    private final ClassDescriptorFactoryContext descriptorRegistry = new ClassDescriptorFactoryContext();
+    private final ClassDescriptorFactory descriptorFactory = new ClassDescriptorFactory(descriptorRegistry);
+
+    private final DefaultUserObjectMarshaller marshaller = new DefaultUserObjectMarshaller(descriptorRegistry, descriptorFactory);
+
+    @Test
+    void marshalsAndUnmarshalsBareObject() throws Exception {
+        MarshalledObject marshalled = marshaller.marshal(null);
+
+        byte[] tooManyBytes = Arrays.copyOf(marshalled.bytes(), marshalled.bytes().length + 1);
+
+        UnmarshalException ex = assertThrows(UnmarshalException.class, () -> marshaller.unmarshal(tooManyBytes, descriptorRegistry));
+        assertThat(ex.getMessage(), is("After reading a value, 1 excessive byte(s) still remain"));
+    }
+
+}
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
index 51201ab..1075041 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.network.serialization.marshal;
 import static java.util.Collections.singletonList;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasItems;
 import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.is;
@@ -103,6 +104,16 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
     }
 
     @Test
+    void usesDescriptorsOfAllAncestors() throws Exception {
+        MarshalledObject marshalled = marshaller.marshal(new SimpleChild("answer", 42));
+
+        assertThat(marshalled.usedDescriptors(), hasItems(
+                descriptorRegistry.getRequiredDescriptor(Parent.class),
+                descriptorRegistry.getRequiredDescriptor(SimpleChild.class)
+        ));
+    }
+
+    @Test
     void marshalsAndUnmarshalsClassInstancesHavingNestedArbitraryObjects() throws Exception {
         MarshalledObject marshalled = marshaller.marshal(new WithArbitraryClassField(new Simple(42)));
 
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java
index 937986f..80f47ed 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java
@@ -94,12 +94,12 @@ class DefaultUserObjectMarshallerWithBuiltinsTest {
     void marshalsBareObjectWithCorrectDescriptorIdInMarshalledRepresentation() throws Exception {
         MarshalledObject marshalled = marshaller.marshal(new Object());
 
-        assertThat(readType(marshalled), is(BuiltinType.BARE_OBJECT.descriptorId()));
+        assertThat(readDescriptorId(marshalled), is(BuiltinType.BARE_OBJECT.descriptorId()));
     }
 
-    private int readType(MarshalledObject marshalled) throws IOException {
+    private int readDescriptorId(MarshalledObject marshalled) throws IOException {
         try (var dis = new DataInputStream(new ByteArrayInputStream(marshalled.bytes()))) {
-            return dis.readInt();
+            return ProtocolMarshalling.readDescriptorOrCommandId(dis);
         }
     }
 
@@ -129,8 +129,7 @@ class DefaultUserObjectMarshallerWithBuiltinsTest {
         Object unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptorRegistry);
 
         assertThat(unmarshalled, is(equalTo(typeValue.value)));
-        if (typeValue.builtinType != BuiltinType.VOID && typeValue.builtinType != BuiltinType.NULL
-                && typeValue.value.getClass().isArray()) {
+        if (typeValue.builtinType != BuiltinType.NULL && typeValue.value.getClass().isArray()) {
             assertThat(unmarshalled, is(notNullValue()));
             assertThat(unmarshalled.getClass().getComponentType(), is(typeValue.value.getClass().getComponentType()));
         }
@@ -200,8 +199,7 @@ class DefaultUserObjectMarshallerWithBuiltinsTest {
                         BuiltinType.ENUM_ARRAY
                 ),
                 builtInTypeValue(BitSet.valueOf(new long[]{42, 43}), BitSet.class, BuiltinType.BIT_SET),
-                builtInTypeValue(null, Null.class, BuiltinType.NULL),
-                builtInTypeValue(null, Void.class, BuiltinType.VOID)
+                builtInTypeValue(null, Null.class, BuiltinType.NULL)
         ).map(Arguments::of);
     }
 
@@ -253,7 +251,7 @@ class DefaultUserObjectMarshallerWithBuiltinsTest {
     void marshalsBuiltInTypesWithCorrectDescriptorIdsInMarshalledRepresentation(BuiltInTypeValue typeValue) throws Exception {
         MarshalledObject marshalled = marshaller.marshal(typeValue.value, typeValue.valueClass);
 
-        assertThat(readType(marshalled), is(equalTo(typeValue.builtinType.descriptorId())));
+        assertThat(readDescriptorId(marshalled), is(equalTo(typeValue.builtinType.descriptorId())));
     }
 
     static Stream<Arguments> builtInTypes() {
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithExternalizableTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithExternalizableTest.java
index 8eb0b46..88c24eb 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithExternalizableTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithExternalizableTest.java
@@ -17,16 +17,26 @@
 
 package org.apache.ignite.internal.network.serialization.marshal;
 
+import static org.apache.ignite.internal.network.serialization.marshal.Throwables.causalChain;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasProperty;
+import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
 import java.io.Externalizable;
 import java.io.IOException;
 import java.io.ObjectInput;
+import java.io.ObjectInputStream;
 import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
 import java.util.Set;
 import org.apache.ignite.internal.network.serialization.ClassDescriptor;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorFactory;
@@ -56,13 +66,16 @@ class DefaultUserObjectMarshallerWithExternalizableTest {
 
     @Test
     void marshalsAndUnmarshalsExternalizable() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(new SimpleExternalizable(42));
-
-        SimpleExternalizable unmarshalled = unmarshalNonNull(marshalled);
+        SimpleExternalizable unmarshalled = marshalAndUnmarshalNonNull(new SimpleExternalizable(42));
 
         assertThat(unmarshalled.intValue, is(42));
     }
 
+    private <T> T marshalAndUnmarshalNonNull(Object object) throws MarshalException, UnmarshalException {
+        MarshalledObject marshalled = marshaller.marshal(object);
+        return unmarshalNonNull(marshalled);
+    }
+
     private <T> T unmarshalNonNull(MarshalledObject marshalled) throws UnmarshalException {
         T unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptorRegistry);
 
@@ -73,27 +86,21 @@ class DefaultUserObjectMarshallerWithExternalizableTest {
 
     @Test
     void appliesWriteReplaceOnExternalizable() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(new ExternalizableWithWriteReplace(42));
-
-        SimpleExternalizable unmarshalled = unmarshalNonNull(marshalled);
+        SimpleExternalizable unmarshalled = marshalAndUnmarshalNonNull(new ExternalizableWithWriteReplace(42));
 
         assertThat(unmarshalled.intValue, is(equalTo(42 + WRITE_REPLACE_INCREMENT)));
     }
 
     @Test
     void appliesReadResolveOnExternalizable() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(new ExternalizableWithReadResolve(42));
-
-        SimpleExternalizable unmarshalled = unmarshalNonNull(marshalled);
+        SimpleExternalizable unmarshalled = marshalAndUnmarshalNonNull(new ExternalizableWithReadResolve(42));
 
         assertThat(unmarshalled.intValue, is(equalTo(42 + READ_RESOLVE_INCREMENT)));
     }
 
     @Test
     void appliesBothWriteReplaceAndReadResolveOnExternalizable() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(new ExternalizableWithWriteReplaceReadResolve(42));
-
-        SimpleExternalizable unmarshalled = unmarshalNonNull(marshalled);
+        SimpleExternalizable unmarshalled = marshalAndUnmarshalNonNull(new ExternalizableWithWriteReplaceReadResolve(42));
 
         assertThat(unmarshalled.intValue, is(equalTo(42 + WRITE_REPLACE_INCREMENT + READ_RESOLVE_INCREMENT)));
     }
@@ -132,6 +139,99 @@ class DefaultUserObjectMarshallerWithExternalizableTest {
         assertThat(unmarshalled, is(nullValue()));
     }
 
+    @Test
+    void appliesWriteReplaceOnExternalizableRecursively() throws Exception {
+        Object result = marshalAndUnmarshalNonNull(new ExternalizableWithWriteReplaceChain1(0));
+
+        assertThat(result, is(instanceOf(Integer.class)));
+        assertThat(result, is(3));
+    }
+
+    @Test
+    void stopsApplyingWriteReplaceOnExternalizableWhenReplacementIsInstanceOfSameClass() throws Exception {
+        ExternalizableWithWriteReplaceWithSameClass result = marshalAndUnmarshalNonNull(new ExternalizableWithWriteReplaceWithSameClass(0));
+
+        assertThat(result.intValue, is(1));
+    }
+
+    @Test
+    void causesInfiniteRecursionOnExternalizableWithIndirectWriteReplaceCycle() {
+        assertThrows(StackOverflowError.class, ()  -> marshalAndUnmarshalNonNull(new ExternalizableWithWriteReplaceCycle1(0)));
+    }
+
+    /**
+     * Java Serialization applies writeReplace() repeatedly, but it only applies readResolve() once.
+     * So we are emulating this behavior.
+     *
+     * @throws Exception if something goes wrong
+     */
+    @Test
+    void onlyAppliesFirstReadResolveOnExternalizable() throws Exception {
+        Object result = marshalAndUnmarshalNonNull(new ExternalizableWithReadResolveChain1(0));
+
+        assertThat(result, is(instanceOf(ExternalizableWithReadResolveChain2.class)));
+    }
+
+    @Test
+    void defaultWriteObjectShouldFailInsideWriteExternal() {
+        MarshalException ex = assertThrows(
+                MarshalException.class,
+                () -> marshaller.marshal(new ExternalizableWithDefaultWriteObjectCallInWriteObjectMethod())
+        );
+        assertThat(causalChain(ex), hasItem(hasProperty("message", equalTo("not in call to writeObject"))));
+    }
+
+    @Test
+    void defaultReadObjectShouldFailInsideReadExternal() {
+        UnmarshalException ex = assertThrows(
+                UnmarshalException.class,
+                () -> marshalAndUnmarshalNonNull(new ExternalizableWithDefaultReadObjectCallInReadObjectMethod())
+        );
+        assertThat(causalChain(ex), hasItem(hasProperty("message", equalTo("not in call to readObject"))));
+    }
+
+    @Test
+    void defaultWriteObjectShouldFailInsideWriteExternalInsideWriteObject() {
+        var payload = new ExternalizableWithDefaultWriteObjectCallInWriteObjectMethod();
+        var object = new SerializableWithDefaultReadWriteObjectCallInReadWriteOverride(payload);
+
+        MarshalException ex = assertThrows(MarshalException.class, () -> marshaller.marshal(object));
+        assertThat(causalChain(ex), hasItem(hasProperty("message", equalTo("not in call to writeObject"))));
+    }
+
+    @Test
+    void defaultReadObjectShouldFailInsideReadExternalInsideReadObject() {
+        var payload = new ExternalizableWithDefaultReadObjectCallInReadObjectMethod();
+        var object = new SerializableWithDefaultReadWriteObjectCallInReadWriteOverride(payload);
+
+        UnmarshalException ex = assertThrows(UnmarshalException.class, () -> marshalAndUnmarshalNonNull(object));
+        assertThat(causalChain(ex), hasItem(hasProperty("message", equalTo("not in call to readObject"))));
+    }
+
+    @Test
+    void writingObjectInsideWriteExternalMarshalsTheObjectInOurFormat() throws Exception {
+        MarshalledObject marshalled = marshaller.marshal(new ExternalizableWritingAndReadingObject(new IntHolder(42)));
+
+        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(marshalled.bytes()));
+        ProtocolMarshalling.readDescriptorOrCommandId(dis);
+        ProtocolMarshalling.readObjectId(dis);
+
+        byte[] externalBytes = dis.readAllBytes();
+
+        IntHolder nested = marshaller.unmarshal(externalBytes, descriptorRegistry);
+        assertThat(nested, is(notNullValue()));
+        assertThat(nested.value, is(42));
+    }
+
+    @Test
+    void marshalsAndUnmarshalsExternalizableWritingReadingObjectInsideWriteReadExternal() throws Exception {
+        ExternalizableWritingAndReadingObject object = new ExternalizableWritingAndReadingObject(new IntHolder(42));
+
+        ExternalizableWritingAndReadingObject unmarshalled = marshalAndUnmarshalNonNull(object);
+
+        assertThat(unmarshalled.intHolder.value, is(42));
+    }
+
     /**
      * An {@link Externalizable} that does not have {@code writeReplace()}/{@code readResolve()} methods.
      */
@@ -145,11 +245,13 @@ class DefaultUserObjectMarshallerWithExternalizableTest {
             this.intValue = intValue;
         }
 
+        /** {@inheritDoc} */
         @Override
         public void writeExternal(ObjectOutput out) throws IOException {
             out.writeInt(-intValue);
         }
 
+        /** {@inheritDoc} */
         @Override
         public void readExternal(ObjectInput in) throws IOException {
             intValue = -in.readInt();
@@ -178,7 +280,7 @@ class DefaultUserObjectMarshallerWithExternalizableTest {
         }
 
         private Object readResolve() {
-            return new ExternalizableWithWriteReplaceReadResolve(intValue + 1_000);
+            return new ExternalizableWithReadResolve(intValue + 1_000);
         }
     }
 
@@ -213,11 +315,13 @@ class DefaultUserObjectMarshallerWithExternalizableTest {
             return new SimpleExternalizable(intValue);
         }
 
+        /** {@inheritDoc} */
         @Override
         public void writeExternal(ObjectOutput out) throws IOException {
             out.writeInt(intValue);
         }
 
+        /** {@inheritDoc} */
         @Override
         public void readExternal(ObjectInput in) throws IOException {
             intValue = in.readInt();
@@ -249,4 +353,177 @@ class DefaultUserObjectMarshallerWithExternalizableTest {
             return null;
         }
     }
+
+    private static class ExternalizableWithWriteReplaceChain1 extends SimpleExternalizable {
+        public ExternalizableWithWriteReplaceChain1() {
+        }
+
+        public ExternalizableWithWriteReplaceChain1(int value) {
+            super(value);
+        }
+
+        private Object writeReplace() {
+            return new ExternalizableWithWriteReplaceChain2(intValue + 1);
+        }
+    }
+
+    private static class ExternalizableWithWriteReplaceChain2 extends SimpleExternalizable {
+        public ExternalizableWithWriteReplaceChain2() {
+        }
+
+        public ExternalizableWithWriteReplaceChain2(int value) {
+            super(value);
+        }
+
+        private Object writeReplace() {
+            return intValue + 2;
+        }
+    }
+
+    private static class ExternalizableWithWriteReplaceWithSameClass extends SimpleExternalizable {
+        public ExternalizableWithWriteReplaceWithSameClass() {
+        }
+
+        public ExternalizableWithWriteReplaceWithSameClass(int value) {
+            super(value);
+        }
+
+        private Object writeReplace() {
+            return new ExternalizableWithWriteReplaceWithSameClass(intValue + 1);
+        }
+    }
+
+    private static class ExternalizableWithWriteReplaceCycle1 extends SimpleExternalizable {
+        public ExternalizableWithWriteReplaceCycle1() {
+        }
+
+        public ExternalizableWithWriteReplaceCycle1(int intValue) {
+            super(intValue);
+        }
+
+        private Object writeReplace() {
+            return new ExternalizableWithWriteReplaceCycle2(intValue);
+        }
+    }
+
+    private static class ExternalizableWithWriteReplaceCycle2 extends SimpleExternalizable {
+        public ExternalizableWithWriteReplaceCycle2() {
+        }
+
+        public ExternalizableWithWriteReplaceCycle2(int intValue) {
+            super(intValue);
+        }
+
+        private Object writeReplace() {
+            return new ExternalizableWithWriteReplaceCycle1(intValue);
+        }
+    }
+
+    private static class ExternalizableWithReadResolveChain1 extends SimpleExternalizable {
+        public ExternalizableWithReadResolveChain1() {
+        }
+
+        public ExternalizableWithReadResolveChain1(int value) {
+            super(value);
+        }
+
+        private Object readResolve() {
+            return new ExternalizableWithReadResolveChain2(intValue + 1);
+        }
+    }
+
+    private static class ExternalizableWithReadResolveChain2 extends SimpleExternalizable {
+        public ExternalizableWithReadResolveChain2() {
+        }
+
+        public ExternalizableWithReadResolveChain2(int value) {
+            super(value);
+        }
+
+        private Object readResolve() {
+            return intValue + 2;
+        }
+    }
+
+    private static class ExternalizableWithDefaultWriteObjectCallInWriteObjectMethod implements Externalizable {
+        public ExternalizableWithDefaultWriteObjectCallInWriteObjectMethod() {
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void writeExternal(ObjectOutput out) throws IOException {
+            ObjectOutputStream stream = (ObjectOutputStream) out;
+            stream.defaultWriteObject();
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void readExternal(ObjectInput in) {
+            // no op
+        }
+    }
+
+    private static class ExternalizableWithDefaultReadObjectCallInReadObjectMethod implements Externalizable {
+        public ExternalizableWithDefaultReadObjectCallInReadObjectMethod() {
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void writeExternal(ObjectOutput out) {
+            // no op
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+            ObjectInputStream stream = (ObjectInputStream) in;
+            stream.defaultReadObject();
+        }
+    }
+
+    private static class SerializableWithDefaultReadWriteObjectCallInReadWriteOverride implements Serializable {
+        @SuppressWarnings({"FieldCanBeLocal", "unused"})
+        private final Object object;
+
+        public SerializableWithDefaultReadWriteObjectCallInReadWriteOverride(Object object) {
+            this.object = object;
+        }
+
+        private void writeObject(ObjectOutputStream stream) throws IOException {
+            stream.defaultWriteObject();
+        }
+
+        private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+            stream.defaultReadObject();
+        }
+    }
+
+    private static class ExternalizableWritingAndReadingObject implements Externalizable {
+        private IntHolder intHolder;
+
+        public ExternalizableWritingAndReadingObject() {
+        }
+
+        public ExternalizableWritingAndReadingObject(IntHolder intHolder) {
+            this.intHolder = intHolder;
+        }
+
+        @Override
+        public void writeExternal(ObjectOutput out) throws IOException {
+            out.writeObject(intHolder);
+        }
+
+        @Override
+        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+            intHolder = (IntHolder) in.readObject();
+        }
+    }
+
+    private static class IntHolder {
+        private final int value;
+
+        private IntHolder(int value) {
+            this.value = value;
+        }
+    }
 }
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java
new file mode 100644
index 0000000..97e86e7
--- /dev/null
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java
@@ -0,0 +1,809 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThan;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import org.apache.ignite.internal.network.serialization.BuiltinType;
+import org.apache.ignite.internal.network.serialization.ClassDescriptor;
+import org.apache.ignite.internal.network.serialization.ClassDescriptorFactory;
+import org.apache.ignite.internal.network.serialization.ClassDescriptorFactoryContext;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Tests for how {@link DefaultUserObjectMarshaller} handles {@link java.io.Serializable}s (but not {@link Externalizable}s).
+ */
+class DefaultUserObjectMarshallerWithSerializableTest {
+    private final ClassDescriptorFactoryContext descriptorRegistry = new ClassDescriptorFactoryContext();
+    private final ClassDescriptorFactory descriptorFactory = new ClassDescriptorFactory(descriptorRegistry);
+
+    private final DefaultUserObjectMarshaller marshaller = new DefaultUserObjectMarshaller(descriptorRegistry, descriptorFactory);
+
+    private static final int WRITE_REPLACE_INCREMENT = 1_000_000;
+    private static final int READ_RESOLVE_INCREMENT = 1_000;
+
+    private static final int WRITE_OBJECT_INCREMENT = 10;
+    private static final int READ_OBJECT_INCREMENT = 100;
+
+    private static final int CHILD_WRITE_OBJECT_INCREMENT = 3;
+    private static final int CHILD_READ_OBJECT_INCREMENT = 6;
+
+    /** This is static so that writeObject()/readObject() can easily find it. */
+    private static ReaderAndWriter<?> readerAndWriter;
+
+    /** Static access to the marshaller (for using in parameterized tests) */
+    private static UserObjectMarshaller staticMarshaller;
+    /** Static access to the registry (for using in parameterized tests) */
+    private static ClassDescriptorFactoryContext staticDescriptorRegistry;
+
+    @BeforeEach
+    void initStatics() {
+        staticMarshaller = marshaller;
+        staticDescriptorRegistry = descriptorRegistry;
+    }
+
+    @Test
+    void marshalsAndUnmarshalsSerializable() throws Exception {
+        SimpleSerializable unmarshalled = marshalAndUnmarshalNonNull(new SimpleSerializable(42));
+
+        assertThat(unmarshalled.intValue, is(42));
+    }
+
+    private <T> T marshalAndUnmarshalNonNull(Object object) throws MarshalException, UnmarshalException {
+        MarshalledObject marshalled = marshaller.marshal(object);
+        return unmarshalNonNull(marshalled);
+    }
+
+    private <T> T unmarshalNonNull(MarshalledObject marshalled) throws UnmarshalException {
+        T unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptorRegistry);
+
+        assertThat(unmarshalled, is(notNullValue()));
+
+        return unmarshalled;
+    }
+
+    @Test
+    void appliesWriteReplaceOnSerializable() throws Exception {
+        SimpleSerializable unmarshalled = marshalAndUnmarshalNonNull(new SerializableWithWriteReplace(42));
+
+        assertThat(unmarshalled.intValue, is(equalTo(42 + WRITE_REPLACE_INCREMENT)));
+    }
+
+    @Test
+    void appliesReadResolveOnSerializable() throws Exception {
+        SimpleSerializable unmarshalled = marshalAndUnmarshalNonNull(new SerializableWithReadResolve(42));
+
+        assertThat(unmarshalled.intValue, is(equalTo(42 + READ_RESOLVE_INCREMENT)));
+    }
+
+    @Test
+    void appliesBothWriteReplaceAndReadResolveOnSerializable() throws Exception {
+        SimpleSerializable unmarshalled = marshalAndUnmarshalNonNull(new SerializableWithWriteReplaceReadResolve(42));
+
+        assertThat(unmarshalled.intValue, is(equalTo(42 + WRITE_REPLACE_INCREMENT + READ_RESOLVE_INCREMENT)));
+    }
+
+    @Test
+    void usesDescriptorOfReplacementWhenSerializableIsReplacedWithSomethingDifferent() throws Exception {
+        MarshalledObject marshalled = marshaller.marshal(new SerializableWithReplaceWithSimple(42));
+
+        ClassDescriptor originalDescriptor = descriptorRegistry.getRequiredDescriptor(SerializableWithReplaceWithSimple.class);
+        assertThat(marshalled.usedDescriptors(), not(hasItem(originalDescriptor)));
+
+        ClassDescriptor replacementDescriptor = descriptorRegistry.getRequiredDescriptor(SimpleSerializable.class);
+        assertThat(marshalled.usedDescriptors(), hasItem(replacementDescriptor));
+    }
+
+    @Test
+    void marshalsSerializableWithReplaceWithNull() throws Exception {
+        MarshalledObject marshalled = marshaller.marshal(new SerializableWithReplaceWithNull(42));
+
+        SimpleSerializable unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptorRegistry);
+
+        assertThat(unmarshalled, is(nullValue()));
+    }
+
+    @Test
+    void onlyUsesDescriptorOfNullWhenSerializableIsReplacedWithNull() throws Exception {
+        MarshalledObject marshalled = marshaller.marshal(new SerializableWithReplaceWithNull(42));
+
+        ClassDescriptor replacementDescriptor = descriptorRegistry.getNullDescriptor();
+        assertThat(marshalled.usedDescriptors(), equalTo(Set.of(replacementDescriptor)));
+    }
+
+    @Test
+    void unmarshalsSerializableWithResolveWithNull() throws Exception {
+        MarshalledObject marshalled = marshaller.marshal(new SerializableWithResolveWithNull(42));
+
+        SimpleSerializable unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptorRegistry);
+
+        assertThat(unmarshalled, is(nullValue()));
+    }
+
+    @Test
+    void appliesWriteReplaceOnExternalizableRecursively() throws Exception {
+        Object result = marshalAndUnmarshalNonNull(new SerializableWithWriteReplaceChain1(0));
+
+        assertThat(result, is(instanceOf(Integer.class)));
+        assertThat(result, is(3));
+    }
+
+    @Test
+    void stopsApplyingWriteReplaceOnExternalizableWhenReplacementIsInstanceOfSameClass() throws Exception {
+        SerializableWithWriteReplaceWithSameClass result = marshalAndUnmarshalNonNull(new SerializableWithWriteReplaceWithSameClass(0));
+
+        assertThat(result.intValue, is(1));
+    }
+
+    @Test
+    void causesInfiniteRecursionOnExternalizableWithIndirectWriteReplaceCycle() {
+        assertThrows(StackOverflowError.class, ()  -> marshalAndUnmarshalNonNull(new SerializableWithWriteReplaceCycle1(0)));
+    }
+
+    /**
+     * Java Serialization applies writeReplace() repeatedly, but it only applies readResolve() once.
+     * So we are emulating this behavior.
+     *
+     * @throws Exception if something goes wrong
+     */
+    @Test
+    void onlyAppliesFirstReadResolveOnExternalizable() throws Exception {
+        Object result = marshalAndUnmarshalNonNull(new SerializableWithReadResolveChain1(0));
+
+        assertThat(result, is(instanceOf(SerializableWithReadResolveChain2.class)));
+    }
+
+    @Test
+    void usesWriteObjectAndReadObject() throws Exception {
+        SerializableWithWriteReadOverride result = marshalAndUnmarshalNonNull(new SerializableWithWriteReadOverride(42));
+
+        assertThat(result.value, is(42 + WRITE_OBJECT_INCREMENT + READ_OBJECT_INCREMENT));
+    }
+
+    @Test
+    void doesNotWriteDefaultFieldValuesDataIfWriteReadOverrideIsPresent() throws Exception {
+        SerializableWithNoOpWriteReadOverride result = marshalAndUnmarshalNonNull(new SerializableWithNoOpWriteReadOverride(42));
+
+        assertThat(result.value, is(0));
+    }
+
+    @ParameterizedTest
+    @MethodSource("readWriteSpecs")
+    <T> void objectOutputStreamFromWriteObjectWritesUsingOurFormat(ReadWriteSpec<T> spec) throws Exception {
+        readerAndWriter = new ReaderAndWriter<>(spec.writer, spec.reader);
+
+        WithCustomizableOverride<T> original = new WithCustomizableOverride<>();
+        MarshalledObject marshalled = marshaller.marshal(original);
+
+        byte[] overrideBytes = readOverrideBytes(marshalled);
+        T overrideValue = spec.parseOverrideValue(overrideBytes);
+
+        spec.assertUnmarshalledValue(overrideValue);
+    }
+
+    @ParameterizedTest
+    @MethodSource("readWriteSpecs")
+    <T> void supportsReadsAndWritesInWriteObjectAndReadObject(ReadWriteSpec<T> spec) throws Exception {
+        readerAndWriter = new ReaderAndWriter<>(spec.writer, spec.reader);
+
+        WithCustomizableOverride<T> original = new WithCustomizableOverride<>();
+        WithCustomizableOverride<T> unmarshalled = marshalAndUnmarshalNonNull(original);
+
+        spec.assertUnmarshalledValue(unmarshalled.value);
+    }
+
+    private byte[] readOverrideBytes(MarshalledObject marshalled) throws IOException {
+        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(marshalled.bytes()));
+
+        ProtocolMarshalling.readDescriptorOrCommandId(dis);
+        ProtocolMarshalling.readObjectId(dis);
+
+        return dis.readAllBytes();
+    }
+
+    private void assertThatDrained(DataInputStream dis) throws IOException {
+        assertThat(dis.read(), is(lessThan(0)));
+    }
+
+    private static Stream<Arguments> readWriteSpecs() {
+        return Stream.of(
+                // the following test perfect pairs like writeByte()/readByte()
+                new ReadWriteSpec<>("data byte", oos -> oos.writeByte(42), DataInput::readByte, (byte) 42),
+                new ReadWriteSpec<>("short", oos -> oos.writeShort(42), DataInput::readShort, (short) 42),
+                new ReadWriteSpec<>("int", oos -> oos.writeInt(42), DataInput::readInt, 42),
+                new ReadWriteSpec<>("long", oos -> oos.writeLong(42), DataInput::readLong, 42L),
+                new ReadWriteSpec<>("float", oos -> oos.writeFloat(42.0f), DataInput::readFloat, 42.0f),
+                new ReadWriteSpec<>("double", oos -> oos.writeDouble(42.0), DataInput::readDouble, 42.0),
+                new ReadWriteSpec<>("char", oos -> oos.writeChar('a'), DataInput::readChar, 'a'),
+                new ReadWriteSpec<>("boolean", oos -> oos.writeBoolean(true), DataInput::readBoolean, true),
+                new ReadWriteSpec<>("stream byte", oos -> oos.write(42), ObjectInputStream::read, DataInputStream::read, 42),
+                new ReadWriteSpec<>("byte array", oos -> oos.write(new byte[]{42, 43}), is -> readBytes(is, 2), is -> readBytes(is, 2), new byte[]{42, 43}),
+                new ReadWriteSpec<>("byte array range", oos -> oos.write(new byte[]{42, 43}, 0, 2), is -> readRange(is, 2), is -> readRange(is, 2), new byte[]{42, 43}),
+                new ReadWriteSpec<>("UTF", oos -> oos.writeUTF("Привет"), DataInput::readUTF, "Привет"),
+                new ReadWriteSpec<>("object", oos -> oos.writeObject(new SimpleNonSerializable(42)), ObjectInputStream::readObject, DefaultUserObjectMarshallerWithSerializableTest::consumeAndUnmarshal, new SimpleNonSerializable(42)),
+                new ReadWriteSpec<>("unshared", oos -> oos.writeUnshared(new SimpleNonSerializable(42)), ObjectInputStream::readUnshared, DefaultUserObjectMarshallerWithSerializableTest::consumeAndUnmarshal, new SimpleNonSerializable(42)),
+                // the following test writing methods only (readers are just to help testing them)
+                new ReadWriteSpec<>("writeBytes", oos -> oos.writeBytes("abc"), input -> readBytesFully(input, 3), "abc".getBytes()),
+                new ReadWriteSpec<>("writeChars", oos -> oos.writeChars("a"), DataInput::readChar, 'a'),
+                // the following test reading methods only (writers are just to help testing them)
+                new ReadWriteSpec<>("readFully", oos -> oos.write(new byte[]{42, 43}), input -> readBytesFully(input, 2), new byte[]{42, 43}),
+                new ReadWriteSpec<>("readFully range", oos -> oos.write(new byte[]{42, 43}), input -> readBytesRangeFully(input, 2), new byte[]{42, 43}),
+                new ReadWriteSpec<>("readUnsignedByte", oos -> oos.writeByte(42), DataInput::readUnsignedByte, 42),
+                new ReadWriteSpec<>("readUnsignedShort", oos -> oos.writeShort(42), DataInput::readUnsignedShort, 42),
+                new ReadWriteSpec<>("readAllBytes", oos -> oos.write(new byte[]{42, 43}), InputStream::readAllBytes, InputStream::readAllBytes, new byte[]{42, 43}),
+                new ReadWriteSpec<>("readNBytes", oos -> oos.write(new byte[]{42, 43}), ois -> ois.readNBytes(2), dis -> readBytesFully(dis, 2), new byte[]{42, 43}),
+                new ReadWriteSpec<>("readNBytes range", oos -> oos.write(new byte[]{42, 43}), ois -> readNBytesRange(ois, 2), dis -> readNBytesRange(dis, 2), new byte[]{42, 43})
+        ).map(Arguments::of);
+    }
+
+    // TODO: IGNITE-16240 - implement putFields()/writeFields()
+    // TODO: IGNITE-16240 - implement readFields()
+
+    private static byte[] readBytes(InputStream is, int count) throws IOException {
+        byte[] bytes = new byte[count];
+        int read = is.read(bytes);
+        assertThat(read, is(count));
+        return bytes;
+    }
+
+    private static byte[] readRange(InputStream is, int count) throws IOException {
+        byte[] bytes = new byte[count];
+        int read = is.read(bytes, 0, count);
+        assertThat(read, is(count));
+        return bytes;
+    }
+
+    private static byte[] readBytesFully(DataInput is, int count) throws IOException {
+        byte[] bytes = new byte[count];
+        is.readFully(bytes);
+        return bytes;
+    }
+
+    private static byte[] readBytesRangeFully(DataInput is, int count) throws IOException {
+        byte[] bytes = new byte[count];
+        is.readFully(bytes, 0, count);
+        return bytes;
+    }
+
+    private static byte[] readNBytesRange(InputStream is, int count) throws IOException {
+        byte[] bytes = new byte[count];
+        is.readNBytes(bytes, 0, count);
+        return bytes;
+    }
+
+    private static <T> T consumeAndUnmarshal(DataInputStream stream) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        stream.transferTo(baos);
+
+        try {
+            return staticMarshaller.unmarshal(baos.toByteArray(), staticDescriptorRegistry);
+        } catch (UnmarshalException e) {
+            throw new RuntimeException("Unmarshalling failed", e);
+        }
+    }
+
+    @Test
+    void supportsFlushInsideWriteObject() {
+        readerAndWriter = new ReaderAndWriter<>(ObjectOutputStream::flush, ois -> null);
+
+        WithCustomizableOverride<?> original = new WithCustomizableOverride<>();
+
+        assertDoesNotThrow(() -> marshalAndUnmarshalNonNull(original));
+    }
+
+    @Test
+    void supportsResetInsideWriteObject() {
+        readerAndWriter = new ReaderAndWriter<>(ObjectOutputStream::reset, ois -> null);
+
+        WithCustomizableOverride<?> original = new WithCustomizableOverride<>();
+
+        assertDoesNotThrow(() -> marshalAndUnmarshalNonNull(original));
+    }
+
+    @Test
+    void supportsUseProtocolVersionInsideWriteObject() {
+        readerAndWriter = new ReaderAndWriter<>(oos -> oos.useProtocolVersion(1), ois -> null);
+
+        WithCustomizableOverride<?> original = new WithCustomizableOverride<>();
+
+        assertDoesNotThrow(() -> marshalAndUnmarshalNonNull(original));
+    }
+
+    @Test
+    void supportsSkipInsideReadObject() throws Exception {
+        readerAndWriter = new ReaderAndWriter<>(oos -> oos.write(new byte[]{42, 43}), ois -> {
+            assertThat(ois.skip(1), is(1L));
+            return ois.readByte();
+        });
+
+        WithCustomizableOverride<?> original = new WithCustomizableOverride<>();
+
+        WithCustomizableOverride<Byte> unmarshalled = marshalAndUnmarshalNonNull(original);
+        assertThat(unmarshalled.value, is((byte) 43));
+    }
+
+    @Test
+    void supportsSkipBytesInsideReadObject() throws Exception {
+        readerAndWriter = new ReaderAndWriter<>(oos -> oos.write(new byte[]{42, 43}), ois -> {
+            assertThat(ois.skipBytes(1), is(1));
+            return ois.readByte();
+        });
+
+        WithCustomizableOverride<?> original = new WithCustomizableOverride<>();
+
+        WithCustomizableOverride<Byte> unmarshalled = marshalAndUnmarshalNonNull(original);
+        assertThat(unmarshalled.value, is((byte) 43));
+    }
+
+    @Test
+    void supportsAvailableInsideReadObject() {
+        readerAndWriter = new ReaderAndWriter<>(oos -> {}, ObjectInputStream::available);
+
+        WithCustomizableOverride<?> original = new WithCustomizableOverride<>();
+
+        assertDoesNotThrow(() -> marshalAndUnmarshalNonNull(original));
+    }
+
+    @Test
+    void supportsMarkAndResetInsideReadObject() {
+        readerAndWriter = new ReaderAndWriter<>(oos -> {}, ois -> {
+            //noinspection ResultOfMethodCallIgnored
+            assertFalse(ois.markSupported());
+            ois.mark(1);
+            try {
+                ois.reset();
+            } catch (IOException e) {
+                // ignore mark/reset not supported
+            }
+            return null;
+        });
+
+        WithCustomizableOverride<?> original = new WithCustomizableOverride<>();
+
+        assertDoesNotThrow(() -> marshalAndUnmarshalNonNull(original));
+    }
+
+    @Test
+    void defaultWriteObjectFromWriteObjectWritesUsingOurFormat() throws Exception {
+        WithOverrideStillUsingDefaultMechanism original = new WithOverrideStillUsingDefaultMechanism(42);
+        MarshalledObject marshalled = marshaller.marshal(original);
+
+        byte[] overrideBytes = readOverrideBytes(marshalled);
+        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(overrideBytes));
+
+        assertThat(ProtocolMarshalling.readDescriptorOrCommandId(dis), is(BuiltinType.INT.descriptorId()));
+        assertThat(dis.readInt(), is(42));
+        assertThatDrained(dis);
+    }
+
+    @Test
+    void marshalsAndUnmarshalsSerializableWithReadWriteObjectUsingDefaultMechanism() throws Exception {
+        WithOverrideStillUsingDefaultMechanism result = marshalAndUnmarshalNonNull(new WithOverrideStillUsingDefaultMechanism(42));
+
+        assertThat(result.value, is(42));
+    }
+
+    @Test
+    void marshalsAndUnmarshalsNestedSerializablesWithReadWriteObjectUsingDefaultMechanism() throws Exception {
+        NestHostUsingDefaultMechanism result = marshalAndUnmarshalNonNull(
+                new NestHostUsingDefaultMechanism(42, new NestedUsingDefaultMechanism(100))
+        );
+
+        assertThat(result.value, is(42));
+        assertThat(result.nested.value, is(100));
+    }
+
+    @Test
+    void supportsWriteObjectAndReadObjectInHierarchy() throws Exception {
+        SubclassWithWriteReadOverride result = marshalAndUnmarshalNonNull(new SubclassWithWriteReadOverride(42));
+
+        assertThat(((SerializableWithWriteReadOverride) result).value, is(42 + WRITE_OBJECT_INCREMENT + READ_OBJECT_INCREMENT));
+        assertThat(result.childValue, is(42 + CHILD_WRITE_OBJECT_INCREMENT + CHILD_READ_OBJECT_INCREMENT));
+    }
+
+    /**
+     * An {@link Serializable} that does not have {@code writeReplace()}/{@code readResolve()} methods or other customizations.
+     */
+    private static class SimpleSerializable implements Serializable {
+        int intValue;
+
+        public SimpleSerializable(int intValue) {
+            this.intValue = intValue;
+        }
+    }
+
+    private static class SerializableWithWriteReplace extends SimpleSerializable {
+        public SerializableWithWriteReplace(int intValue) {
+            super(intValue);
+        }
+
+        private Object writeReplace() {
+            return new SerializableWithWriteReplace(intValue + 1_000_000);
+        }
+    }
+
+    private static class SerializableWithReadResolve extends SimpleSerializable {
+        public SerializableWithReadResolve(int intValue) {
+            super(intValue);
+        }
+
+        private Object readResolve() {
+            return new SerializableWithReadResolve(intValue + 1_000);
+        }
+    }
+
+    private static class SerializableWithWriteReplaceReadResolve extends SimpleSerializable {
+        public SerializableWithWriteReplaceReadResolve(int intValue) {
+            super(intValue);
+        }
+
+        private Object writeReplace() {
+            return new SerializableWithWriteReplaceReadResolve(intValue + 1_000_000);
+        }
+
+        private Object readResolve() {
+            return new SerializableWithWriteReplaceReadResolve(intValue + 1_000);
+        }
+    }
+
+    private static class SerializableWithReplaceWithSimple implements Serializable {
+        private final int intValue;
+
+        public SerializableWithReplaceWithSimple(int intValue) {
+            this.intValue = intValue;
+        }
+
+        private Object writeReplace() {
+            return new SimpleSerializable(intValue);
+        }
+    }
+
+    private static class SerializableWithReplaceWithNull extends SimpleSerializable {
+        public SerializableWithReplaceWithNull(int intValue) {
+            super(intValue);
+        }
+
+        private Object writeReplace() {
+            return null;
+        }
+    }
+
+    private static class SerializableWithResolveWithNull extends SimpleSerializable {
+        public SerializableWithResolveWithNull(int intValue) {
+            super(intValue);
+        }
+
+        private Object readResolve() {
+            return null;
+        }
+    }
+
+    private static class SerializableWithWriteReplaceChain1 extends SimpleSerializable {
+        public SerializableWithWriteReplaceChain1(int value) {
+            super(value);
+        }
+
+        private Object writeReplace() {
+            return new SerializableWithWriteReplaceChain2(intValue + 1);
+        }
+    }
+
+    private static class SerializableWithWriteReplaceChain2 extends SimpleSerializable {
+        public SerializableWithWriteReplaceChain2(int value) {
+            super(value);
+        }
+
+        private Object writeReplace() {
+            return intValue + 2;
+        }
+    }
+
+    private static class SerializableWithWriteReplaceWithSameClass extends SimpleSerializable {
+        public SerializableWithWriteReplaceWithSameClass(int value) {
+            super(value);
+        }
+
+        private Object writeReplace() {
+            return new SerializableWithWriteReplaceWithSameClass(intValue + 1);
+        }
+    }
+
+    private static class SerializableWithWriteReplaceCycle1 extends SimpleSerializable {
+        public SerializableWithWriteReplaceCycle1(int intValue) {
+            super(intValue);
+        }
+
+        private Object writeReplace() {
+            return new SerializableWithWriteReplaceCycle2(intValue);
+        }
+    }
+
+    private static class SerializableWithWriteReplaceCycle2 extends SimpleSerializable {
+        public SerializableWithWriteReplaceCycle2(int intValue) {
+            super(intValue);
+        }
+
+        private Object writeReplace() {
+            return new SerializableWithWriteReplaceCycle1(intValue);
+        }
+    }
+
+    private static class SerializableWithReadResolveChain1 extends SimpleSerializable {
+        public SerializableWithReadResolveChain1(int value) {
+            super(value);
+        }
+
+        private Object readResolve() {
+            return new SerializableWithReadResolveChain2(intValue + 1);
+        }
+    }
+
+    private static class SerializableWithReadResolveChain2 extends SimpleSerializable {
+        public SerializableWithReadResolveChain2(int value) {
+            super(value);
+        }
+
+        private Object readResolve() {
+            return intValue + 2;
+        }
+    }
+
+    private static class SerializableWithWriteReadOverride implements Serializable {
+        private int value;
+
+        public SerializableWithWriteReadOverride() {
+        }
+
+        public SerializableWithWriteReadOverride(int value) {
+            this.value = value;
+        }
+
+        private void writeObject(ObjectOutputStream oos) throws IOException {
+            oos.writeInt(value + WRITE_OBJECT_INCREMENT);
+        }
+
+        private void readObject(ObjectInputStream ois) throws IOException {
+            value = ois.readInt() + READ_OBJECT_INCREMENT;
+        }
+    }
+
+    private static class SerializableWithNoOpWriteReadOverride implements Serializable {
+        private int value;
+
+        public SerializableWithNoOpWriteReadOverride() {
+        }
+
+        public SerializableWithNoOpWriteReadOverride(int value) {
+            this.value = value;
+        }
+
+        private void writeObject(ObjectOutputStream oos) throws IOException {
+            // no-op
+        }
+
+        private void readObject(ObjectInputStream ois) throws IOException {
+            // no-op
+        }
+    }
+
+    private interface ContentsWriter {
+        void writeTo(ObjectOutputStream stream) throws IOException;
+    }
+
+    private interface ContentsReader<T> {
+        T readFrom(ObjectInputStream stream) throws IOException, ClassNotFoundException;
+    }
+
+    private interface DataReader<T> {
+        T readFrom(DataInputStream stream) throws IOException;
+    }
+
+    private interface InputReader<T> {
+        T readFrom(DataInput input) throws IOException;
+    }
+
+    private static class ReaderAndWriter<T> {
+        private final ContentsWriter writer;
+        private final ContentsReader<T> reader;
+
+        private ReaderAndWriter(ContentsWriter writer, ContentsReader<T> reader) {
+            this.writer = writer;
+            this.reader = reader;
+        }
+    }
+
+    private static class ReadWriteSpec<T> {
+        private final String name;
+        private final ContentsWriter writer;
+        private final ContentsReader<T> reader;
+        private final DataReader<T> dataReader;
+        private final Consumer<T> valueAsserter;
+
+        private ReadWriteSpec(String name, ContentsWriter writer, InputReader<T> inputReader, T expectedValue) {
+            this(name, writer, inputReader::readFrom, inputReader::readFrom, expectedValue);
+        }
+
+        private ReadWriteSpec(String name, ContentsWriter writer, ContentsReader<T> reader, DataReader<T> dataReader, T expectedValue) {
+            this(name, writer, reader, dataReader, actual -> assertThat(actual, is(expectedValue)));
+        }
+
+        private ReadWriteSpec(String name, ContentsWriter writer, ContentsReader<T> reader, DataReader<T> dataReader, Consumer<T> valueAsserter) {
+            this.name = name;
+            this.writer = writer;
+            this.reader = reader;
+            this.dataReader = dataReader;
+            this.valueAsserter = valueAsserter;
+        }
+
+        private T parseOverrideValue(byte[] overrideBytes) throws IOException {
+            DataInputStream dis = new DataInputStream(new ByteArrayInputStream(overrideBytes));
+            return dataReader.readFrom(dis);
+        }
+
+        private void assertUnmarshalledValue(T value) {
+            valueAsserter.accept(value);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public String toString() {
+            return "ReadWriteSpec (" + name + ")";
+        }
+    }
+
+    private static class WithCustomizableOverride<T> implements Serializable {
+        private T value;
+
+        private void writeObject(ObjectOutputStream oos) throws IOException {
+            readerAndWriter.writer.writeTo(oos);
+        }
+
+        @SuppressWarnings("unchecked")
+        private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
+            value = (T) readerAndWriter.reader.readFrom(ois);
+        }
+    }
+
+    private static class SimpleNonSerializable {
+        private int value;
+
+        @SuppressWarnings("unused")
+        public SimpleNonSerializable() {
+        }
+
+        public SimpleNonSerializable(int value) {
+            this.value = value;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            SimpleNonSerializable that = (SimpleNonSerializable) o;
+            return value == that.value;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(value);
+        }
+    }
+
+    private static class WithOverrideStillUsingDefaultMechanism implements Serializable {
+        private final int value;
+
+        private WithOverrideStillUsingDefaultMechanism(int value) {
+            this.value = value;
+        }
+
+        private void writeObject(ObjectOutputStream stream) throws IOException {
+            stream.defaultWriteObject();
+        }
+
+        private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+            stream.defaultReadObject();
+        }
+    }
+
+    private static class NestedUsingDefaultMechanism implements Serializable {
+        private final int value;
+
+        private NestedUsingDefaultMechanism(int value) {
+            this.value = value;
+        }
+
+        private void writeObject(ObjectOutputStream stream) throws IOException {
+            stream.defaultWriteObject();
+        }
+
+        private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+            stream.defaultReadObject();
+        }
+    }
+
+    private static class NestHostUsingDefaultMechanism implements Serializable {
+        private final int value;
+        private final NestedUsingDefaultMechanism nested;
+
+        private NestHostUsingDefaultMechanism(int value, NestedUsingDefaultMechanism nested) {
+            this.value = value;
+            this.nested = nested;
+        }
+
+        private void writeObject(ObjectOutputStream stream) throws IOException {
+            stream.defaultWriteObject();
+        }
+
+        private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+            stream.defaultReadObject();
+        }
+    }
+
+    private static class SubclassWithWriteReadOverride extends SerializableWithWriteReadOverride {
+        private int childValue;
+
+        public SubclassWithWriteReadOverride() {
+        }
+
+        public SubclassWithWriteReadOverride(int value) {
+            super(value);
+            this.childValue = value;
+        }
+
+        private void writeObject(ObjectOutputStream oos) throws IOException {
+            oos.writeInt(childValue + CHILD_WRITE_OBJECT_INCREMENT);
+        }
+
+        private void readObject(ObjectInputStream ois) throws IOException {
+            childValue = ois.readInt() + CHILD_READ_OBJECT_INCREMENT;
+        }
+    }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshalException.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/Throwables.java
similarity index 67%
copy from modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshalException.java
copy to modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/Throwables.java
index 9e7b624..9b548bb 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshalException.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/Throwables.java
@@ -17,13 +17,25 @@
 
 package org.apache.ignite.internal.network.serialization.marshal;
 
-import org.apache.ignite.lang.IgniteInternalCheckedException;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
- * Thrown when unmarshalling fails.
+ * Throwable-related utilities.
  */
-public class UnmarshalException extends IgniteInternalCheckedException {
-    public UnmarshalException(String message, Throwable cause) {
-        super(message, cause);
+class Throwables {
+    static List<Throwable> causalChain(Throwable th) {
+        List<Throwable> lineage = new ArrayList<>();
+
+        Throwable current = th;
+        while (current != null) {
+            lineage.add(current);
+            current = current.getCause();
+        }
+
+        return List.copyOf(lineage);
+    }
+
+    private Throwables() {
     }
 }

[ignite-3] 25/27: - resolve a TODO

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 0dab994d92a086651938df8651fbbd6f54d7b651
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 19:12:56 2022 +0400

    - resolve a TODO
---
 .../marshal/DefaultUserObjectMarshaller.java       |  1 -
 ...ltUserObjectMarshallerWithSerializableTest.java | 55 ++++++++++++++++++++++
 2 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
index 36a8c6d..b15f6be 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
@@ -315,7 +315,6 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
     private <T> T unmarshalFromInput(DataInputStream input, UnmarshallingContext context) throws IOException, UnmarshalException {
         int commandOrDescriptorId = ProtocolMarshalling.readDescriptorOrCommandId(input);
         if (commandOrDescriptorId == SerializedStreamCommands.REFERENCE) {
-            // TODO: IGNITE-16165 - make sure readResolve() is applied correctly when we exit early due to reading a reference
             return unmarshalReference(input, context);
         }
 
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java
index 66e421e..2cd2388 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java
@@ -25,6 +25,7 @@ import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
+import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -216,6 +217,28 @@ class DefaultUserObjectMarshallerWithSerializableTest {
         assertFalse(constructorCalled);
     }
 
+    @Test
+    void resolveObjectWorksCorrectlyWhenInSelfCycle() throws Exception {
+        SelfRefWithResolveToSelf deserialized = marshalAndUnmarshalNonNull(new SelfRefWithResolveToSelf(42));
+
+        assertThat(deserialized.value, is(42 + READ_RESOLVE_INCREMENT));
+        assertThat(deserialized.self, is(sameInstance(deserialized)));
+    }
+
+    @Test
+    void resolveObjectProducesUnrolledCyclesAsInJavaSerializationWhenObjectIsInCycleWithLengthOfMoreThanOne() throws Exception {
+        RelativeSelfRefWithResolveToSelf first = new RelativeSelfRefWithResolveToSelf(42);
+        RelativeSelfRefWithResolveToSelf second = new RelativeSelfRefWithResolveToSelf(43);
+        first.ref = second;
+        second.ref = first;
+
+        RelativeSelfRefWithResolveToSelf deserialized = marshalAndUnmarshalNonNull(first);
+
+        assertThat(deserialized.value, is(42 + READ_RESOLVE_INCREMENT));
+        assertThat(deserialized.ref.value, is(43 + READ_RESOLVE_INCREMENT));
+        assertThat(deserialized.ref.ref, is(not(sameInstance(deserialized))));
+    }
+
     /**
      * An {@link Serializable} that does not have {@code writeReplace()}/{@code readResolve()} methods or other customizations.
      */
@@ -427,4 +450,36 @@ class DefaultUserObjectMarshallerWithSerializableTest {
             constructorCalled = true;
         }
     }
+
+    private static class SelfRefWithResolveToSelf implements Serializable {
+        private final int value;
+        private final SelfRefWithResolveToSelf self;
+
+        private SelfRefWithResolveToSelf(int value) {
+            this.value = value;
+            this.self = this;
+        }
+
+        private Object readResolve() {
+            return new SelfRefWithResolveToSelf(value + READ_RESOLVE_INCREMENT);
+        }
+    }
+
+    private static class RelativeSelfRefWithResolveToSelf implements Serializable {
+        private final int value;
+        private RelativeSelfRefWithResolveToSelf ref;
+
+        private RelativeSelfRefWithResolveToSelf(int value) {
+            this.value = value;
+        }
+
+        private RelativeSelfRefWithResolveToSelf(int value, RelativeSelfRefWithResolveToSelf ref) {
+            this.value = value;
+            this.ref = ref;
+        }
+
+        private Object readResolve() {
+            return new RelativeSelfRefWithResolveToSelf(value + READ_RESOLVE_INCREMENT, ref);
+        }
+    }
 }

[ignite-3] 02/27: - make ClassDescriptorFactoryContext not implement IdIndexedDescriptors

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 5f1bcddf3dc717e8215d5f55d3e868a6cb9802af
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Mon Jan 10 18:56:12 2022 +0400

    - make ClassDescriptorFactoryContext not implement IdIndexedDescriptors
---
 .../ClassDescriptorFactoryContext.java             | 29 ++++++++++++++--
 .../serialization/IdIndexedDescriptors.java        |  9 -----
 .../marshal/ContextBasedIdIndexedDescriptors.java  | 40 ++++++++++++++++++++++
 .../DefaultUserObjectMarshallerCommonTest.java     |  4 ++-
 ...erObjectMarshallerWithArbitraryObjectsTest.java |  4 ++-
 ...efaultUserObjectMarshallerWithBuiltinsTest.java |  6 ++--
 ...UserObjectMarshallerWithExternalizableTest.java | 10 +++---
 ...ltUserObjectMarshallerWithSerializableTest.java | 10 +++---
 8 files changed, 89 insertions(+), 23 deletions(-)

diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryContext.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryContext.java
index 708aae9..47cba73 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryContext.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryContext.java
@@ -25,7 +25,7 @@ import org.jetbrains.annotations.Nullable;
 /**
  * Class descriptor factory context.
  */
-public class ClassDescriptorFactoryContext implements IdIndexedDescriptors, ClassIndexedDescriptors {
+public class ClassDescriptorFactoryContext implements ClassIndexedDescriptors {
     /** Quantity of descriptor ids reserved for the default descriptors. */
     private static final int DEFAULT_DESCRIPTORS_OFFSET_COUNT = 1000;
 
@@ -81,7 +81,6 @@ public class ClassDescriptorFactoryContext implements IdIndexedDescriptors, Clas
      * @param descriptorId Descriptor id.
      * @return Descriptor.
      */
-    @Override
     @Nullable
     public ClassDescriptor getDescriptor(int descriptorId) {
         return descriptorMap.get(descriptorId);
@@ -115,6 +114,32 @@ public class ClassDescriptorFactoryContext implements IdIndexedDescriptors, Clas
     }
 
     /**
+     * Returns a descriptor by ID or throws an exception if no such descriptor is known.
+     *
+     * @param descriptorId ID of the descriptor
+     * @return descriptor by ID
+     */
+    public ClassDescriptor getRequiredDescriptor(int descriptorId) {
+        ClassDescriptor descriptor = getDescriptor(descriptorId);
+
+        if (descriptor == null) {
+            throw new IllegalStateException("Did not find a descriptor with ID=" + descriptorId);
+        }
+
+        return descriptor;
+    }
+
+    /**
+     * Returns {@code true} if there is a descriptor for the id.
+     *
+     * @param descriptorId Descriptor id.
+     * @return {@code true} if there is a descriptor for the id.
+     */
+    public boolean hasDescriptor(int descriptorId) {
+        return getDescriptor(descriptorId) != null;
+    }
+
+    /**
      * Returns a descriptor for {@code null} value.
      *
      * @return a descriptor for {@code null} value
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/IdIndexedDescriptors.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/IdIndexedDescriptors.java
index 0881bb6..b5938a8 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/IdIndexedDescriptors.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/IdIndexedDescriptors.java
@@ -48,13 +48,4 @@ public interface IdIndexedDescriptors {
         return descriptor;
     }
 
-    /**
-     * Returns {@code true} if there is a descriptor for the id.
-     *
-     * @param descriptorId Descriptor id.
-     * @return {@code true} if there is a descriptor for the id.
-     */
-    default boolean hasDescriptor(int descriptorId) {
-        return getDescriptor(descriptorId) != null;
-    }
 }
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/ContextBasedIdIndexedDescriptors.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/ContextBasedIdIndexedDescriptors.java
new file mode 100644
index 0000000..1952312
--- /dev/null
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/ContextBasedIdIndexedDescriptors.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+import org.apache.ignite.internal.network.serialization.ClassDescriptor;
+import org.apache.ignite.internal.network.serialization.ClassDescriptorFactoryContext;
+import org.apache.ignite.internal.network.serialization.IdIndexedDescriptors;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * {@link IdIndexedDescriptors} implementation that is backed by a {@link ClassDescriptorFactoryContext}.
+ */
+class ContextBasedIdIndexedDescriptors implements IdIndexedDescriptors {
+    private final ClassDescriptorFactoryContext context;
+
+    ContextBasedIdIndexedDescriptors(ClassDescriptorFactoryContext context) {
+        this.context = context;
+    }
+
+    @Override
+    @Nullable
+    public ClassDescriptor getDescriptor(int descriptorId) {
+        return context.getDescriptor(descriptorId);
+    }
+}
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerCommonTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerCommonTest.java
index 78f36f3..00aaf6a 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerCommonTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerCommonTest.java
@@ -24,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import java.util.Arrays;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorFactory;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorFactoryContext;
+import org.apache.ignite.internal.network.serialization.IdIndexedDescriptors;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -32,6 +33,7 @@ import org.junit.jupiter.api.Test;
 class DefaultUserObjectMarshallerCommonTest {
     private final ClassDescriptorFactoryContext descriptorRegistry = new ClassDescriptorFactoryContext();
     private final ClassDescriptorFactory descriptorFactory = new ClassDescriptorFactory(descriptorRegistry);
+    private final IdIndexedDescriptors descriptors = new ContextBasedIdIndexedDescriptors(descriptorRegistry);
 
     private final DefaultUserObjectMarshaller marshaller = new DefaultUserObjectMarshaller(descriptorRegistry, descriptorFactory);
 
@@ -41,7 +43,7 @@ class DefaultUserObjectMarshallerCommonTest {
 
         byte[] tooManyBytes = Arrays.copyOf(marshalled.bytes(), marshalled.bytes().length + 1);
 
-        UnmarshalException ex = assertThrows(UnmarshalException.class, () -> marshaller.unmarshal(tooManyBytes, descriptorRegistry));
+        UnmarshalException ex = assertThrows(UnmarshalException.class, () -> marshaller.unmarshal(tooManyBytes, descriptors));
         assertThat(ex.getMessage(), is("After reading a value, 1 excessive byte(s) still remain"));
     }
 
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
index 1075041..4ed04b3 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
@@ -41,6 +41,7 @@ import java.util.concurrent.Callable;
 import org.apache.ignite.internal.network.serialization.BuiltinType;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorFactory;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorFactoryContext;
+import org.apache.ignite.internal.network.serialization.IdIndexedDescriptors;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
@@ -50,6 +51,7 @@ import org.junit.jupiter.api.Test;
 class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
     private final ClassDescriptorFactoryContext descriptorRegistry = new ClassDescriptorFactoryContext();
     private final ClassDescriptorFactory descriptorFactory = new ClassDescriptorFactory(descriptorRegistry);
+    private final IdIndexedDescriptors descriptors = new ContextBasedIdIndexedDescriptors(descriptorRegistry);
 
     private final DefaultUserObjectMarshaller marshaller = new DefaultUserObjectMarshaller(descriptorRegistry, descriptorFactory);
 
@@ -63,7 +65,7 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
     }
 
     private <T> T unmarshalNonNull(MarshalledObject marshalled) throws UnmarshalException {
-        T unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptorRegistry);
+        T unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptors);
 
         assertThat(unmarshalled, is(notNullValue()));
 
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java
index 80f47ed..438dd44 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java
@@ -50,6 +50,7 @@ import org.apache.ignite.internal.network.serialization.BuiltinType;
 import org.apache.ignite.internal.network.serialization.ClassDescriptor;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorFactory;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorFactoryContext;
+import org.apache.ignite.internal.network.serialization.IdIndexedDescriptors;
 import org.apache.ignite.internal.network.serialization.Null;
 import org.apache.ignite.lang.IgniteUuid;
 import org.junit.jupiter.api.Test;
@@ -63,6 +64,7 @@ import org.junit.jupiter.params.provider.MethodSource;
 class DefaultUserObjectMarshallerWithBuiltinsTest {
     private final ClassDescriptorFactoryContext descriptorRegistry = new ClassDescriptorFactoryContext();
     private final ClassDescriptorFactory descriptorFactory = new ClassDescriptorFactory(descriptorRegistry);
+    private final IdIndexedDescriptors descriptors = new ContextBasedIdIndexedDescriptors(descriptorRegistry);
 
     private final DefaultUserObjectMarshaller marshaller = new DefaultUserObjectMarshaller(descriptorRegistry, descriptorFactory);
 
@@ -76,7 +78,7 @@ class DefaultUserObjectMarshallerWithBuiltinsTest {
     }
 
     private <T> T unmarshalNonNull(MarshalledObject marshalled) throws UnmarshalException {
-        T unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptorRegistry);
+        T unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptors);
 
         assertThat(unmarshalled, is(notNullValue()));
 
@@ -126,7 +128,7 @@ class DefaultUserObjectMarshallerWithBuiltinsTest {
     void marshalsAndUnmarshalsBuiltInNonCollectionTypes(BuiltInTypeValue typeValue) throws Exception {
         MarshalledObject marshalled = marshaller.marshal(typeValue.value, typeValue.valueClass);
 
-        Object unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptorRegistry);
+        Object unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptors);
 
         assertThat(unmarshalled, is(equalTo(typeValue.value)));
         if (typeValue.builtinType != BuiltinType.NULL && typeValue.value.getClass().isArray()) {
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithExternalizableTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithExternalizableTest.java
index 88c24eb..c46c078 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithExternalizableTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithExternalizableTest.java
@@ -41,6 +41,7 @@ import java.util.Set;
 import org.apache.ignite.internal.network.serialization.ClassDescriptor;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorFactory;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorFactoryContext;
+import org.apache.ignite.internal.network.serialization.IdIndexedDescriptors;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -49,6 +50,7 @@ import org.junit.jupiter.api.Test;
 class DefaultUserObjectMarshallerWithExternalizableTest {
     private final ClassDescriptorFactoryContext descriptorRegistry = new ClassDescriptorFactoryContext();
     private final ClassDescriptorFactory descriptorFactory = new ClassDescriptorFactory(descriptorRegistry);
+    private final IdIndexedDescriptors descriptors = new ContextBasedIdIndexedDescriptors(descriptorRegistry);
 
     private final DefaultUserObjectMarshaller marshaller = new DefaultUserObjectMarshaller(descriptorRegistry, descriptorFactory);
 
@@ -77,7 +79,7 @@ class DefaultUserObjectMarshallerWithExternalizableTest {
     }
 
     private <T> T unmarshalNonNull(MarshalledObject marshalled) throws UnmarshalException {
-        T unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptorRegistry);
+        T unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptors);
 
         assertThat(unmarshalled, is(notNullValue()));
 
@@ -117,7 +119,7 @@ class DefaultUserObjectMarshallerWithExternalizableTest {
     void marshalsExternalizableWithReplaceWithNull() throws Exception {
         MarshalledObject marshalled = marshaller.marshal(new ExternalizableWithReplaceWithNull(42));
 
-        SimpleExternalizable unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptorRegistry);
+        SimpleExternalizable unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptors);
 
         assertThat(unmarshalled, is(nullValue()));
     }
@@ -134,7 +136,7 @@ class DefaultUserObjectMarshallerWithExternalizableTest {
     void unmarshalsExternalizableWithResolveWithNull() throws Exception {
         MarshalledObject marshalled = marshaller.marshal(new ExternalizableWithResolveWithNull(42));
 
-        SimpleExternalizable unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptorRegistry);
+        SimpleExternalizable unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptors);
 
         assertThat(unmarshalled, is(nullValue()));
     }
@@ -218,7 +220,7 @@ class DefaultUserObjectMarshallerWithExternalizableTest {
 
         byte[] externalBytes = dis.readAllBytes();
 
-        IntHolder nested = marshaller.unmarshal(externalBytes, descriptorRegistry);
+        IntHolder nested = marshaller.unmarshal(externalBytes, descriptors);
         assertThat(nested, is(notNullValue()));
         assertThat(nested.value, is(42));
     }
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java
index 97e86e7..0ac491c 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java
@@ -48,6 +48,7 @@ import org.apache.ignite.internal.network.serialization.BuiltinType;
 import org.apache.ignite.internal.network.serialization.ClassDescriptor;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorFactory;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorFactoryContext;
+import org.apache.ignite.internal.network.serialization.IdIndexedDescriptors;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -60,6 +61,7 @@ import org.junit.jupiter.params.provider.MethodSource;
 class DefaultUserObjectMarshallerWithSerializableTest {
     private final ClassDescriptorFactoryContext descriptorRegistry = new ClassDescriptorFactoryContext();
     private final ClassDescriptorFactory descriptorFactory = new ClassDescriptorFactory(descriptorRegistry);
+    private final IdIndexedDescriptors descriptors = new ContextBasedIdIndexedDescriptors(descriptorRegistry);
 
     private final DefaultUserObjectMarshaller marshaller = new DefaultUserObjectMarshaller(descriptorRegistry, descriptorFactory);
 
@@ -99,7 +101,7 @@ class DefaultUserObjectMarshallerWithSerializableTest {
     }
 
     private <T> T unmarshalNonNull(MarshalledObject marshalled) throws UnmarshalException {
-        T unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptorRegistry);
+        T unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptors);
 
         assertThat(unmarshalled, is(notNullValue()));
 
@@ -142,7 +144,7 @@ class DefaultUserObjectMarshallerWithSerializableTest {
     void marshalsSerializableWithReplaceWithNull() throws Exception {
         MarshalledObject marshalled = marshaller.marshal(new SerializableWithReplaceWithNull(42));
 
-        SimpleSerializable unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptorRegistry);
+        SimpleSerializable unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptors);
 
         assertThat(unmarshalled, is(nullValue()));
     }
@@ -159,7 +161,7 @@ class DefaultUserObjectMarshallerWithSerializableTest {
     void unmarshalsSerializableWithResolveWithNull() throws Exception {
         MarshalledObject marshalled = marshaller.marshal(new SerializableWithResolveWithNull(42));
 
-        SimpleSerializable unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptorRegistry);
+        SimpleSerializable unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptors);
 
         assertThat(unmarshalled, is(nullValue()));
     }
@@ -320,7 +322,7 @@ class DefaultUserObjectMarshallerWithSerializableTest {
         stream.transferTo(baos);
 
         try {
-            return staticMarshaller.unmarshal(baos.toByteArray(), staticDescriptorRegistry);
+            return staticMarshaller.unmarshal(baos.toByteArray(), new ContextBasedIdIndexedDescriptors(staticDescriptorRegistry));
         } catch (UnmarshalException e) {
             throw new RuntimeException("Unmarshalling failed", e);
         }

[ignite-3] 23/27: - improve javadocs

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 7dcb5766863cc607e5ca05b02ce0246d0f95b2c9
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 16:31:30 2022 +0400

    - improve javadocs
---
 .../marshal/UosObjectInputStream.java              | 23 ++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectInputStream.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectInputStream.java
index 2a5ede8..7fb3f70 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectInputStream.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectInputStream.java
@@ -37,92 +37,110 @@ class UosObjectInputStream extends ObjectInputStream {
         this.context = context;
     }
 
+    /** {@inheritDoc} */
     @Override
     public int read() throws IOException {
         return input.read();
     }
 
+    /** {@inheritDoc} */
     @SuppressWarnings("NullableProblems")
     @Override
     public int read(byte[] buf) throws IOException {
         return input.read(buf);
     }
 
+    /** {@inheritDoc} */
     @Override
     public int read(byte[] buf, int off, int len) throws IOException {
         return input.read(buf, off, len);
     }
 
+    /** {@inheritDoc} */
     @Override
     public byte readByte() throws IOException {
         return input.readByte();
     }
 
+    /** {@inheritDoc} */
     @Override
     public short readShort() throws IOException {
         return input.readShort();
     }
 
+    /** {@inheritDoc} */
     @Override
     public int readInt() throws IOException {
         return input.readInt();
     }
 
+    /** {@inheritDoc} */
     @Override
     public long readLong() throws IOException {
         return input.readLong();
     }
 
+    /** {@inheritDoc} */
     @Override
     public float readFloat() throws IOException {
         return input.readFloat();
     }
 
+    /** {@inheritDoc} */
     @Override
     public double readDouble() throws IOException {
         return input.readDouble();
     }
 
+    /** {@inheritDoc} */
     @Override
     public char readChar() throws IOException {
         return input.readChar();
     }
 
+    /** {@inheritDoc} */
     @Override
     public boolean readBoolean() throws IOException {
         return input.readBoolean();
     }
 
+    /** {@inheritDoc} */
     @Override
     public String readUTF() throws IOException {
         return input.readUTF();
     }
 
+    /** {@inheritDoc} */
     @Override
     public int readUnsignedByte() throws IOException {
         return input.readUnsignedByte();
     }
 
+    /** {@inheritDoc} */
     @Override
     public int readUnsignedShort() throws IOException {
         return input.readUnsignedShort();
     }
 
+    /** {@inheritDoc} */
     @Override
     public void readFully(byte[] buf) throws IOException {
         input.readFully(buf);
     }
 
+    /** {@inheritDoc} */
     @Override
     public void readFully(byte[] buf, int off, int len) throws IOException {
         input.readFully(buf, off, len);
     }
 
+    /** {@inheritDoc} */
     @Override
     public String readLine() throws IOException {
         return input.readLine();
     }
 
+    /** {@inheritDoc} */
     @Override
     protected Object readObjectOverride() throws IOException {
         return doReadObject();
@@ -136,12 +154,14 @@ class UosObjectInputStream extends ObjectInputStream {
         }
     }
 
+    /** {@inheritDoc} */
     @Override
     public Object readUnshared() throws IOException {
         // TODO: IGNITE-16257 - implement 'unshared' logic?
         return doReadObject();
     }
 
+    /** {@inheritDoc} */
     @Override
     public void defaultReadObject() throws IOException {
         try {
@@ -156,16 +176,19 @@ class UosObjectInputStream extends ObjectInputStream {
         }
     }
 
+    /** {@inheritDoc} */
     @Override
     public int available() throws IOException {
         return input.available();
     }
 
+    /** {@inheritDoc} */
     @Override
     public int skipBytes(int len) throws IOException {
         return input.skipBytes(len);
     }
 
+    /** {@inheritDoc} */
     @Override
     public void close() throws IOException {
     }

[ignite-3] 13/27: - refactor mark/reset a bit

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 10df1f877fd853e6286409d721e30a32db923cc9
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 13:45:33 2022 +0400

    - refactor mark/reset a bit
---
 .../network/serialization/marshal/BuiltInContainerMarshallers.java    | 4 ++--
 .../internal/network/serialization/marshal/BuiltInMarshalling.java    | 4 ++--
 .../internal/network/serialization/marshal/ProtocolMarshalling.java   | 3 +++
 .../internal/network/serialization/marshal/UnmarshallingContext.java  | 4 ++--
 4 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java
index 426e46a..184b0f6 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java
@@ -165,7 +165,7 @@ class BuiltInContainerMarshallers {
     ) throws IOException {
         IntFunction<C> collectionFactory = requiredCollectionFactory(collectionDescriptor);
 
-        context.markSource();
+        context.markSource(ProtocolMarshalling.MAX_LENGTH_BYTE_COUNT);
 
         C collection = BuiltInMarshalling.preInstantiateCollection(input, collectionFactory);
 
@@ -212,7 +212,7 @@ class BuiltInContainerMarshallers {
     ) throws IOException {
         IntFunction<M> mapFactory = requiredMapFactory(mapDescriptor);
 
-        context.markSource();
+        context.markSource(ProtocolMarshalling.MAX_LENGTH_BYTE_COUNT);
 
         M map = BuiltInMarshalling.preInstantiateMap(input, mapFactory);
 
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java
index 3aebf83..05fe05a 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java
@@ -364,7 +364,7 @@ class BuiltInMarshalling {
     }
 
     static <T, C extends Collection<T>> C preInstantiateCollection(DataInput input, IntFunction<C> collectionFactory) throws IOException {
-        int length = input.readInt();
+        int length = ProtocolMarshalling.readLength(input);
         return collectionFactory.apply(length);
     }
 
@@ -413,7 +413,7 @@ class BuiltInMarshalling {
     }
 
     static <K, V, M extends Map<K, V>> M preInstantiateMap(DataInput input, IntFunction<M> mapFactory) throws IOException {
-        int length = input.readInt();
+        int length = ProtocolMarshalling.readLength(input);
         return mapFactory.apply(length);
     }
 
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ProtocolMarshalling.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ProtocolMarshalling.java
index c4bc2f3..57574c5 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ProtocolMarshalling.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ProtocolMarshalling.java
@@ -25,6 +25,9 @@ import java.io.IOException;
  * Protocol-wide elements marshalling.
  */
 class ProtocolMarshalling {
+    /** Maximum number of bytes needed to encode a container length. */
+    static final int MAX_LENGTH_BYTE_COUNT = 4;
+
     static void writeDescriptorOrCommandId(int id, DataOutput output) throws IOException {
         output.writeInt(id);
     }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshallingContext.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshallingContext.java
index 9dd093d..d6bfb7b 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshallingContext.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshallingContext.java
@@ -67,8 +67,8 @@ class UnmarshallingContext implements IdIndexedDescriptors {
         return (T) result;
     }
 
-    public void markSource() {
-        source.mark(4);
+    public void markSource(int readAheadLimit) {
+        source.mark(readAheadLimit);
     }
 
     public void resetSourceToMark() {

[ignite-3] 09/27: - rename a class

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 2b275f19970f077d7e601af660e367f7b191b2f8
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 11:40:35 2022 +0400

    - rename a class
---
 ...efaultUserObjectMarshallerWithArbitraryObjectsTest.java | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
index 4ed04b3..20f07ba 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
@@ -97,9 +97,9 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
 
     @Test
     void marshalsAndUnmarshalsClassInstancesInvolvingSuperclasses() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(new SimpleChild("answer", 42));
+        MarshalledObject marshalled = marshaller.marshal(new Child("answer", 42));
 
-        SimpleChild unmarshalled = unmarshalNonNull(marshalled);
+        Child unmarshalled = unmarshalNonNull(marshalled);
 
         assertThat(unmarshalled.parentValue(), is("answer"));
         assertThat(unmarshalled.childValue(), is(42));
@@ -107,11 +107,11 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
 
     @Test
     void usesDescriptorsOfAllAncestors() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(new SimpleChild("answer", 42));
+        MarshalledObject marshalled = marshaller.marshal(new Child("answer", 42));
 
         assertThat(marshalled.usedDescriptors(), hasItems(
                 descriptorRegistry.getRequiredDescriptor(Parent.class),
-                descriptorRegistry.getRequiredDescriptor(SimpleChild.class)
+                descriptorRegistry.getRequiredDescriptor(Child.class)
         ));
     }
 
@@ -407,14 +407,14 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
         }
     }
 
-    private static class SimpleChild extends Parent {
+    private static class Child extends Parent {
         private int value;
 
         @SuppressWarnings("unused") // needed for instantiation
-        public SimpleChild() {
+        public Child() {
         }
 
-        public SimpleChild(String parentValue, int childValue) {
+        public Child(String parentValue, int childValue) {
             super(parentValue);
             this.value = childValue;
         }

[ignite-3] 14/27: - split tests

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 03df52c64ee8078cfdadab5273f6f15da5b52a04
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 14:17:38 2022 +0400

    - split tests
---
 ...hallerWithSerializableOverrideStreamsTest.java} | 391 +------------------
 ...ltUserObjectMarshallerWithSerializableTest.java | 415 ---------------------
 2 files changed, 9 insertions(+), 797 deletions(-)

diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest.java
similarity index 58%
copy from modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java
copy to modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest.java
index 011bfa2..2a6a4d9 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest.java
@@ -18,35 +18,25 @@
 package org.apache.ignite.internal.network.serialization.marshal;
 
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.hasItem;
-import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.lessThan;
-import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.notNullValue;
-import static org.hamcrest.Matchers.nullValue;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.DataInput;
 import java.io.DataInputStream;
-import java.io.Externalizable;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
 import java.util.Objects;
-import java.util.Set;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 import org.apache.ignite.internal.network.serialization.BuiltinType;
-import org.apache.ignite.internal.network.serialization.ClassDescriptor;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorFactory;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorFactoryContext;
 import org.apache.ignite.internal.network.serialization.IdIndexedDescriptors;
@@ -57,24 +47,16 @@ import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
 
 /**
- * Tests for how {@link DefaultUserObjectMarshaller} handles {@link java.io.Serializable}s (but not {@link Externalizable}s).
+ * Tests for how {@link DefaultUserObjectMarshaller} handles {@link ObjectOutputStream}/{@link ObjectInputStream} specifics
+ * when working with writeObject()/readObject() of {@link Serializable}s.
  */
-class DefaultUserObjectMarshallerWithSerializableTest {
+class DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest {
     private final ClassDescriptorFactoryContext descriptorRegistry = new ClassDescriptorFactoryContext();
     private final ClassDescriptorFactory descriptorFactory = new ClassDescriptorFactory(descriptorRegistry);
     private final IdIndexedDescriptors descriptors = new ContextBasedIdIndexedDescriptors(descriptorRegistry);
 
     private final DefaultUserObjectMarshaller marshaller = new DefaultUserObjectMarshaller(descriptorRegistry, descriptorFactory);
 
-    private static final int WRITE_REPLACE_INCREMENT = 1_000_000;
-    private static final int READ_RESOLVE_INCREMENT = 1_000;
-
-    private static final int WRITE_OBJECT_INCREMENT = 10;
-    private static final int READ_OBJECT_INCREMENT = 100;
-
-    private static final int CHILD_WRITE_OBJECT_INCREMENT = 3;
-    private static final int CHILD_READ_OBJECT_INCREMENT = 6;
-
     /** This is static so that writeObject()/readObject() can easily find it. */
     private static ReaderAndWriter<?> readerAndWriter;
 
@@ -83,22 +65,12 @@ class DefaultUserObjectMarshallerWithSerializableTest {
     /** Static access to the registry (for using in parameterized tests) */
     private static ClassDescriptorFactoryContext staticDescriptorRegistry;
 
-    private static boolean nonSerializableParentConstructorCalled;
-    private static boolean constructorCalled;
-
     @BeforeEach
     void initStatics() {
         staticMarshaller = marshaller;
         staticDescriptorRegistry = descriptorRegistry;
     }
 
-    @Test
-    void marshalsAndUnmarshalsSerializable() throws Exception {
-        SimpleSerializable unmarshalled = marshalAndUnmarshalNonNull(new SimpleSerializable(42));
-
-        assertThat(unmarshalled.intValue, is(42));
-    }
-
     private <T> T marshalAndUnmarshalNonNull(Object object) throws MarshalException, UnmarshalException {
         MarshalledObject marshalled = marshaller.marshal(object);
         return unmarshalNonNull(marshalled);
@@ -112,111 +84,6 @@ class DefaultUserObjectMarshallerWithSerializableTest {
         return unmarshalled;
     }
 
-    @Test
-    void appliesWriteReplaceOnSerializable() throws Exception {
-        SimpleSerializable unmarshalled = marshalAndUnmarshalNonNull(new SerializableWithWriteReplace(42));
-
-        assertThat(unmarshalled.intValue, is(equalTo(42 + WRITE_REPLACE_INCREMENT)));
-    }
-
-    @Test
-    void appliesReadResolveOnSerializable() throws Exception {
-        SimpleSerializable unmarshalled = marshalAndUnmarshalNonNull(new SerializableWithReadResolve(42));
-
-        assertThat(unmarshalled.intValue, is(equalTo(42 + READ_RESOLVE_INCREMENT)));
-    }
-
-    @Test
-    void appliesBothWriteReplaceAndReadResolveOnSerializable() throws Exception {
-        SimpleSerializable unmarshalled = marshalAndUnmarshalNonNull(new SerializableWithWriteReplaceReadResolve(42));
-
-        assertThat(unmarshalled.intValue, is(equalTo(42 + WRITE_REPLACE_INCREMENT + READ_RESOLVE_INCREMENT)));
-    }
-
-    @Test
-    void usesDescriptorOfReplacementWhenSerializableIsReplacedWithSomethingDifferent() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(new SerializableWithReplaceWithSimple(42));
-
-        ClassDescriptor originalDescriptor = descriptorRegistry.getRequiredDescriptor(SerializableWithReplaceWithSimple.class);
-        assertThat(marshalled.usedDescriptors(), not(hasItem(originalDescriptor)));
-
-        ClassDescriptor replacementDescriptor = descriptorRegistry.getRequiredDescriptor(SimpleSerializable.class);
-        assertThat(marshalled.usedDescriptors(), hasItem(replacementDescriptor));
-    }
-
-    @Test
-    void marshalsSerializableWithReplaceWithNull() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(new SerializableWithReplaceWithNull(42));
-
-        SimpleSerializable unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptors);
-
-        assertThat(unmarshalled, is(nullValue()));
-    }
-
-    @Test
-    void onlyUsesDescriptorOfNullWhenSerializableIsReplacedWithNull() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(new SerializableWithReplaceWithNull(42));
-
-        ClassDescriptor replacementDescriptor = descriptorRegistry.getNullDescriptor();
-        assertThat(marshalled.usedDescriptors(), equalTo(Set.of(replacementDescriptor)));
-    }
-
-    @Test
-    void unmarshalsSerializableWithResolveWithNull() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(new SerializableWithResolveWithNull(42));
-
-        SimpleSerializable unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptors);
-
-        assertThat(unmarshalled, is(nullValue()));
-    }
-
-    @Test
-    void appliesWriteReplaceOnExternalizableRecursively() throws Exception {
-        Object result = marshalAndUnmarshalNonNull(new SerializableWithWriteReplaceChain1(0));
-
-        assertThat(result, is(instanceOf(Integer.class)));
-        assertThat(result, is(3));
-    }
-
-    @Test
-    void stopsApplyingWriteReplaceOnExternalizableWhenReplacementIsInstanceOfSameClass() throws Exception {
-        SerializableWithWriteReplaceWithSameClass result = marshalAndUnmarshalNonNull(new SerializableWithWriteReplaceWithSameClass(0));
-
-        assertThat(result.intValue, is(1));
-    }
-
-    @Test
-    void causesInfiniteRecursionOnExternalizableWithIndirectWriteReplaceCycle() {
-        assertThrows(StackOverflowError.class, ()  -> marshalAndUnmarshalNonNull(new SerializableWithWriteReplaceCycle1(0)));
-    }
-
-    /**
-     * Java Serialization applies writeReplace() repeatedly, but it only applies readResolve() once.
-     * So we are emulating this behavior.
-     *
-     * @throws Exception if something goes wrong
-     */
-    @Test
-    void onlyAppliesFirstReadResolveOnExternalizable() throws Exception {
-        Object result = marshalAndUnmarshalNonNull(new SerializableWithReadResolveChain1(0));
-
-        assertThat(result, is(instanceOf(SerializableWithReadResolveChain2.class)));
-    }
-
-    @Test
-    void usesWriteObjectAndReadObject() throws Exception {
-        SerializableWithWriteReadOverride result = marshalAndUnmarshalNonNull(new SerializableWithWriteReadOverride(42));
-
-        assertThat(result.value, is(42 + WRITE_OBJECT_INCREMENT + READ_OBJECT_INCREMENT));
-    }
-
-    @Test
-    void doesNotWriteDefaultFieldValuesDataIfWriteReadOverrideIsPresent() throws Exception {
-        SerializableWithNoOpWriteReadOverride result = marshalAndUnmarshalNonNull(new SerializableWithNoOpWriteReadOverride(42));
-
-        assertThat(result.value, is(0));
-    }
-
     @ParameterizedTest
     @MethodSource("readWriteSpecs")
     <T> void objectOutputStreamFromWriteObjectWritesUsingOurFormat(ReadWriteSpec<T> spec) throws Exception {
@@ -270,8 +137,8 @@ class DefaultUserObjectMarshallerWithSerializableTest {
                 new ReadWriteSpec<>("byte array", oos -> oos.write(new byte[]{42, 43}), is -> readBytes(is, 2), is -> readBytes(is, 2), new byte[]{42, 43}),
                 new ReadWriteSpec<>("byte array range", oos -> oos.write(new byte[]{42, 43}, 0, 2), is -> readRange(is, 2), is -> readRange(is, 2), new byte[]{42, 43}),
                 new ReadWriteSpec<>("UTF", oos -> oos.writeUTF("Привет"), DataInput::readUTF, "Привет"),
-                new ReadWriteSpec<>("object", oos -> oos.writeObject(new SimpleNonSerializable(42)), ObjectInputStream::readObject, DefaultUserObjectMarshallerWithSerializableTest::consumeAndUnmarshal, new SimpleNonSerializable(42)),
-                new ReadWriteSpec<>("unshared", oos -> oos.writeUnshared(new SimpleNonSerializable(42)), ObjectInputStream::readUnshared, DefaultUserObjectMarshallerWithSerializableTest::consumeAndUnmarshal, new SimpleNonSerializable(42)),
+                new ReadWriteSpec<>("object", oos -> oos.writeObject(new SimpleNonSerializable(42)), ObjectInputStream::readObject, DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest::consumeAndUnmarshal, new SimpleNonSerializable(42)),
+                new ReadWriteSpec<>("unshared", oos -> oos.writeUnshared(new SimpleNonSerializable(42)), ObjectInputStream::readUnshared, DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest::consumeAndUnmarshal, new SimpleNonSerializable(42)),
                 // the following test writing methods only (readers are just to help testing them)
                 new ReadWriteSpec<>("writeBytes", oos -> oos.writeBytes("abc"), input -> readBytesFully(input, 3), "abc".getBytes()),
                 new ReadWriteSpec<>("writeChars", oos -> oos.writeChars("a"), DataInput::readChar, 'a'),
@@ -289,6 +156,7 @@ class DefaultUserObjectMarshallerWithSerializableTest {
     // TODO: IGNITE-16240 - implement putFields()/writeFields()
     // TODO: IGNITE-16240 - implement readFields()
 
+    @SuppressWarnings("SameParameterValue")
     private static byte[] readBytes(InputStream is, int count) throws IOException {
         byte[] bytes = new byte[count];
         int read = is.read(bytes);
@@ -296,6 +164,7 @@ class DefaultUserObjectMarshallerWithSerializableTest {
         return bytes;
     }
 
+    @SuppressWarnings("SameParameterValue")
     private static byte[] readRange(InputStream is, int count) throws IOException {
         byte[] bytes = new byte[count];
         int read = is.read(bytes, 0, count);
@@ -309,12 +178,14 @@ class DefaultUserObjectMarshallerWithSerializableTest {
         return bytes;
     }
 
+    @SuppressWarnings("SameParameterValue")
     private static byte[] readBytesRangeFully(DataInput is, int count) throws IOException {
         byte[] bytes = new byte[count];
         is.readFully(bytes, 0, count);
         return bytes;
     }
 
+    @SuppressWarnings("SameParameterValue")
     private static byte[] readNBytesRange(InputStream is, int count) throws IOException {
         byte[] bytes = new byte[count];
         is.readNBytes(bytes, 0, count);
@@ -397,7 +268,6 @@ class DefaultUserObjectMarshallerWithSerializableTest {
     @Test
     void supportsMarkAndResetInsideReadObject() {
         readerAndWriter = new ReaderAndWriter<>(oos -> {}, ois -> {
-            //noinspection ResultOfMethodCallIgnored
             assertFalse(ois.markSupported());
             ois.mark(1);
             try {
@@ -443,216 +313,6 @@ class DefaultUserObjectMarshallerWithSerializableTest {
         assertThat(result.nested.value, is(100));
     }
 
-    @Test
-    void supportsWriteObjectAndReadObjectInHierarchy() throws Exception {
-        SubclassWithWriteReadOverride result = marshalAndUnmarshalNonNull(new SubclassWithWriteReadOverride(42));
-
-        assertThat(((SerializableWithWriteReadOverride) result).value, is(42 + WRITE_OBJECT_INCREMENT + READ_OBJECT_INCREMENT));
-        assertThat(result.childValue, is(42 + CHILD_WRITE_OBJECT_INCREMENT + CHILD_READ_OBJECT_INCREMENT));
-    }
-
-    @Test
-    void invokesNoArgConstructorOfNonSerializableParentOnUnmarshalling() throws Exception {
-        SerializableWithSideEffectInParentConstructor object = new SerializableWithSideEffectInParentConstructor();
-        nonSerializableParentConstructorCalled = false;
-
-        marshalAndUnmarshalNonNull(object);
-
-        assertTrue(nonSerializableParentConstructorCalled);
-    }
-
-    @Test
-    void doesNotInvokeNoArgConstructorOfSerializableOnUnmarshalling() throws Exception {
-        SerializableWithSideEffectInConstructor object = new SerializableWithSideEffectInConstructor();
-        constructorCalled = false;
-
-        marshalAndUnmarshalNonNull(object);
-
-        assertFalse(constructorCalled);
-    }
-
-    /**
-     * An {@link Serializable} that does not have {@code writeReplace()}/{@code readResolve()} methods or other customizations.
-     */
-    private static class SimpleSerializable implements Serializable {
-        int intValue;
-
-        public SimpleSerializable(int intValue) {
-            this.intValue = intValue;
-        }
-    }
-
-    private static class SerializableWithWriteReplace extends SimpleSerializable {
-        public SerializableWithWriteReplace(int intValue) {
-            super(intValue);
-        }
-
-        private Object writeReplace() {
-            return new SerializableWithWriteReplace(intValue + 1_000_000);
-        }
-    }
-
-    private static class SerializableWithReadResolve extends SimpleSerializable {
-        public SerializableWithReadResolve(int intValue) {
-            super(intValue);
-        }
-
-        private Object readResolve() {
-            return new SerializableWithReadResolve(intValue + 1_000);
-        }
-    }
-
-    private static class SerializableWithWriteReplaceReadResolve extends SimpleSerializable {
-        public SerializableWithWriteReplaceReadResolve(int intValue) {
-            super(intValue);
-        }
-
-        private Object writeReplace() {
-            return new SerializableWithWriteReplaceReadResolve(intValue + 1_000_000);
-        }
-
-        private Object readResolve() {
-            return new SerializableWithWriteReplaceReadResolve(intValue + 1_000);
-        }
-    }
-
-    private static class SerializableWithReplaceWithSimple implements Serializable {
-        private final int intValue;
-
-        public SerializableWithReplaceWithSimple(int intValue) {
-            this.intValue = intValue;
-        }
-
-        private Object writeReplace() {
-            return new SimpleSerializable(intValue);
-        }
-    }
-
-    private static class SerializableWithReplaceWithNull extends SimpleSerializable {
-        public SerializableWithReplaceWithNull(int intValue) {
-            super(intValue);
-        }
-
-        private Object writeReplace() {
-            return null;
-        }
-    }
-
-    private static class SerializableWithResolveWithNull extends SimpleSerializable {
-        public SerializableWithResolveWithNull(int intValue) {
-            super(intValue);
-        }
-
-        private Object readResolve() {
-            return null;
-        }
-    }
-
-    private static class SerializableWithWriteReplaceChain1 extends SimpleSerializable {
-        public SerializableWithWriteReplaceChain1(int value) {
-            super(value);
-        }
-
-        private Object writeReplace() {
-            return new SerializableWithWriteReplaceChain2(intValue + 1);
-        }
-    }
-
-    private static class SerializableWithWriteReplaceChain2 extends SimpleSerializable {
-        public SerializableWithWriteReplaceChain2(int value) {
-            super(value);
-        }
-
-        private Object writeReplace() {
-            return intValue + 2;
-        }
-    }
-
-    private static class SerializableWithWriteReplaceWithSameClass extends SimpleSerializable {
-        public SerializableWithWriteReplaceWithSameClass(int value) {
-            super(value);
-        }
-
-        private Object writeReplace() {
-            return new SerializableWithWriteReplaceWithSameClass(intValue + 1);
-        }
-    }
-
-    private static class SerializableWithWriteReplaceCycle1 extends SimpleSerializable {
-        public SerializableWithWriteReplaceCycle1(int intValue) {
-            super(intValue);
-        }
-
-        private Object writeReplace() {
-            return new SerializableWithWriteReplaceCycle2(intValue);
-        }
-    }
-
-    private static class SerializableWithWriteReplaceCycle2 extends SimpleSerializable {
-        public SerializableWithWriteReplaceCycle2(int intValue) {
-            super(intValue);
-        }
-
-        private Object writeReplace() {
-            return new SerializableWithWriteReplaceCycle1(intValue);
-        }
-    }
-
-    private static class SerializableWithReadResolveChain1 extends SimpleSerializable {
-        public SerializableWithReadResolveChain1(int value) {
-            super(value);
-        }
-
-        private Object readResolve() {
-            return new SerializableWithReadResolveChain2(intValue + 1);
-        }
-    }
-
-    private static class SerializableWithReadResolveChain2 extends SimpleSerializable {
-        public SerializableWithReadResolveChain2(int value) {
-            super(value);
-        }
-
-        private Object readResolve() {
-            return intValue + 2;
-        }
-    }
-
-    private static class SerializableWithWriteReadOverride implements Serializable {
-        private int value;
-
-        public SerializableWithWriteReadOverride() {
-        }
-
-        public SerializableWithWriteReadOverride(int value) {
-            this.value = value;
-        }
-
-        private void writeObject(ObjectOutputStream oos) throws IOException {
-            oos.writeInt(value + WRITE_OBJECT_INCREMENT);
-        }
-
-        private void readObject(ObjectInputStream ois) throws IOException {
-            value = ois.readInt() + READ_OBJECT_INCREMENT;
-        }
-    }
-
-    private static class SerializableWithNoOpWriteReadOverride implements Serializable {
-        private final int value;
-
-        public SerializableWithNoOpWriteReadOverride(int value) {
-            this.value = value;
-        }
-
-        private void writeObject(ObjectOutputStream oos) throws IOException {
-            // no-op
-        }
-
-        private void readObject(ObjectInputStream ois) throws IOException {
-            // no-op
-        }
-    }
-
     private interface ContentsWriter {
         void writeTo(ObjectOutputStream stream) throws IOException;
     }
@@ -809,37 +469,4 @@ class DefaultUserObjectMarshallerWithSerializableTest {
             stream.defaultReadObject();
         }
     }
-
-    private static class SubclassWithWriteReadOverride extends SerializableWithWriteReadOverride {
-        private int childValue;
-
-        public SubclassWithWriteReadOverride(int value) {
-            super(value);
-            this.childValue = value;
-        }
-
-        private void writeObject(ObjectOutputStream oos) throws IOException {
-            oos.writeInt(childValue + CHILD_WRITE_OBJECT_INCREMENT);
-        }
-
-        private void readObject(ObjectInputStream ois) throws IOException {
-            childValue = ois.readInt() + CHILD_READ_OBJECT_INCREMENT;
-        }
-    }
-
-    private static class NonSerializableParentWithSideEffectInConstructor {
-        protected NonSerializableParentWithSideEffectInConstructor() {
-            nonSerializableParentConstructorCalled = true;
-        }
-    }
-
-    private static class SerializableWithSideEffectInParentConstructor extends NonSerializableParentWithSideEffectInConstructor
-            implements Serializable {
-    }
-
-    private static class SerializableWithSideEffectInConstructor implements Serializable {
-        public SerializableWithSideEffectInConstructor() {
-            constructorCalled = true;
-        }
-    }
 }
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java
index 011bfa2..66e421e 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java
@@ -22,39 +22,24 @@ import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasItem;
 import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.lessThan;
 import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
-import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.DataInput;
-import java.io.DataInputStream;
 import java.io.Externalizable;
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
-import java.util.Objects;
 import java.util.Set;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
-import org.apache.ignite.internal.network.serialization.BuiltinType;
 import org.apache.ignite.internal.network.serialization.ClassDescriptor;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorFactory;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorFactoryContext;
 import org.apache.ignite.internal.network.serialization.IdIndexedDescriptors;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
 
 /**
  * Tests for how {@link DefaultUserObjectMarshaller} handles {@link java.io.Serializable}s (but not {@link Externalizable}s).
@@ -75,23 +60,9 @@ class DefaultUserObjectMarshallerWithSerializableTest {
     private static final int CHILD_WRITE_OBJECT_INCREMENT = 3;
     private static final int CHILD_READ_OBJECT_INCREMENT = 6;
 
-    /** This is static so that writeObject()/readObject() can easily find it. */
-    private static ReaderAndWriter<?> readerAndWriter;
-
-    /** Static access to the marshaller (for using in parameterized tests) */
-    private static UserObjectMarshaller staticMarshaller;
-    /** Static access to the registry (for using in parameterized tests) */
-    private static ClassDescriptorFactoryContext staticDescriptorRegistry;
-
     private static boolean nonSerializableParentConstructorCalled;
     private static boolean constructorCalled;
 
-    @BeforeEach
-    void initStatics() {
-        staticMarshaller = marshaller;
-        staticDescriptorRegistry = descriptorRegistry;
-    }
-
     @Test
     void marshalsAndUnmarshalsSerializable() throws Exception {
         SimpleSerializable unmarshalled = marshalAndUnmarshalNonNull(new SimpleSerializable(42));
@@ -217,232 +188,6 @@ class DefaultUserObjectMarshallerWithSerializableTest {
         assertThat(result.value, is(0));
     }
 
-    @ParameterizedTest
-    @MethodSource("readWriteSpecs")
-    <T> void objectOutputStreamFromWriteObjectWritesUsingOurFormat(ReadWriteSpec<T> spec) throws Exception {
-        readerAndWriter = new ReaderAndWriter<>(spec.writer, spec.reader);
-
-        WithCustomizableOverride<T> original = new WithCustomizableOverride<>();
-        MarshalledObject marshalled = marshaller.marshal(original);
-
-        byte[] overrideBytes = readOverrideBytes(marshalled);
-        T overrideValue = spec.parseOverrideValue(overrideBytes);
-
-        spec.assertUnmarshalledValue(overrideValue);
-    }
-
-    @ParameterizedTest
-    @MethodSource("readWriteSpecs")
-    <T> void supportsReadsAndWritesInWriteObjectAndReadObject(ReadWriteSpec<T> spec) throws Exception {
-        readerAndWriter = new ReaderAndWriter<>(spec.writer, spec.reader);
-
-        WithCustomizableOverride<T> original = new WithCustomizableOverride<>();
-        WithCustomizableOverride<T> unmarshalled = marshalAndUnmarshalNonNull(original);
-
-        spec.assertUnmarshalledValue(unmarshalled.value);
-    }
-
-    private byte[] readOverrideBytes(MarshalledObject marshalled) throws IOException {
-        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(marshalled.bytes()));
-
-        ProtocolMarshalling.readDescriptorOrCommandId(dis);
-        ProtocolMarshalling.readObjectId(dis);
-
-        return dis.readAllBytes();
-    }
-
-    private void assertThatDrained(DataInputStream dis) throws IOException {
-        assertThat(dis.read(), is(lessThan(0)));
-    }
-
-    private static Stream<Arguments> readWriteSpecs() {
-        return Stream.of(
-                // the following test perfect pairs like writeByte()/readByte()
-                new ReadWriteSpec<>("data byte", oos -> oos.writeByte(42), DataInput::readByte, (byte) 42),
-                new ReadWriteSpec<>("short", oos -> oos.writeShort(42), DataInput::readShort, (short) 42),
-                new ReadWriteSpec<>("int", oos -> oos.writeInt(42), DataInput::readInt, 42),
-                new ReadWriteSpec<>("long", oos -> oos.writeLong(42), DataInput::readLong, 42L),
-                new ReadWriteSpec<>("float", oos -> oos.writeFloat(42.0f), DataInput::readFloat, 42.0f),
-                new ReadWriteSpec<>("double", oos -> oos.writeDouble(42.0), DataInput::readDouble, 42.0),
-                new ReadWriteSpec<>("char", oos -> oos.writeChar('a'), DataInput::readChar, 'a'),
-                new ReadWriteSpec<>("boolean", oos -> oos.writeBoolean(true), DataInput::readBoolean, true),
-                new ReadWriteSpec<>("stream byte", oos -> oos.write(42), ObjectInputStream::read, DataInputStream::read, 42),
-                new ReadWriteSpec<>("byte array", oos -> oos.write(new byte[]{42, 43}), is -> readBytes(is, 2), is -> readBytes(is, 2), new byte[]{42, 43}),
-                new ReadWriteSpec<>("byte array range", oos -> oos.write(new byte[]{42, 43}, 0, 2), is -> readRange(is, 2), is -> readRange(is, 2), new byte[]{42, 43}),
-                new ReadWriteSpec<>("UTF", oos -> oos.writeUTF("Привет"), DataInput::readUTF, "Привет"),
-                new ReadWriteSpec<>("object", oos -> oos.writeObject(new SimpleNonSerializable(42)), ObjectInputStream::readObject, DefaultUserObjectMarshallerWithSerializableTest::consumeAndUnmarshal, new SimpleNonSerializable(42)),
-                new ReadWriteSpec<>("unshared", oos -> oos.writeUnshared(new SimpleNonSerializable(42)), ObjectInputStream::readUnshared, DefaultUserObjectMarshallerWithSerializableTest::consumeAndUnmarshal, new SimpleNonSerializable(42)),
-                // the following test writing methods only (readers are just to help testing them)
-                new ReadWriteSpec<>("writeBytes", oos -> oos.writeBytes("abc"), input -> readBytesFully(input, 3), "abc".getBytes()),
-                new ReadWriteSpec<>("writeChars", oos -> oos.writeChars("a"), DataInput::readChar, 'a'),
-                // the following test reading methods only (writers are just to help testing them)
-                new ReadWriteSpec<>("readFully", oos -> oos.write(new byte[]{42, 43}), input -> readBytesFully(input, 2), new byte[]{42, 43}),
-                new ReadWriteSpec<>("readFully range", oos -> oos.write(new byte[]{42, 43}), input -> readBytesRangeFully(input, 2), new byte[]{42, 43}),
-                new ReadWriteSpec<>("readUnsignedByte", oos -> oos.writeByte(42), DataInput::readUnsignedByte, 42),
-                new ReadWriteSpec<>("readUnsignedShort", oos -> oos.writeShort(42), DataInput::readUnsignedShort, 42),
-                new ReadWriteSpec<>("readAllBytes", oos -> oos.write(new byte[]{42, 43}), InputStream::readAllBytes, InputStream::readAllBytes, new byte[]{42, 43}),
-                new ReadWriteSpec<>("readNBytes", oos -> oos.write(new byte[]{42, 43}), ois -> ois.readNBytes(2), dis -> readBytesFully(dis, 2), new byte[]{42, 43}),
-                new ReadWriteSpec<>("readNBytes range", oos -> oos.write(new byte[]{42, 43}), ois -> readNBytesRange(ois, 2), dis -> readNBytesRange(dis, 2), new byte[]{42, 43})
-        ).map(Arguments::of);
-    }
-
-    // TODO: IGNITE-16240 - implement putFields()/writeFields()
-    // TODO: IGNITE-16240 - implement readFields()
-
-    private static byte[] readBytes(InputStream is, int count) throws IOException {
-        byte[] bytes = new byte[count];
-        int read = is.read(bytes);
-        assertThat(read, is(count));
-        return bytes;
-    }
-
-    private static byte[] readRange(InputStream is, int count) throws IOException {
-        byte[] bytes = new byte[count];
-        int read = is.read(bytes, 0, count);
-        assertThat(read, is(count));
-        return bytes;
-    }
-
-    private static byte[] readBytesFully(DataInput is, int count) throws IOException {
-        byte[] bytes = new byte[count];
-        is.readFully(bytes);
-        return bytes;
-    }
-
-    private static byte[] readBytesRangeFully(DataInput is, int count) throws IOException {
-        byte[] bytes = new byte[count];
-        is.readFully(bytes, 0, count);
-        return bytes;
-    }
-
-    private static byte[] readNBytesRange(InputStream is, int count) throws IOException {
-        byte[] bytes = new byte[count];
-        is.readNBytes(bytes, 0, count);
-        return bytes;
-    }
-
-    private static <T> T consumeAndUnmarshal(DataInputStream stream) throws IOException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        stream.transferTo(baos);
-
-        try {
-            return staticMarshaller.unmarshal(baos.toByteArray(), new ContextBasedIdIndexedDescriptors(staticDescriptorRegistry));
-        } catch (UnmarshalException e) {
-            throw new RuntimeException("Unmarshalling failed", e);
-        }
-    }
-
-    @Test
-    void supportsFlushInsideWriteObject() {
-        readerAndWriter = new ReaderAndWriter<>(ObjectOutputStream::flush, ois -> null);
-
-        WithCustomizableOverride<?> original = new WithCustomizableOverride<>();
-
-        assertDoesNotThrow(() -> marshalAndUnmarshalNonNull(original));
-    }
-
-    @Test
-    void supportsResetInsideWriteObject() {
-        readerAndWriter = new ReaderAndWriter<>(ObjectOutputStream::reset, ois -> null);
-
-        WithCustomizableOverride<?> original = new WithCustomizableOverride<>();
-
-        assertDoesNotThrow(() -> marshalAndUnmarshalNonNull(original));
-    }
-
-    @Test
-    void supportsUseProtocolVersionInsideWriteObject() {
-        readerAndWriter = new ReaderAndWriter<>(oos -> oos.useProtocolVersion(1), ois -> null);
-
-        WithCustomizableOverride<?> original = new WithCustomizableOverride<>();
-
-        assertDoesNotThrow(() -> marshalAndUnmarshalNonNull(original));
-    }
-
-    @Test
-    void supportsSkipInsideReadObject() throws Exception {
-        readerAndWriter = new ReaderAndWriter<>(oos -> oos.write(new byte[]{42, 43}), ois -> {
-            assertThat(ois.skip(1), is(1L));
-            return ois.readByte();
-        });
-
-        WithCustomizableOverride<?> original = new WithCustomizableOverride<>();
-
-        WithCustomizableOverride<Byte> unmarshalled = marshalAndUnmarshalNonNull(original);
-        assertThat(unmarshalled.value, is((byte) 43));
-    }
-
-    @Test
-    void supportsSkipBytesInsideReadObject() throws Exception {
-        readerAndWriter = new ReaderAndWriter<>(oos -> oos.write(new byte[]{42, 43}), ois -> {
-            assertThat(ois.skipBytes(1), is(1));
-            return ois.readByte();
-        });
-
-        WithCustomizableOverride<?> original = new WithCustomizableOverride<>();
-
-        WithCustomizableOverride<Byte> unmarshalled = marshalAndUnmarshalNonNull(original);
-        assertThat(unmarshalled.value, is((byte) 43));
-    }
-
-    @Test
-    void supportsAvailableInsideReadObject() {
-        readerAndWriter = new ReaderAndWriter<>(oos -> {}, ObjectInputStream::available);
-
-        WithCustomizableOverride<?> original = new WithCustomizableOverride<>();
-
-        assertDoesNotThrow(() -> marshalAndUnmarshalNonNull(original));
-    }
-
-    @Test
-    void supportsMarkAndResetInsideReadObject() {
-        readerAndWriter = new ReaderAndWriter<>(oos -> {}, ois -> {
-            //noinspection ResultOfMethodCallIgnored
-            assertFalse(ois.markSupported());
-            ois.mark(1);
-            try {
-                ois.reset();
-            } catch (IOException e) {
-                // ignore mark/reset not supported
-            }
-            return null;
-        });
-
-        WithCustomizableOverride<?> original = new WithCustomizableOverride<>();
-
-        assertDoesNotThrow(() -> marshalAndUnmarshalNonNull(original));
-    }
-
-    @Test
-    void defaultWriteObjectFromWriteObjectWritesUsingOurFormat() throws Exception {
-        WithOverrideStillUsingDefaultMechanism original = new WithOverrideStillUsingDefaultMechanism(42);
-        MarshalledObject marshalled = marshaller.marshal(original);
-
-        byte[] overrideBytes = readOverrideBytes(marshalled);
-        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(overrideBytes));
-
-        assertThat(ProtocolMarshalling.readDescriptorOrCommandId(dis), is(BuiltinType.INT.descriptorId()));
-        assertThat(dis.readInt(), is(42));
-        assertThatDrained(dis);
-    }
-
-    @Test
-    void marshalsAndUnmarshalsSerializableWithReadWriteObjectUsingDefaultMechanism() throws Exception {
-        WithOverrideStillUsingDefaultMechanism result = marshalAndUnmarshalNonNull(new WithOverrideStillUsingDefaultMechanism(42));
-
-        assertThat(result.value, is(42));
-    }
-
-    @Test
-    void marshalsAndUnmarshalsNestedSerializablesWithReadWriteObjectUsingDefaultMechanism() throws Exception {
-        NestHostUsingDefaultMechanism result = marshalAndUnmarshalNonNull(
-                new NestHostUsingDefaultMechanism(42, new NestedUsingDefaultMechanism(100))
-        );
-
-        assertThat(result.value, is(42));
-        assertThat(result.nested.value, is(100));
-    }
-
     @Test
     void supportsWriteObjectAndReadObjectInHierarchy() throws Exception {
         SubclassWithWriteReadOverride result = marshalAndUnmarshalNonNull(new SubclassWithWriteReadOverride(42));
@@ -621,9 +366,6 @@ class DefaultUserObjectMarshallerWithSerializableTest {
     private static class SerializableWithWriteReadOverride implements Serializable {
         private int value;
 
-        public SerializableWithWriteReadOverride() {
-        }
-
         public SerializableWithWriteReadOverride(int value) {
             this.value = value;
         }
@@ -653,163 +395,6 @@ class DefaultUserObjectMarshallerWithSerializableTest {
         }
     }
 
-    private interface ContentsWriter {
-        void writeTo(ObjectOutputStream stream) throws IOException;
-    }
-
-    private interface ContentsReader<T> {
-        T readFrom(ObjectInputStream stream) throws IOException, ClassNotFoundException;
-    }
-
-    private interface DataReader<T> {
-        T readFrom(DataInputStream stream) throws IOException;
-    }
-
-    private interface InputReader<T> {
-        T readFrom(DataInput input) throws IOException;
-    }
-
-    private static class ReaderAndWriter<T> {
-        private final ContentsWriter writer;
-        private final ContentsReader<T> reader;
-
-        private ReaderAndWriter(ContentsWriter writer, ContentsReader<T> reader) {
-            this.writer = writer;
-            this.reader = reader;
-        }
-    }
-
-    private static class ReadWriteSpec<T> {
-        private final String name;
-        private final ContentsWriter writer;
-        private final ContentsReader<T> reader;
-        private final DataReader<T> dataReader;
-        private final Consumer<T> valueAsserter;
-
-        private ReadWriteSpec(String name, ContentsWriter writer, InputReader<T> inputReader, T expectedValue) {
-            this(name, writer, inputReader::readFrom, inputReader::readFrom, expectedValue);
-        }
-
-        private ReadWriteSpec(String name, ContentsWriter writer, ContentsReader<T> reader, DataReader<T> dataReader, T expectedValue) {
-            this(name, writer, reader, dataReader, actual -> assertThat(actual, is(expectedValue)));
-        }
-
-        private ReadWriteSpec(String name, ContentsWriter writer, ContentsReader<T> reader, DataReader<T> dataReader, Consumer<T> valueAsserter) {
-            this.name = name;
-            this.writer = writer;
-            this.reader = reader;
-            this.dataReader = dataReader;
-            this.valueAsserter = valueAsserter;
-        }
-
-        private T parseOverrideValue(byte[] overrideBytes) throws IOException {
-            DataInputStream dis = new DataInputStream(new ByteArrayInputStream(overrideBytes));
-            return dataReader.readFrom(dis);
-        }
-
-        private void assertUnmarshalledValue(T value) {
-            valueAsserter.accept(value);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public String toString() {
-            return "ReadWriteSpec (" + name + ")";
-        }
-    }
-
-    private static class WithCustomizableOverride<T> implements Serializable {
-        private T value;
-
-        private void writeObject(ObjectOutputStream oos) throws IOException {
-            readerAndWriter.writer.writeTo(oos);
-        }
-
-        @SuppressWarnings("unchecked")
-        private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
-            value = (T) readerAndWriter.reader.readFrom(ois);
-        }
-    }
-
-    private static class SimpleNonSerializable {
-        private int value;
-
-        @SuppressWarnings("unused")
-        public SimpleNonSerializable() {
-        }
-
-        public SimpleNonSerializable(int value) {
-            this.value = value;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            SimpleNonSerializable that = (SimpleNonSerializable) o;
-            return value == that.value;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(value);
-        }
-    }
-
-    private static class WithOverrideStillUsingDefaultMechanism implements Serializable {
-        private final int value;
-
-        private WithOverrideStillUsingDefaultMechanism(int value) {
-            this.value = value;
-        }
-
-        private void writeObject(ObjectOutputStream stream) throws IOException {
-            stream.defaultWriteObject();
-        }
-
-        private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
-            stream.defaultReadObject();
-        }
-    }
-
-    private static class NestedUsingDefaultMechanism implements Serializable {
-        private final int value;
-
-        private NestedUsingDefaultMechanism(int value) {
-            this.value = value;
-        }
-
-        private void writeObject(ObjectOutputStream stream) throws IOException {
-            stream.defaultWriteObject();
-        }
-
-        private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
-            stream.defaultReadObject();
-        }
-    }
-
-    private static class NestHostUsingDefaultMechanism implements Serializable {
-        private final int value;
-        private final NestedUsingDefaultMechanism nested;
-
-        private NestHostUsingDefaultMechanism(int value, NestedUsingDefaultMechanism nested) {
-            this.value = value;
-            this.nested = nested;
-        }
-
-        private void writeObject(ObjectOutputStream stream) throws IOException {
-            stream.defaultWriteObject();
-        }
-
-        private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
-            stream.defaultReadObject();
-        }
-    }
-
     private static class SubclassWithWriteReadOverride extends SerializableWithWriteReadOverride {
         private int childValue;
 

[ignite-3] 08/27: - make sure Serializable constructor is not called on unmarshalling, while first non-serializable parent constructor gets called

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 5d77e863d0964abd17a50672d903ae184a481acc
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 11:38:27 2022 +0400

    - make sure Serializable constructor is not called on unmarshalling, while first non-serializable parent constructor gets called
---
 .../marshal/ArbitraryObjectMarshaller.java         |  6 +-
 .../marshal/DefaultUserObjectMarshaller.java       |  6 +-
 .../marshal/SerializableInstantiation.java         | 99 ++++++----------------
 .../marshal/SerializableInstantiationTest.java     | 36 ++------
 4 files changed, 36 insertions(+), 111 deletions(-)

diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ArbitraryObjectMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ArbitraryObjectMarshaller.java
index 3ec8eeb..1ad2e39 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ArbitraryObjectMarshaller.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ArbitraryObjectMarshaller.java
@@ -25,7 +25,6 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import org.apache.ignite.internal.network.serialization.ClassDescriptor;
-import org.apache.ignite.internal.network.serialization.ClassIndexedDescriptors;
 import org.apache.ignite.internal.network.serialization.FieldDescriptor;
 import org.apache.ignite.internal.network.serialization.SpecialMethodInvocationException;
 
@@ -38,13 +37,12 @@ class ArbitraryObjectMarshaller implements DefaultFieldsReaderWriter {
 
     private final Instantiation instantiation;
 
-    ArbitraryObjectMarshaller(ClassIndexedDescriptors descriptors, TypedValueWriter valueWriter, ValueReader<Object> valueReader) {
+    ArbitraryObjectMarshaller(TypedValueWriter valueWriter, ValueReader<Object> valueReader) {
         this.valueWriter = valueWriter;
         this.valueReader = valueReader;
 
         instantiation = new BestEffortInstantiation(
-                new NoArgConstructorInstantiation(),
-                new SerializableInstantiation(descriptors),
+                new SerializableInstantiation(),
                 new UnsafeInstantiation()
         );
     }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
index 82c3973..e1dc6a1 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
@@ -65,11 +65,7 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
         this.descriptorRegistry = descriptorRegistry;
         this.descriptorFactory = descriptorFactory;
 
-        arbitraryObjectMarshaller = new ArbitraryObjectMarshaller(
-                descriptorRegistry,
-                this::marshalToOutput,
-                this::unmarshalFromInput
-        );
+        arbitraryObjectMarshaller = new ArbitraryObjectMarshaller(this::marshalToOutput, this::unmarshalFromInput);
 
         externalizableMarshaller = new ExternalizableMarshaller(this::unmarshalFromInput, this::marshalToOutput, arbitraryObjectMarshaller);
     }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SerializableInstantiation.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SerializableInstantiation.java
index 665bfb1..f31448d 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SerializableInstantiation.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SerializableInstantiation.java
@@ -17,99 +17,56 @@
 
 package org.apache.ignite.internal.network.serialization.marshal;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
 import java.io.ObjectStreamClass;
-import java.io.ObjectStreamConstants;
 import java.io.Serializable;
-import org.apache.ignite.internal.network.serialization.ClassDescriptor;
-import org.apache.ignite.internal.network.serialization.ClassIndexedDescriptors;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
 
 /**
- * Instantiates {@link Serializable} classes (they are the only ones supported) by crafting a representation of
- * a serialized object of a given class (without any field data) and then deserializing it using the standard
- * Java Serialization.
+ * Instantiates {@link Serializable} classes using the mechanism defined by Java Serialization. That is,
+ * for an {@link java.io.Externalizable}, its no-arg constructor is invoked. For a non-Externalizable {@link Serializable},
+ * a new constructor is generated that invokes the no-arg constructor of the deepest non-serializable ancestor in the hierarchy.
  */
 class SerializableInstantiation implements Instantiation {
 
-    private static final int STREAM_VERSION = 5;
+    private static final MethodHandle STREAM_CLASS_NEW_INSTANCE;
 
-    private final ClassIndexedDescriptors descriptors;
+    static {
+        try {
+            STREAM_CLASS_NEW_INSTANCE = streamClassNewInstanceMethodHandle();
+        } catch (ReflectiveOperationException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+    }
 
-    SerializableInstantiation(ClassIndexedDescriptors descriptors) {
-        this.descriptors = descriptors;
+    private static MethodHandle streamClassNewInstanceMethodHandle() throws NoSuchMethodException, IllegalAccessException {
+        MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(ObjectStreamClass.class, MethodHandles.lookup());
+        return lookup.findVirtual(ObjectStreamClass.class, "newInstance", MethodType.methodType(Object.class));
     }
 
     /** {@inheritDoc} */
     @Override
     public boolean supports(Class<?> objectClass) {
-        if (!Serializable.class.isAssignableFrom(objectClass)) {
-            return false;
-        }
-
-        ClassDescriptor descriptor = descriptors.getRequiredDescriptor(objectClass);
-        return !descriptor.hasWriteReplace() && !descriptor.hasReadResolve();
+        return Serializable.class.isAssignableFrom(objectClass);
     }
 
     /** {@inheritDoc} */
     @Override
     public Object newInstance(Class<?> objectClass) throws InstantiationException {
-        byte[] jdkSerialization = jdkSerializationOfEmptyInstanceOf(objectClass);
-
-        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(jdkSerialization))) {
-            return ois.readObject();
-        } catch (IOException | ClassNotFoundException e) {
-            throw new InstantiationException("Cannot deserialize JDK serialization of an empty instance", e);
-        }
-    }
-
-    private byte[] jdkSerializationOfEmptyInstanceOf(Class<?> objectClass) throws InstantiationException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-
-        try (DataOutputStream dos = new DataOutputStream(baos)) {
-            writeSignature(dos);
-
-            dos.writeByte(ObjectStreamConstants.TC_OBJECT);
-            dos.writeByte(ObjectStreamConstants.TC_CLASSDESC);
-            dos.writeUTF(objectClass.getName());
-
-            dos.writeLong(serialVersionUid(objectClass));
+        // Using the standard machinery (ObjectStreamClass) to instantiate an object to avoid generating excessive constructors
+        // (as the standard machinery caches the constructors effectively).
 
-            writeFlags(dos);
+        ObjectStreamClass desc = ObjectStreamClass.lookup(objectClass);
 
-            writeZeroFields(dos);
+        // But as ObjectStreamClass#newInstance() is package-local, we have to resort to reflection/method handles magic.
 
-            dos.writeByte(ObjectStreamConstants.TC_ENDBLOCKDATA);
-            writeNullForNoParentDescriptor(dos);
-        } catch (IOException e) {
-            throw new InstantiationException("Cannot create JDK serialization of an empty instance", e);
+        try {
+            return STREAM_CLASS_NEW_INSTANCE.invokeExact(desc);
+        } catch (Error e) {
+            throw e;
+        } catch (Throwable e) {
+            throw new InstantiationException("Cannot instantiate", e);
         }
-
-        return baos.toByteArray();
-    }
-
-    private void writeSignature(DataOutputStream dos) throws IOException {
-        dos.writeShort(ObjectStreamConstants.STREAM_MAGIC);
-        dos.writeShort(STREAM_VERSION);
-    }
-
-    private long serialVersionUid(Class<?> objectClass) {
-        ObjectStreamClass descriptor = ObjectStreamClass.lookup(objectClass);
-        return descriptor.getSerialVersionUID();
-    }
-
-    private void writeFlags(DataOutputStream dos) throws IOException {
-        dos.writeByte(ObjectStreamConstants.SC_SERIALIZABLE);
-    }
-
-    private void writeZeroFields(DataOutputStream dos) throws IOException {
-        dos.writeShort(0);
-    }
-
-    private void writeNullForNoParentDescriptor(DataOutputStream dos) throws IOException {
-        dos.writeByte(ObjectStreamConstants.TC_NULL);
     }
 }
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/SerializableInstantiationTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/SerializableInstantiationTest.java
index fbcb148..c0dbe03 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/SerializableInstantiationTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/SerializableInstantiationTest.java
@@ -22,17 +22,10 @@ import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.lenient;
 
 import java.io.Serializable;
-import org.apache.ignite.internal.network.serialization.ClassDescriptor;
-import org.apache.ignite.internal.network.serialization.ClassIndexedDescriptors;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 
 /**
@@ -40,22 +33,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
  */
 @ExtendWith(MockitoExtension.class)
 class SerializableInstantiationTest {
-    @Mock
-    private ClassIndexedDescriptors descriptors;
-    @Mock
-    private ClassDescriptor descriptor;
-
-    private Instantiation instantiation;
-
-    @BeforeEach
-    void initMocks() {
-        lenient().when(descriptors.getRequiredDescriptor(any())).thenReturn(descriptor);
-    }
-
-    @BeforeEach
-    void createInstantiation() {
-        instantiation = new SerializableInstantiation(descriptors);
-    }
+    private final Instantiation instantiation = new SerializableInstantiation();
 
     @Test
     void doesNotSupportNonSerializableClasses() {
@@ -68,17 +46,13 @@ class SerializableInstantiationTest {
     }
 
     @Test
-    void doesNotSupportClassesWithWriteReplace() {
-        doReturn(true).when(descriptor).hasWriteReplace();
-
-        assertFalse(instantiation.supports(WithWriteReplace.class));
+    void supportsClassesWithWriteReplace() {
+        assertTrue(instantiation.supports(WithWriteReplace.class));
     }
 
     @Test
-    void doesNotSupportClassesWithReadResolve() {
-        doReturn(true).when(descriptor).hasReadResolve();
-
-        assertFalse(instantiation.supports(WithReadResolve.class));
+    void supportsClassesWithReadResolve() {
+        assertTrue(instantiation.supports(WithReadResolve.class));
     }
 
     // TODO: IGNITE-16165 - test that it does not support instantiation of Serializable classes with writeObject()/readObject()

[ignite-3] 03/27: - improve javadoc

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 1c26598513954f20367aca8ffaef7d5df3821d81
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Mon Jan 10 19:00:23 2022 +0400

    - improve javadoc
---
 .../internal/network/serialization/marshal/UserObjectMarshaller.java | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UserObjectMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UserObjectMarshaller.java
index acf13b8..04c616b 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UserObjectMarshaller.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UserObjectMarshaller.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.network.serialization.marshal;
 
+import java.lang.reflect.Field;
 import org.apache.ignite.internal.network.serialization.IdIndexedDescriptors;
 import org.jetbrains.annotations.Nullable;
 
@@ -30,8 +31,8 @@ public interface UserObjectMarshaller {
      * Marshals the provided object.
      *
      * @param object        object to marshal
-     * @param declaredClass class of the object as it is seen externally; it may differ from object.getClass() only
-     *                      when it is for a primitive type (i.e. byte.class) or Void.class
+     * @param declaredClass class of the object as it is perceived externally, usually in {@link Field#getType()} from which the value
+     *                      is extracted; it may differ from object.getClass() only when it is for a primitive type (i.e. byte.class)
      * @return marshalled representation
      * @throws MarshalException if marshalling fails
      */

[ignite-3] 24/27: - use varints for descriptor/command/object ID and container serialization

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 2432830b6dd55d431e06f4af342d9e6f93b7d73d
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 17:53:52 2022 +0400

    - use varints for descriptor/command/object ID and container serialization
---
 .../serialization/marshal/BuiltInMarshalling.java  |   2 +-
 .../serialization/marshal/ProtocolMarshalling.java |  30 +----
 .../network/serialization/marshal/VarInts.java     |  66 +++++++++++
 ...erObjectMarshallerWithArbitraryObjectsTest.java |   2 +-
 .../network/serialization/marshal/VarIntsTest.java | 127 +++++++++++++++++++++
 5 files changed, 201 insertions(+), 26 deletions(-)

diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java
index 0d48982..a2f02db 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java
@@ -304,7 +304,7 @@ class BuiltInMarshalling {
 
     static <T> T[] preInstantiateGenericRefArray(DataInput input) throws IOException, UnmarshalException {
         IntFunction<T[]> arrayFactory = readTypeAndCreateArrayFactory(input);
-        int length = input.readInt();
+        int length = readLength(input);
         return arrayFactory.apply(length);
     }
 
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ProtocolMarshalling.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ProtocolMarshalling.java
index 57574c5..32f5788 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ProtocolMarshalling.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ProtocolMarshalling.java
@@ -29,46 +29,28 @@ class ProtocolMarshalling {
     static final int MAX_LENGTH_BYTE_COUNT = 4;
 
     static void writeDescriptorOrCommandId(int id, DataOutput output) throws IOException {
-        output.writeInt(id);
+        VarInts.writeUnsignedInt(id, output);
     }
 
     static int readDescriptorOrCommandId(DataInput input) throws IOException {
-        return input.readInt();
+        return VarInts.readUnsignedInt(input);
     }
 
     static void writeObjectId(int id, DataOutput output) throws IOException {
-        output.writeInt(id);
+        VarInts.writeUnsignedInt(id, output);
     }
 
     static int readObjectId(DataInput input) throws IOException {
-        return input.readInt();
+        return VarInts.readUnsignedInt(input);
     }
 
 
     static void writeLength(int length, DataOutput output) throws IOException {
-        writeUnsignedInt(length, output);
-    }
-
-    private static void writeUnsignedInt(int value, DataOutput output) throws IOException {
-        if (value < 0) {
-            throw new IllegalArgumentException(value + " is negative" );
-        }
-
-        output.writeInt(value);
+        VarInts.writeUnsignedInt(length, output);
     }
 
     static int readLength(DataInput input) throws IOException {
-        return readUnsignedInt(input);
-    }
-
-    private static int readUnsignedInt(DataInput input) throws IOException {
-        int value = input.readInt();
-
-        if (value < 0) {
-            throw new IllegalStateException(value + " is negative");
-        }
-
-        return value;
+        return VarInts.readUnsignedInt(input);
     }
 
     private ProtocolMarshalling() {
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/VarInts.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/VarInts.java
new file mode 100644
index 0000000..fe7ff4e
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/VarInts.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ *
+ */
+class VarInts {
+    private VarInts() {
+    }
+
+    static void writeUnsignedInt(int value, DataOutput output) throws IOException {
+        if (value < 0) {
+            throw new IllegalArgumentException(value + " is negative" );
+        }
+
+        if (value < 0xFF) {
+            output.writeByte(value);
+        } else if (value < 0xFFFF) {
+            output.writeByte(0xFF);
+            output.writeShort(value);
+        } else {
+            output.writeByte(0xFF);
+            output.writeShort(0xFFFF);
+            output.writeInt(value);
+        }
+    }
+
+    static int readUnsignedInt(DataInput input) throws IOException {
+        int first = input.readUnsignedByte();
+        if (first < 0xFF) {
+            return first;
+        }
+
+        int second = input.readUnsignedShort();
+        if (second < 0xFFFF) {
+            return second;
+        }
+
+        int third = input.readInt();
+        if (third < 0) {
+            throw new IllegalStateException(third + " is negative");
+        }
+
+        return third;
+    }
+}
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
index 625d940..4edf235 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
@@ -101,7 +101,7 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
 
     private int readType(MarshalledObject marshalled) throws IOException {
         try (var dis = new DataInputStream(new ByteArrayInputStream(marshalled.bytes()))) {
-            return dis.readInt();
+            return ProtocolMarshalling.readDescriptorOrCommandId(dis);
         }
     }
 
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/VarIntsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/VarIntsTest.java
new file mode 100644
index 0000000..d7b5100
--- /dev/null
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/VarIntsTest.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThan;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class VarIntsTest {
+    @ParameterizedTest
+    @MethodSource("intsBetween0And254")
+    void writesIntsBetween0And254As1Byte(int value) throws Exception {
+        byte[] bytes = writeToBytes(output -> VarInts.writeUnsignedInt(value, output));
+
+        assertThat(bytes.length, is(1));
+    }
+
+    private static Stream<Arguments> intsBetween0And254() {
+        return IntStream.rangeClosed(0, 254)
+                .boxed()
+                .map(Arguments::of);
+    }
+
+    private byte[] writeToBytes(StreamWriter streamWriter) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (var dos = new DataOutputStream(baos)) {
+            streamWriter.write(dos);
+        }
+        return baos.toByteArray();
+    }
+
+    @ParameterizedTest
+    @MethodSource("intsHigherThan254")
+    void writesIntsHigherThan254WithCorrectLengths(IntWriteSpec spec) throws Exception {
+        byte[] bytes = writeToBytes(output -> VarInts.writeUnsignedInt(spec.value, output));
+
+        assertThat(bytes.length, is(spec.expectedLength));
+    }
+
+    private static Stream<Arguments> intsHigherThan254() {
+        return Stream.of(
+                new IntWriteSpec(0xFF, 3),
+                new IntWriteSpec(0xFFFE, 3),
+                new IntWriteSpec(0xFFFF, 7),
+                new IntWriteSpec(Integer.MAX_VALUE, 7)
+        ).map(Arguments::of);
+    }
+
+    @ParameterizedTest
+    @MethodSource("intsBetween0And254")
+    void writesAndReadsIntsBetween0And254(int value) throws Exception {
+        byte[] bytes = writeToBytes(output -> VarInts.writeUnsignedInt(value, output));
+        int result = readIntFromBytesConsuming(bytes, VarInts::readUnsignedInt);
+
+        assertThat(result, is(value));
+    }
+
+    private int readIntFromBytesConsuming(byte[] bytes, StreamReader streamReader) throws IOException {
+        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+        try (var dos = new DataInputStream(bais)) {
+            int result = streamReader.read(dos);
+            assertThat("Stream was not fully consumed", dos.read(), is(lessThan(0)));
+            return result;
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource("intsHigherThan254")
+    void writesAndReadsIntsHigherThan254(IntWriteSpec spec) throws Exception {
+        byte[] bytes = writeToBytes(output -> VarInts.writeUnsignedInt(spec.value, output));
+        int result = readIntFromBytesConsuming(bytes, VarInts::readUnsignedInt);
+
+        assertThat(result, is(spec.value));
+    }
+
+    private interface StreamWriter {
+        void write(DataOutput output) throws IOException;
+    }
+
+    private interface StreamReader {
+        int read(DataInput input) throws IOException;
+    }
+
+    private static class IntWriteSpec {
+        private final int value;
+        private final int expectedLength;
+
+        private IntWriteSpec(int value, int expectedLength) {
+            this.value = value;
+            this.expectedLength = expectedLength;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public String toString() {
+            return Integer.toString(value);
+        }
+    }
+}

[ignite-3] 19/27: - marshal null correctly

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 369541338e36d81b1fdf0132e8f73e4b8b4f51b7
Merge: 7b4063e 67c494a
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 15:47:35 2022 +0400

    - marshal null correctly

 .../marshal/DefaultUserObjectMarshaller.java            | 17 ++++++++---------
 .../DefaultUserObjectMarshallerWithBuiltinsTest.java    | 10 ++++++++++
 2 files changed, 18 insertions(+), 9 deletions(-)

diff --cc modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
index c9828bb,64c4e17..bc2617c
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
@@@ -133,16 -126,6 +133,19 @@@ public class DefaultUserObjectMarshalle
          return !builtInNonContainerMarshallers.supports(descriptor.clazz());
      }
  
 +    private ClassDescriptor obtainOriginalDescriptor(@Nullable Object object, Class<?> declaredClass) {
-         // object class is not a subclass of the declared class for primitives
-         // for enums we don't need the specific classes at all
-         Class<?> classToQueryForOriginalDescriptor = isInstanceOfSubclass(object, declaredClass)
-                 && !(object instanceof Enum)
-                 ? object.getClass() : declaredClass;
++        if (object == null) {
++            return descriptorRegistry.getNullDescriptor();
++        }
++
++        // For primitives we need to keep the declaredClass (it differs from object.getClass()).
++        // For enums we don't need the specific classes at all.
++        Class<?> classToQueryForOriginalDescriptor = declaredClass.isPrimitive() || object instanceof Enum
++                ? declaredClass : object.getClass();
 +
 +        return getOrCreateDescriptor(classToQueryForOriginalDescriptor);
 +    }
 +
      private boolean objectIsMemberOfEnumWithAnonymousClassesForMembers(Object object, Class<?> declaredClass) {
          return declaredClass.isEnum() && object.getClass().getSuperclass() == declaredClass;
      }
@@@ -195,10 -185,10 +198,6 @@@
          }
      }
  
--    private boolean isInstanceOfSubclass(@Nullable Object object, Class<?> maybeSuperclass) {
--        return object != null && maybeSuperclass.isAssignableFrom(object.getClass());
--    }
--
      @Nullable
      private Object applyWriteReplace(Object originalObject, ClassDescriptor originalDescriptor) throws MarshalException {
          try {
diff --cc modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java
index 438dd44,68d2557..5658b5a
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java
@@@ -106,6 -105,15 +107,15 @@@ class DefaultUserObjectMarshallerWithBu
      }
  
      @Test
+     void marshalsAndUnmarshalsNullThrowable() throws Exception {
+         MarshalledObject marshalled = marshaller.marshal(null, Throwable.class);
+ 
 -        Throwable unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptorRegistry);
++        Throwable unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptors);
+ 
+         assertThat(unmarshalled, is(nullValue()));
+     }
+ 
+     @Test
      void marshalsObjectArrayUsingExactlyDescriptorsOfObjectArrayAndComponents() throws Exception {
          MarshalledObject marshalled = marshaller.marshal(new Object[]{42, "abc"});
  

[ignite-3] 21/27: - improve code readability

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit fdcd2fc6bf964d40d89a349e62d018e0f7cfe558
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 15:59:55 2022 +0400

    - improve code readability
---
 .../marshal/DefaultUserObjectMarshaller.java       | 38 ++++++++--------------
 .../serialization/marshal/MarshallingContext.java  |  3 +-
 2 files changed, 16 insertions(+), 25 deletions(-)

diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
index bc2617c..36a8c6d 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
@@ -36,7 +36,6 @@ import org.apache.ignite.internal.network.serialization.ClassDescriptor;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorFactory;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorFactoryContext;
 import org.apache.ignite.internal.network.serialization.IdIndexedDescriptors;
-import org.apache.ignite.internal.network.serialization.Null;
 import org.apache.ignite.internal.network.serialization.SerializedStreamCommands;
 import org.apache.ignite.internal.network.serialization.SpecialMethodInvocationException;
 import org.jetbrains.annotations.Nullable;
@@ -107,19 +106,19 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
 
         throwIfMarshallingNotSupported(object);
 
-        ClassDescriptor originalDescriptor = obtainOriginalDescriptor(object, declaredClass);
+        ClassDescriptor originalDescriptor = getOrCreateDescriptor(object, declaredClass);
 
-        DescribedObject writeReplaced = applyWriteReplaceIfNeeded(object, originalDescriptor);
+        DescribedObject afterReplacement = applyWriteReplaceIfNeeded(object, originalDescriptor);
 
-        if (canParticipateInCycles(writeReplaced.descriptor)) {
-            Integer maybeRefId = context.rememberAsSeen(writeReplaced.object);
+        if (canParticipateInCycles(afterReplacement.descriptor)) {
+            Integer maybeRefId = context.rememberAsSeen(afterReplacement.object);
             if (maybeRefId != null) {
                 writeReference(maybeRefId, output);
             } else {
-                marshalCycleable(writeReplaced, output, context);
+                marshalCycleable(afterReplacement, output, context);
             }
         } else {
-            marshalNonCycleable(writeReplaced, output, context);
+            marshalNonCycleable(afterReplacement, output, context);
         }
     }
 
@@ -133,19 +132,6 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
         return !builtInNonContainerMarshallers.supports(descriptor.clazz());
     }
 
-    private ClassDescriptor obtainOriginalDescriptor(@Nullable Object object, Class<?> declaredClass) {
-        if (object == null) {
-            return descriptorRegistry.getNullDescriptor();
-        }
-
-        // For primitives we need to keep the declaredClass (it differs from object.getClass()).
-        // For enums we don't need the specific classes at all.
-        Class<?> classToQueryForOriginalDescriptor = declaredClass.isPrimitive() || object instanceof Enum
-                ? declaredClass : object.getClass();
-
-        return getOrCreateDescriptor(classToQueryForOriginalDescriptor);
-    }
-
     private boolean objectIsMemberOfEnumWithAnonymousClassesForMembers(Object object, Class<?> declaredClass) {
         return declaredClass.isEnum() && object.getClass().getSuperclass() == declaredClass;
     }
@@ -194,6 +180,7 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
         if (descriptorBefore.describesSameClass(replacementDescriptor)) {
             return new DescribedObject(replacedObject, replacementDescriptor);
         } else {
+            // Let's do it again!
             return applyWriteReplaceIfNeeded(replacedObject, replacementDescriptor);
         }
     }
@@ -208,13 +195,16 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
     }
 
     private ClassDescriptor getOrCreateDescriptor(@Nullable Object object, Class<?> declaredClass) {
-        assert object != null || declaredClass == Null.class;
-
         if (object == null) {
             return descriptorRegistry.getNullDescriptor();
         }
 
-        return getOrCreateDescriptor(object.getClass());
+        // For primitives, we need to keep the declaredClass (it differs from object.getClass()).
+        // For enums, we don't need the specific classes at all.
+        Class<?> classToQueryForOriginalDescriptor = declaredClass.isPrimitive() || object instanceof Enum
+                ? declaredClass : object.getClass();
+
+        return getOrCreateDescriptor(classToQueryForOriginalDescriptor);
     }
 
     private ClassDescriptor getOrCreateDescriptor(Class<?> objectClass) {
@@ -375,7 +365,7 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
         }
     }
 
-    private Object[] preInstantiateGenericRefArray(DataInput input) throws IOException {
+    private Object[] preInstantiateGenericRefArray(DataInput input) throws IOException, UnmarshalException {
         return builtInContainerMarshallers.preInstantiateGenericRefArray(input);
     }
 
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshallingContext.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshallingContext.java
index 30683f9..7287d0e 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshallingContext.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshallingContext.java
@@ -53,7 +53,8 @@ class MarshallingContext {
     }
 
     /**
-     * If the object was already seen before, its ID is returned; otherwise, it's memorized as seen with a fresh ID.
+     * If the object was already seen before, its ID is returned; otherwise, it's memorized as seen with a fresh ID
+     * and {@code null} is returned.
      *
      * @param object object to operate upon
      * @return object ID if it was seen earlier or {@code null} if the object is new

[ignite-3] 20/27: - use correct exception

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit f8f28989bbca9ca3d90f00c015ad5e3e398eb4ae
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 15:57:56 2022 +0400

    - use correct exception
---
 .../serialization/marshal/BuiltInContainerMarshallers.java   |  2 +-
 .../network/serialization/marshal/BuiltInMarshalling.java    | 12 ++++++------
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java
index 184b0f6..1918984 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java
@@ -77,7 +77,7 @@ class BuiltInContainerMarshallers {
         writeCollection(Arrays.asList(array), arrayDescriptor, output, context);
     }
 
-    <T> T[] preInstantiateGenericRefArray(DataInput input) throws IOException {
+    <T> T[] preInstantiateGenericRefArray(DataInput input) throws IOException, UnmarshalException {
         return BuiltInMarshalling.preInstantiateGenericRefArray(input);
     }
 
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java
index 05fe05a..0d48982 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java
@@ -249,24 +249,24 @@ class BuiltInMarshalling {
         output.writeUTF(object.name());
     }
 
-    static <T extends Enum<T>> Enum<?> readEnum(DataInput input) throws IOException {
+    static <T extends Enum<T>> Enum<?> readEnum(DataInput input) throws IOException, UnmarshalException {
         String enumClassName = input.readUTF();
         Class<T> enumClass = enumClass(enumClassName);
         return Enum.valueOf(enumClass, input.readUTF());
     }
 
-    private static <T extends Enum<T>> Class<T> enumClass(String className) throws IOException {
+    private static <T extends Enum<T>> Class<T> enumClass(String className) throws UnmarshalException {
         return classByName(className, "enum");
     }
 
     @NotNull
-    private static <T> Class<T> classByName(String className, String classKind) throws IOException {
+    private static <T> Class<T> classByName(String className, String classKind) throws UnmarshalException {
         try {
             // TODO: what classloader to use?
             @SuppressWarnings("unchecked") Class<T> castedClass = (Class<T>) Class.forName(className);
             return castedClass;
         } catch (ClassNotFoundException e) {
-            throw new IOException("Can not load " + classKind + " class: " + className, e);
+            throw new UnmarshalException("Can not load " + classKind + " class: " + className, e);
         }
     }
 
@@ -296,13 +296,13 @@ class BuiltInMarshalling {
     }
 
     @SuppressWarnings("unchecked")
-    private static <T> IntFunction<T[]> readTypeAndCreateArrayFactory(DataInput input) throws IOException {
+    private static <T> IntFunction<T[]> readTypeAndCreateArrayFactory(DataInput input) throws IOException, UnmarshalException {
         String componentClassName = input.readUTF();
         Class<T> componentType = classByName(componentClassName, "component");
         return len -> (T[]) Array.newInstance(componentType, len);
     }
 
-    static <T> T[] preInstantiateGenericRefArray(DataInput input) throws IOException {
+    static <T> T[] preInstantiateGenericRefArray(DataInput input) throws IOException, UnmarshalException {
         IntFunction<T[]> arrayFactory = readTypeAndCreateArrayFactory(input);
         int length = input.readInt();
         return arrayFactory.apply(length);

[ignite-3] 04/27: - improve javadoc

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 54f04afa2264cca818beb19ff9a9dd47b3ede7fc
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Mon Jan 10 19:03:08 2022 +0400

    - improve javadoc
---
 .../network/serialization/marshal/DefaultUserObjectMarshaller.java | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
index 3a16ff7..82c3973 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
@@ -74,6 +74,13 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
         externalizableMarshaller = new ExternalizableMarshaller(this::unmarshalFromInput, this::marshalToOutput, arbitraryObjectMarshaller);
     }
 
+    /**
+     * Marshals an object detecting its type from the value.
+     *
+     * @param object object to marshal
+     * @return marshalled representation
+     * @throws MarshalException if marshalling fails
+     */
     public MarshalledObject marshal(@Nullable Object object) throws MarshalException {
         return marshal(object, objectClass(object));
     }

[ignite-3] 27/27: IGNITE-16250 Raft, SQL and TX use marshallable and network messages

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit d2637e72dd6b8d64b23687c3297cb8a52802442d
Author: Semyon Danilov <sa...@yandex.ru>
AuthorDate: Mon Jan 10 19:34:19 2022 +0300

    IGNITE-16250 Raft, SQL and TX use marshallable and network messages
---
 .../client/ItMetaStorageRaftGroupTest.java         |   8 +-
 .../client/ItMetaStorageServiceTest.java           |  11 ++-
 .../serialization/MessageReaderMethodResolver.java |   4 +-
 .../network/netty/ItConnectionManagerTest.java     |   3 +-
 .../network/recovery/ItRecoveryHandshakeTest.java  |   6 +-
 .../network/scalecube/ItNodeRestartsTest.java      |   6 +-
 .../scalecube/ItScaleCubeNetworkMessagingTest.java |  35 ++-----
 .../ignite/utils/ClusterServiceTestUtils.java      |   8 +-
 .../stream/DirectByteBufferStreamImplV1.java       |  33 +++++--
 .../internal/network/message/ScaleCubeMessage.java |   8 +-
 .../serialization/ClassDescriptorFactory.java      |   2 +-
 .../serialization/CompositeIndexedDescriptors.java |  45 +++++++++
 .../PerSessionSerializationService.java            | 110 ++++++++++++++-------
 .../serialization/SerializationService.java        |  95 +++++++++++++-----
 .../UserObjectSerializationContext.java            |  53 ++++++++++
 .../UserObjectSerializationException.java}         |  33 +++----
 .../serialization/UserObjectSerializer.java        |  48 ---------
 .../scalecube/ScaleCubeClusterServiceFactory.java  |  12 ++-
 .../ScaleCubeDirectMarshallerTransport.java        |  32 +++---
 .../internal/network/netty/InboundDecoderTest.java |   8 +-
 .../internal/network/netty/NettyServerTest.java    |   3 +-
 .../network/serialization/MarshallableTest.java    |  62 +++++-------
 .../apache/ignite/internal/raft/ItLozaTest.java    |  11 ++-
 .../service/ItAbstractListenerSnapshotTest.java    |  11 ++-
 .../ignite/raft/jraft/core/ItCliServiceTest.java   |   8 +-
 .../apache/ignite/raft/jraft/core/ItNodeTest.java  |  14 ++-
 .../ignite/raft/server/RaftServerAbstractTest.java |  11 ++-
 .../ignite/raft/jraft/core/CliServiceImpl.java     |   2 +-
 .../raft/jraft/entity/LocalFileMetaOutter.java     |   6 +-
 .../raft/jraft/entity/LocalStorageOutter.java      |  12 ++-
 .../ignite/raft/jraft/entity/RaftOutter.java       |  26 ++---
 .../ignite/raft/jraft/rpc/ActionRequest.java       |   5 +-
 .../ignite/raft/jraft/rpc/ActionResponse.java      |   5 +-
 .../apache/ignite/raft/jraft/rpc/CliRequests.java  |  70 ++++++-------
 .../apache/ignite/raft/jraft/rpc/RpcRequests.java  |  37 ++++---
 .../raft/jraft/rpc/impl/RaftGroupServiceImpl.java  |  44 ++++-----
 .../apache/ignite/raft/jraft/core/TestCluster.java |   8 +-
 .../ignite/raft/jraft/rpc/AbstractRpcTest.java     |   8 +-
 .../ignite/raft/jraft/rpc/IgniteRpcTest.java       |  16 ++-
 .../ItDistributedConfigurationPropertiesTest.java  |  10 +-
 .../ItDistributedConfigurationStorageTest.java     |  10 +-
 .../org/apache/ignite/internal/app/IgniteImpl.java |  10 +-
 .../internal/sql/engine/message/ErrorMessage.java  |   4 +-
 .../sql/engine/message/QueryBatchMessage.java      |   4 +-
 .../sql/engine/message/QueryStartRequest.java      |   5 +-
 .../sql/engine/message/QueryStartResponse.java     |   4 +-
 .../distributed/ItInternalTableScanTest.java       |  13 ++-
 .../ignite/distributed/ItTablePersistenceTest.java |   5 +
 .../distributed/ItTxDistributedTestSingleNode.java |  13 ++-
 .../internal/tx/message/TxFinishRequest.java       |   5 +-
 .../internal/tx/message/TxFinishResponse.java      |   2 +-
 51 files changed, 642 insertions(+), 362 deletions(-)

diff --git a/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ItMetaStorageRaftGroupTest.java b/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ItMetaStorageRaftGroupTest.java
index 3f3a386..1ff978f 100644
--- a/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ItMetaStorageRaftGroupTest.java
+++ b/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ItMetaStorageRaftGroupTest.java
@@ -50,12 +50,14 @@ import org.apache.ignite.internal.util.IgniteUtils;
 import org.apache.ignite.lang.ByteArray;
 import org.apache.ignite.lang.IgniteLogger;
 import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.MessageSerializationRegistryImpl;
 import org.apache.ignite.network.NetworkAddress;
 import org.apache.ignite.network.StaticNodeFinder;
 import org.apache.ignite.network.scalecube.TestScaleCubeClusterServiceFactory;
 import org.apache.ignite.raft.client.Peer;
 import org.apache.ignite.raft.client.service.RaftGroupService;
 import org.apache.ignite.raft.jraft.RaftMessagesFactory;
+import org.apache.ignite.raft.jraft.RaftMessagesSerializationRegistryInitializer;
 import org.apache.ignite.raft.jraft.Status;
 import org.apache.ignite.raft.jraft.core.Replicator;
 import org.apache.ignite.raft.jraft.entity.PeerId;
@@ -172,13 +174,17 @@ public class ItMetaStorageRaftGroupTest {
 
         var nodeFinder = new StaticNodeFinder(localAddresses);
 
+        MessageSerializationRegistryImpl registry = new MessageSerializationRegistryImpl();
+        RaftMessagesSerializationRegistryInitializer.registerFactories(registry);
+
         localAddresses.stream()
                 .map(
                         addr -> ClusterServiceTestUtils.clusterService(
                                 testInfo,
                                 addr.port(),
                                 nodeFinder,
-                                NETWORK_FACTORY
+                                NETWORK_FACTORY,
+                                registry
                         )
                 )
                 .forEach(clusterService -> {
diff --git a/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ItMetaStorageServiceTest.java b/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ItMetaStorageServiceTest.java
index 7d9244e..e00081e 100644
--- a/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ItMetaStorageServiceTest.java
+++ b/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ItMetaStorageServiceTest.java
@@ -65,12 +65,14 @@ import org.apache.ignite.lang.ByteArray;
 import org.apache.ignite.lang.IgniteLogger;
 import org.apache.ignite.lang.IgniteUuid;
 import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.MessageSerializationRegistryImpl;
 import org.apache.ignite.network.NetworkAddress;
 import org.apache.ignite.network.StaticNodeFinder;
 import org.apache.ignite.network.scalecube.TestScaleCubeClusterServiceFactory;
 import org.apache.ignite.raft.client.Peer;
 import org.apache.ignite.raft.client.service.RaftGroupService;
 import org.apache.ignite.raft.jraft.RaftMessagesFactory;
+import org.apache.ignite.raft.jraft.RaftMessagesSerializationRegistryInitializer;
 import org.apache.ignite.raft.jraft.rpc.impl.RaftGroupServiceImpl;
 import org.apache.ignite.utils.ClusterServiceTestUtils;
 import org.jetbrains.annotations.NotNull;
@@ -107,6 +109,12 @@ public class ItMetaStorageServiceTest {
     /** Network factory. */
     private static final TestScaleCubeClusterServiceFactory NETWORK_FACTORY = new TestScaleCubeClusterServiceFactory();
 
+    private static final MessageSerializationRegistryImpl SERIALIZATION_REGISTRY = new MessageSerializationRegistryImpl();
+
+    static {
+        RaftMessagesSerializationRegistryInitializer.registerFactories(SERIALIZATION_REGISTRY);
+    }
+
     /** Expected server result entry. */
     private static final org.apache.ignite.internal.metastorage.server.Entry EXPECTED_SRV_RESULT_ENTRY =
             new org.apache.ignite.internal.metastorage.server.Entry(
@@ -209,7 +217,8 @@ public class ItMetaStorageServiceTest {
                                 testInfo,
                                 addr.port(),
                                 nodeFinder,
-                                NETWORK_FACTORY
+                                NETWORK_FACTORY,
+                                SERIALIZATION_REGISTRY
                         )
                 )
                 .forEach(clusterService -> {
diff --git a/modules/network-annotation-processor/src/main/java/org/apache/ignite/internal/network/processor/serialization/MessageReaderMethodResolver.java b/modules/network-annotation-processor/src/main/java/org/apache/ignite/internal/network/processor/serialization/MessageReaderMethodResolver.java
index 99e0dc2..13e9ae2 100644
--- a/modules/network-annotation-processor/src/main/java/org/apache/ignite/internal/network/processor/serialization/MessageReaderMethodResolver.java
+++ b/modules/network-annotation-processor/src/main/java/org/apache/ignite/internal/network/processor/serialization/MessageReaderMethodResolver.java
@@ -59,14 +59,14 @@ class MessageReaderMethodResolver {
 
         String parameterName = getter.getSimpleName().toString();
 
-        String methodName = methodNameResolver.resolveBaseMethodName(parameterType);
-
         if (getter.getAnnotation(Marshallable.class) != null) {
             return CodeBlock.builder()
                     .add("readMarshallable($S)", parameterName)
                     .build();
         }
 
+        String methodName = methodNameResolver.resolveBaseMethodName(parameterType);
+
         switch (methodName) {
             case "ObjectArray":
                 return resolveReadObjectArray((ArrayType) parameterType, parameterName);
diff --git a/modules/network/src/integrationTest/java/org/apache/ignite/internal/network/netty/ItConnectionManagerTest.java b/modules/network/src/integrationTest/java/org/apache/ignite/internal/network/netty/ItConnectionManagerTest.java
index 8229dbd..bff3ab5 100644
--- a/modules/network/src/integrationTest/java/org/apache/ignite/internal/network/netty/ItConnectionManagerTest.java
+++ b/modules/network/src/integrationTest/java/org/apache/ignite/internal/network/netty/ItConnectionManagerTest.java
@@ -48,6 +48,7 @@ import org.apache.ignite.internal.network.NetworkMessagesFactory;
 import org.apache.ignite.internal.network.recovery.RecoveryClientHandshakeManager;
 import org.apache.ignite.internal.network.recovery.RecoveryServerHandshakeManager;
 import org.apache.ignite.internal.network.serialization.SerializationService;
+import org.apache.ignite.internal.network.serialization.UserObjectSerializationContext;
 import org.apache.ignite.lang.IgniteBiTuple;
 import org.apache.ignite.lang.IgniteInternalException;
 import org.apache.ignite.network.NettyBootstrapFactory;
@@ -349,7 +350,7 @@ public class ItConnectionManagerTest {
 
         var manager = new ConnectionManager(
                 cfg,
-                new SerializationService(registry, null),
+                new SerializationService(registry, mock(UserObjectSerializationContext.class)),
                 consistentId,
                 () -> new RecoveryServerHandshakeManager(launchId, consistentId, messageFactory),
                 () -> new RecoveryClientHandshakeManager(launchId, consistentId, messageFactory),
diff --git a/modules/network/src/integrationTest/java/org/apache/ignite/internal/network/recovery/ItRecoveryHandshakeTest.java b/modules/network/src/integrationTest/java/org/apache/ignite/internal/network/recovery/ItRecoveryHandshakeTest.java
index 16d846c..d4ffcee 100644
--- a/modules/network/src/integrationTest/java/org/apache/ignite/internal/network/recovery/ItRecoveryHandshakeTest.java
+++ b/modules/network/src/integrationTest/java/org/apache/ignite/internal/network/recovery/ItRecoveryHandshakeTest.java
@@ -31,6 +31,7 @@ import static org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCo
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
 
 import io.netty.channel.Channel;
 import java.util.ArrayList;
@@ -48,6 +49,7 @@ import org.apache.ignite.internal.network.handshake.HandshakeAction;
 import org.apache.ignite.internal.network.netty.ConnectionManager;
 import org.apache.ignite.internal.network.netty.NettySender;
 import org.apache.ignite.internal.network.serialization.SerializationService;
+import org.apache.ignite.internal.network.serialization.UserObjectSerializationContext;
 import org.apache.ignite.network.NettyBootstrapFactory;
 import org.apache.ignite.network.NetworkMessage;
 import org.apache.ignite.network.TestMessageSerializationRegistryImpl;
@@ -400,7 +402,7 @@ public class ItRecoveryHandshakeTest {
             ClientStageFail clientHandshakeFailAt
     ) {
         var registry = new TestMessageSerializationRegistryImpl();
-        var serializationService = new SerializationService(registry, null);
+        var serializationService = new SerializationService(registry, mock(UserObjectSerializationContext.class));
 
         var messageFactory = new NetworkMessagesFactory();
 
@@ -439,7 +441,7 @@ public class ItRecoveryHandshakeTest {
      */
     private ConnectionManager startManager(int port) {
         var registry = new TestMessageSerializationRegistryImpl();
-        var serializationService = new SerializationService(registry, null);
+        var serializationService = new SerializationService(registry, mock(UserObjectSerializationContext.class));
 
         var messageFactory = new NetworkMessagesFactory();
 
diff --git a/modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/ItNodeRestartsTest.java b/modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/ItNodeRestartsTest.java
index 7e3a668..62d9fc7 100644
--- a/modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/ItNodeRestartsTest.java
+++ b/modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/ItNodeRestartsTest.java
@@ -25,6 +25,7 @@ import java.util.List;
 import java.util.stream.Collectors;
 import org.apache.ignite.lang.IgniteLogger;
 import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.MessageSerializationRegistryImpl;
 import org.apache.ignite.network.NetworkAddress;
 import org.apache.ignite.network.NodeFinder;
 import org.apache.ignite.network.StaticNodeFinder;
@@ -43,6 +44,8 @@ class ItNodeRestartsTest {
     /** Network factory. */
     private final TestScaleCubeClusterServiceFactory networkFactory = new TestScaleCubeClusterServiceFactory();
 
+    private final MessageSerializationRegistryImpl serializationRegistry = new MessageSerializationRegistryImpl();
+
     /** Created {@link ClusterService}s. Needed for resource management. */
     private List<ClusterService> services;
 
@@ -112,7 +115,8 @@ class ItNodeRestartsTest {
                 testInfo,
                 addr.port(),
                 nodeFinder,
-                networkFactory
+                networkFactory,
+                serializationRegistry
         );
 
         clusterService.start();
diff --git a/modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/ItScaleCubeNetworkMessagingTest.java b/modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/ItScaleCubeNetworkMessagingTest.java
index bd2c4f8..749b8b0 100644
--- a/modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/ItScaleCubeNetworkMessagingTest.java
+++ b/modules/network/src/integrationTest/java/org/apache/ignite/network/scalecube/ItScaleCubeNetworkMessagingTest.java
@@ -29,7 +29,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import io.scalecube.cluster.ClusterImpl;
 import io.scalecube.cluster.transport.api.Transport;
-import java.io.Serializable;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.Collection;
@@ -43,6 +42,8 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 import org.apache.ignite.internal.network.NetworkMessageTypes;
+import org.apache.ignite.internal.network.NetworkMessagesFactory;
+import org.apache.ignite.internal.network.message.ScaleCubeMessage;
 import org.apache.ignite.lang.NodeStoppingException;
 import org.apache.ignite.network.ClusterNode;
 import org.apache.ignite.network.ClusterService;
@@ -51,10 +52,10 @@ import org.apache.ignite.network.NetworkMessage;
 import org.apache.ignite.network.NodeFinder;
 import org.apache.ignite.network.StaticNodeFinder;
 import org.apache.ignite.network.TestMessage;
+import org.apache.ignite.network.TestMessageSerializationRegistryImpl;
 import org.apache.ignite.network.TestMessageTypes;
 import org.apache.ignite.network.TestMessagesFactory;
 import org.apache.ignite.network.TopologyEventHandler;
-import org.apache.ignite.network.annotations.MessageGroup;
 import org.apache.ignite.utils.ClusterServiceTestUtils;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
@@ -264,29 +265,6 @@ class ItScaleCubeNetworkMessagingTest {
     }
 
     /**
-     * Serializable message that belongs to the {@link NetworkMessageTypes} message group.
-     */
-    private static class MockNetworkMessage implements NetworkMessage, Serializable {
-        /** {@inheritDoc} */
-        @Override
-        public short messageType() {
-            return 666;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public short groupType() {
-            return NetworkMessageTypes.class.getAnnotation(MessageGroup.class).groupType();
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public boolean equals(Object obj) {
-            return getClass() == obj.getClass();
-        }
-    }
-
-    /**
      * Tests that messages from different message groups can be delivered to different sets of handlers.
      *
      * @throws Exception in case of errors.
@@ -322,7 +300,7 @@ class ItScaleCubeNetworkMessagingTest {
 
         var testMessage = messageFactory.testMessage().msg("foo").build();
 
-        var networkMessage = new MockNetworkMessage();
+        ScaleCubeMessage networkMessage = new NetworkMessagesFactory().scaleCubeMessage().build();
 
         // test that a message gets delivered to both handlers
         node2.messagingService()
@@ -420,6 +398,8 @@ class ItScaleCubeNetworkMessagingTest {
         /** Network factory. */
         private final TestScaleCubeClusterServiceFactory networkFactory = new TestScaleCubeClusterServiceFactory();
 
+        private final TestMessageSerializationRegistryImpl serializationRegistry = new TestMessageSerializationRegistryImpl();
+
         /** Members of the cluster. */
         final List<ClusterService> members;
 
@@ -466,7 +446,8 @@ class ItScaleCubeNetworkMessagingTest {
                     testInfo,
                     addr.port(),
                     nodeFinder,
-                    networkFactory
+                    networkFactory,
+                    serializationRegistry
             );
 
             if (initial) {
diff --git a/modules/network/src/integrationTest/java/org/apache/ignite/utils/ClusterServiceTestUtils.java b/modules/network/src/integrationTest/java/org/apache/ignite/utils/ClusterServiceTestUtils.java
index 2bf246c..d58618b 100644
--- a/modules/network/src/integrationTest/java/org/apache/ignite/utils/ClusterServiceTestUtils.java
+++ b/modules/network/src/integrationTest/java/org/apache/ignite/utils/ClusterServiceTestUtils.java
@@ -31,7 +31,6 @@ import org.apache.ignite.internal.configuration.ConfigurationManager;
 import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
 import org.apache.ignite.network.ClusterLocalConfiguration;
 import org.apache.ignite.network.ClusterService;
-import org.apache.ignite.network.MessageSerializationRegistryImpl;
 import org.apache.ignite.network.MessagingService;
 import org.apache.ignite.network.NettyBootstrapFactory;
 import org.apache.ignite.network.NetworkAddress;
@@ -39,6 +38,7 @@ import org.apache.ignite.network.NodeFinder;
 import org.apache.ignite.network.StaticNodeFinder;
 import org.apache.ignite.network.TopologyService;
 import org.apache.ignite.network.scalecube.TestScaleCubeClusterServiceFactory;
+import org.apache.ignite.network.serialization.MessageSerializationRegistry;
 import org.junit.jupiter.api.TestInfo;
 
 /**
@@ -59,10 +59,10 @@ public class ClusterServiceTestUtils {
             TestInfo testInfo,
             int port,
             NodeFinder nodeFinder,
-            TestScaleCubeClusterServiceFactory clusterSvcFactory
+            TestScaleCubeClusterServiceFactory clusterSvcFactory,
+            MessageSerializationRegistry registry
     ) {
-        var messageSerializationRegistry = new MessageSerializationRegistryImpl();
-        var ctx = new ClusterLocalConfiguration(testNodeName(testInfo, port), messageSerializationRegistry);
+        var ctx = new ClusterLocalConfiguration(testNodeName(testInfo, port), registry);
 
         ConfigurationManager nodeConfigurationMgr = new ConfigurationManager(
                 Collections.singleton(NetworkConfiguration.KEY),
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/direct/stream/DirectByteBufferStreamImplV1.java b/modules/network/src/main/java/org/apache/ignite/internal/network/direct/stream/DirectByteBufferStreamImplV1.java
index 0122280..e84a474 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/direct/stream/DirectByteBufferStreamImplV1.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/direct/stream/DirectByteBufferStreamImplV1.java
@@ -46,7 +46,7 @@ import java.util.RandomAccess;
 import java.util.UUID;
 import org.apache.ignite.internal.network.message.ClassDescriptorMessage;
 import org.apache.ignite.internal.network.serialization.PerSessionSerializationService;
-import org.apache.ignite.internal.network.serialization.SerializationResult;
+import org.apache.ignite.internal.network.serialization.marshal.MarshalledObject;
 import org.apache.ignite.internal.util.ArrayFactory;
 import org.apache.ignite.internal.util.GridUnsafe;
 import org.apache.ignite.internal.util.IgniteUtils;
@@ -1365,13 +1365,22 @@ public class DirectByteBufferStreamImplV1 implements DirectByteBufferStream {
     public <T> void writeMarshallable(T object, MessageWriter writer) {
         switch (marshallableState) {
             case 0:
+                writeBoolean(object == null);
+
+                if (!lastFinished || object == null) {
+                    return;
+                }
+
+                marshallableState++;
+
+                //noinspection fallthrough
+            case 1:
                 if (marshallable == null) {
                     // If object was not serialized to a byte array, serialize it
-                    SerializationResult res = serializationService.writeMarshallable(object);
-                    List<Integer> descriptorIds = res.ids();
-                    marshallable = res.array();
+                    MarshalledObject res = serializationService.writeMarshallable(object);
+                    marshallable = res.bytes();
                     // Get descriptors that were not previously sent to the remote node
-                    descriptors = serializationService.createClassDescriptorsMessages(descriptorIds);
+                    descriptors = serializationService.createClassDescriptorsMessages(res.usedDescriptors());
                 }
 
                 writeCollection(descriptors, MessageCollectionItemType.MSG, writer);
@@ -1383,7 +1392,7 @@ public class DirectByteBufferStreamImplV1 implements DirectByteBufferStream {
                 marshallableState++;
 
                 //noinspection fallthrough
-            case 1:
+            case 2:
                 writeByteArray(marshallable);
 
                 if (!lastFinished) {
@@ -1405,6 +1414,16 @@ public class DirectByteBufferStreamImplV1 implements DirectByteBufferStream {
     public <T> T readMarshallable(MessageReader reader) {
         switch (marshallableState) {
             case 0:
+                boolean isNull = readBoolean();
+
+                if (!lastFinished || isNull) {
+                    return null;
+                }
+
+                marshallableState++;
+
+                //noinspection fallthrough
+            case 1:
                 descriptors = readCollection(MessageCollectionItemType.MSG, reader);
 
                 if (!lastFinished) {
@@ -1414,7 +1433,7 @@ public class DirectByteBufferStreamImplV1 implements DirectByteBufferStream {
                 marshallableState++;
 
                 //noinspection fallthrough
-            case 1:
+            case 2:
                 marshallable = readByteArray();
 
                 if (!lastFinished) {
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/message/ScaleCubeMessage.java b/modules/network/src/main/java/org/apache/ignite/internal/network/message/ScaleCubeMessage.java
index 3369684..33fdbe6 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/message/ScaleCubeMessage.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/message/ScaleCubeMessage.java
@@ -21,15 +21,19 @@ import io.scalecube.cluster.transport.api.Message;
 import java.util.Map;
 import org.apache.ignite.internal.network.NetworkMessageTypes;
 import org.apache.ignite.network.NetworkMessage;
+import org.apache.ignite.network.annotations.Marshallable;
 import org.apache.ignite.network.annotations.Transferable;
 
 /**
- * Wrapper for ScaleCube's {@link Message}. {@link Message#data} is stored in {@link #array} and {@link Message#headers} are stored in
+ * Wrapper for ScaleCube's {@link Message}. {@link Message#data} is stored in {@link #data} and {@link Message#headers} are stored in
  * {@link #headers}.
  */
 @Transferable(NetworkMessageTypes.SCALE_CUBE_MESSAGE)
 public interface ScaleCubeMessage extends NetworkMessage {
-    byte[] array();
+    @Marshallable
+    Object data();
+
+    NetworkMessage message();
 
     Map<String, String> headers();
 }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactory.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactory.java
index 6d45f34..0649739 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactory.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactory.java
@@ -150,7 +150,7 @@ public class ClassDescriptorFactory {
     private ClassDescriptor superClassDescriptor(Class<?> clazz) {
         Class<?> superclass = clazz.getSuperclass();
 
-        if (superclass == null || superclass == Object.class) {
+        if (superclass == null || superclass == Object.class || superclass == Enum.class) {
             return null;
         }
 
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/CompositeIndexedDescriptors.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/CompositeIndexedDescriptors.java
new file mode 100644
index 0000000..ddeb86e
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/CompositeIndexedDescriptors.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization;
+
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Descriptor provider that uses {@link ClassDescriptorFactoryContext} for built-in descriptor ids and
+ * delegates to another {@link IdIndexedDescriptors} for other ids.
+ */
+public class CompositeIndexedDescriptors implements IdIndexedDescriptors {
+    private final IdIndexedDescriptors descriptors;
+    private final ClassDescriptorFactoryContext ctx;
+
+    public CompositeIndexedDescriptors(IdIndexedDescriptors descriptors,
+            ClassDescriptorFactoryContext ctx) {
+        this.descriptors = descriptors;
+        this.ctx = ctx;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public @Nullable ClassDescriptor getDescriptor(int descriptorId) {
+        if (ClassDescriptorFactoryContext.shouldBeBuiltIn(descriptorId)) {
+            return ctx.getDescriptor(descriptorId);
+        }
+
+        return descriptors.getDescriptor(descriptorId);
+    }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/PerSessionSerializationService.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/PerSessionSerializationService.java
index fb92bd0..f57c013 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/PerSessionSerializationService.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/PerSessionSerializationService.java
@@ -22,11 +22,13 @@ import static java.util.stream.Collectors.toList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import org.apache.ignite.internal.network.NetworkMessagesFactory;
 import org.apache.ignite.internal.network.message.ClassDescriptorMessage;
 import org.apache.ignite.internal.network.message.FieldDescriptorMessage;
+import org.apache.ignite.internal.network.serialization.marshal.MarshalledObject;
 import org.apache.ignite.network.NetworkMessage;
 import org.apache.ignite.network.serialization.MessageDeserializer;
 import org.apache.ignite.network.serialization.MessageSerializer;
@@ -60,8 +62,25 @@ public class PerSessionSerializationService {
      */
     private final Map<Integer, ClassDescriptor> descriptorMapView = Collections.unmodifiableMap(mergedDescriptorMap);
 
+    /**
+     * A collection of the descriptors that were sent to the remote node.
+     */
+    private final Set<Integer> sentDescriptors = ConcurrentHashMap.newKeySet();
+
+    /**
+     * Descriptors provider.
+     */
+    private final CompositeIndexedDescriptors descriptors;
+
+    /**
+     * Constructor.
+     *
+     * @param serializationService Serialization service.
+     */
     public PerSessionSerializationService(@NotNull SerializationService serializationService) {
         this.serializationService = serializationService;
+        this.descriptors = new CompositeIndexedDescriptors(new MapBackedIdIndexedDescriptors(descriptorMapView),
+                serializationService.getDescriptorRegistry());
     }
 
     /**
@@ -85,59 +104,76 @@ public class PerSessionSerializationService {
     /**
      * Serializes a marshallable object to a byte array.
      *
+     * @param marshallable Marshallable object to serialize.
+     * @param <T> Object's type.
+     * @throws UserObjectSerializationException If failed to serialize an object.
      * @see SerializationService#writeMarshallable(Object)
      */
-    public <T> SerializationResult writeMarshallable(T marshallable) {
+    public <T> MarshalledObject writeMarshallable(T marshallable)
+            throws UserObjectSerializationException {
         return serializationService.writeMarshallable(marshallable);
     }
 
     /**
      * Deserializes a marshallable object from a byte array.
      *
+     * @param missingDescriptors Descriptors that were received from the remote node.
+     * @param array Byte array that contains a serialized object.
+     * @param <T> Object's type.
+     * @throws UserObjectSerializationException If failed to deserialize an object.
      * @see SerializationService#readMarshallable(Map, byte[])
      */
-    public <T> T readMarshallable(List<ClassDescriptorMessage> missingDescriptors, byte[] marshallableData) {
+    public <T> T readMarshallable(List<ClassDescriptorMessage> missingDescriptors, byte[] array)
+            throws UserObjectSerializationException {
         mergeDescriptors(missingDescriptors);
 
-        return serializationService.readMarshallable(descriptorMapView, marshallableData);
+        return serializationService.readMarshallable(descriptors, array);
     }
 
     /**
      * Creates a list of messages holding class descriptors.
      *
-     * @param descriptorIds Ids of class descriptors.
+     * @param descriptors Class descriptors.
      * @return List of class descriptor network messages.
      */
-    public List<ClassDescriptorMessage> createClassDescriptorsMessages(List<Integer> descriptorIds) {
-        return descriptorIds.stream().map(id -> {
-            ClassDescriptor descriptor = serializationService.getClassDescriptor(id);
-
-            List<FieldDescriptorMessage> fields = descriptor.fields().stream()
-                    .map(d -> {
-                        return MSG_FACTORY.fieldDescriptorMessage()
-                                .name(d.name())
-                                .typeDescriptorId(d.typeDescriptorId())
-                                .className(d.clazz().getName())
-                                .build();
-                    })
-                    .collect(toList());
-
-            Serialization serialization = descriptor.serialization();
-
-            return MSG_FACTORY.classDescriptorMessage()
-                    .fields(fields)
-                    .isFinal(descriptor.isFinal())
-                    .serializationType(serialization.type().value())
-                    .hasSerializationOverride(serialization.hasSerializationOverride())
-                    .hasReadObjectNoData(serialization.hasReadObjectNoData())
-                    .hasWriteReplace(serialization.hasWriteReplace())
-                    .hasReadResolve(serialization.hasReadResolve())
-                    .descriptorId(descriptor.descriptorId())
-                    .className(descriptor.className())
-                    .superClassDescriptorId(superClassDescriptorIdForMessage(descriptor))
-                    .superClassName(descriptor.superClassName())
-                    .build();
-        }).collect(toList());
+    @Nullable
+    public List<ClassDescriptorMessage> createClassDescriptorsMessages(Set<ClassDescriptor> descriptors) {
+        List<ClassDescriptorMessage> messages = descriptors.stream()
+                .filter(descriptor -> {
+                    int descriptorId = descriptor.descriptorId();
+                    return !sentDescriptors.contains(descriptorId) && !serializationService.shouldBeBuiltIn(descriptorId);
+                })
+                .map(descriptor -> {
+                    List<FieldDescriptorMessage> fields = descriptor.fields().stream()
+                            .map(d -> {
+                                return MSG_FACTORY.fieldDescriptorMessage()
+                                    .name(d.name())
+                                    .typeDescriptorId(d.typeDescriptorId())
+                                    .className(d.clazz().getName())
+                                    .build();
+                            })
+                            .collect(toList());
+
+                    Serialization serialization = descriptor.serialization();
+
+                    return MSG_FACTORY.classDescriptorMessage()
+                            .fields(fields)
+                            .isFinal(descriptor.isFinal())
+                            .serializationType(serialization.type().value())
+                            .hasSerializationOverride(serialization.hasSerializationOverride())
+                            .hasReadObjectNoData(serialization.hasReadObjectNoData())
+                            .hasWriteReplace(serialization.hasWriteReplace())
+                            .hasReadResolve(serialization.hasReadResolve())
+                            .descriptorId(descriptor.descriptorId())
+                            .className(descriptor.className())
+                            .superClassDescriptorId(superClassDescriptorIdForMessage(descriptor))
+                            .superClassName(descriptor.superClassName())
+                            .build();
+                }).collect(toList());
+
+        messages.forEach(classDescriptorMessage -> sentDescriptors.add(classDescriptorMessage.descriptorId()));
+
+        return messages;
     }
 
     private int superClassDescriptorIdForMessage(ClassDescriptor descriptor) {
@@ -177,7 +213,7 @@ public class PerSessionSerializationService {
      */
     @NotNull
     private ClassDescriptor messageToMergedClassDescriptor(ClassDescriptorMessage clsMsg) {
-        ClassDescriptor localDescriptor = serializationService.getClassDescriptor(clsMsg.className());
+        ClassDescriptor localDescriptor = serializationService.getOrCreateDescriptor(clsMsg.className());
 
         List<FieldDescriptor> remoteFields = clsMsg.fields().stream()
                 .map(fieldMsg -> fieldDescriptorFromMessage(fieldMsg, localDescriptor.clazz()))
@@ -224,9 +260,9 @@ public class PerSessionSerializationService {
 
     private ClassDescriptor getClassDescriptor(int descriptorId, String typeName) {
         if (serializationService.shouldBeBuiltIn(descriptorId)) {
-            return serializationService.getClassDescriptor(descriptorId);
+            return serializationService.getDescriptor(descriptorId);
         } else {
-            return serializationService.getClassDescriptor(typeName);
+            return serializationService.getOrCreateDescriptor(typeName);
         }
     }
 
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SerializationService.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SerializationService.java
index 3f56725..045feee 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SerializationService.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SerializationService.java
@@ -17,7 +17,10 @@
 
 package org.apache.ignite.internal.network.serialization;
 
-import java.util.Map;
+import org.apache.ignite.internal.network.serialization.marshal.MarshalException;
+import org.apache.ignite.internal.network.serialization.marshal.MarshalledObject;
+import org.apache.ignite.internal.network.serialization.marshal.UnmarshalException;
+import org.apache.ignite.internal.network.serialization.marshal.UserObjectMarshaller;
 import org.apache.ignite.network.NetworkMessage;
 import org.apache.ignite.network.serialization.MessageDeserializer;
 import org.apache.ignite.network.serialization.MessageSerializationRegistry;
@@ -30,12 +33,29 @@ public class SerializationService {
     /** Message serialization registry. */
     private final MessageSerializationRegistry messageRegistry;
 
-    /** User object serializer. */
-    private final UserObjectSerializer userObjectSerializer;
+    /** Descriptor registry. */
+    private final ClassDescriptorFactoryContext descriptorRegistry;
 
-    public SerializationService(MessageSerializationRegistry messageRegistry, UserObjectSerializer userObjectSerializer) {
+    /** Descriptor factory. */
+    private final ClassDescriptorFactory descriptorFactory;
+
+    /** User object marshaller. */
+    private final UserObjectMarshaller marshaller;
+
+    /**
+     * Constructor.
+     *
+     * @param messageRegistry Message registry.
+     * @param userObjectSerializationContext User object serialization context.
+     */
+    public SerializationService(
+            MessageSerializationRegistry messageRegistry,
+            UserObjectSerializationContext userObjectSerializationContext
+    ) {
         this.messageRegistry = messageRegistry;
-        this.userObjectSerializer = userObjectSerializer;
+        this.descriptorRegistry = userObjectSerializationContext.descriptorRegistry();
+        this.descriptorFactory = userObjectSerializationContext.descriptorFactory();
+        this.marshaller = userObjectSerializationContext.marshaller();
     }
 
     /**
@@ -59,19 +79,36 @@ public class SerializationService {
     /**
      * Serializes a marshallable object to a byte array.
      *
-     * @see UserObjectSerializer#write(Object)
+     * @param object Object to serialize.
+     * @param <T> Object's type.
+     * @throws UserObjectSerializationException If failed to serialize an object.
+     * @see UserObjectMarshaller#marshal(Object, Class)
      */
-    public <T> SerializationResult writeMarshallable(T object) {
-        return userObjectSerializer.write(object);
+    public <T> MarshalledObject writeMarshallable(T object) throws UserObjectSerializationException {
+        Class<?> clazz = object.getClass();
+        try {
+            return marshaller.marshal(object, clazz);
+        } catch (MarshalException e) {
+            throw new UserObjectSerializationException("Failed to serialize object of type " + clazz.getName(), e);
+        }
     }
 
     /**
      * Deserializes a marshallable object from a byte array.
      *
-     * @see UserObjectSerializer#read(Map, byte[])
+     * @param descriptors Descriptors provider.
+     * @param array Byte array that contains a serialized object.
+     * @param <T> Object's type.
+     * @throws UserObjectSerializationException If failed to deserialize an object.
+     * @see UserObjectMarshaller#unmarshal(byte[], IdIndexedDescriptors)
      */
-    public <T> T readMarshallable(Map<Integer, ClassDescriptor> descriptor, byte[] array) {
-        return userObjectSerializer.read(descriptor, array);
+    public <T> T readMarshallable(IdIndexedDescriptors descriptors, byte[] array)
+            throws UserObjectSerializationException {
+        try {
+            return marshaller.unmarshal(array, descriptors);
+        } catch (UnmarshalException e) {
+            throw new UserObjectSerializationException("Failed to deserialize object: " + e.getMessage(), e);
+        }
     }
 
     /**
@@ -81,17 +118,7 @@ public class SerializationService {
      * @return {@code true} if descriptor belongs to the range reserved for built-in types, {@code false} otherwise.
      */
     public boolean shouldBeBuiltIn(int typeDescriptorId) {
-        return userObjectSerializer.shouldBeBuiltIn(typeDescriptorId);
-    }
-
-    /**
-     * Gets a class descriptor by the descriptor id.
-     *
-     * @param typeDescriptorId Type descriptor id.
-     * @return Class descriptor.
-     */
-    public ClassDescriptor getClassDescriptor(int typeDescriptorId) {
-        return userObjectSerializer.getClassDescriptor(typeDescriptorId);
+        return ClassDescriptorFactoryContext.shouldBeBuiltIn(typeDescriptorId);
     }
 
     /**
@@ -100,7 +127,27 @@ public class SerializationService {
      * @param typeName Class' name.
      * @return Class descriptor.
      */
-    public ClassDescriptor getClassDescriptor(String typeName) {
-        return userObjectSerializer.getClassDescriptor(typeName);
+    public ClassDescriptor getOrCreateDescriptor(String typeName) {
+        Class<?> clazz;
+        try {
+            clazz = Class.forName(typeName);
+        } catch (ClassNotFoundException e) {
+            throw new IllegalArgumentException("Class " + typeName + " is not found", e);
+        }
+
+        ClassDescriptor descriptor = descriptorRegistry.getDescriptor(clazz);
+        if (descriptor != null) {
+            return descriptor;
+        } else {
+            return descriptorFactory.create(clazz);
+        }
+    }
+
+    public ClassDescriptor getDescriptor(int descriptorId) {
+        return descriptorRegistry.getDescriptor(descriptorId);
+    }
+
+    public ClassDescriptorFactoryContext getDescriptorRegistry() {
+        return descriptorRegistry;
     }
 }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/UserObjectSerializationContext.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/UserObjectSerializationContext.java
new file mode 100644
index 0000000..cf88a76
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/UserObjectSerializationContext.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization;
+
+import org.apache.ignite.internal.network.serialization.marshal.UserObjectMarshaller;
+
+/** User object serialization objects wrapper. */
+public class UserObjectSerializationContext {
+    private final ClassDescriptorFactoryContext descriptorRegistry;
+    private final ClassDescriptorFactory descriptorFactory;
+    private final UserObjectMarshaller marshaller;
+
+    /**
+     * Constructor.
+     *
+     * @param descriptorRegistry Descriptor registry.
+     * @param descriptorFactory Descriptor factory.
+     * @param marshaller User object marshaller.
+     */
+    public UserObjectSerializationContext(ClassDescriptorFactoryContext descriptorRegistry,
+            ClassDescriptorFactory descriptorFactory, UserObjectMarshaller marshaller) {
+        this.descriptorRegistry = descriptorRegistry;
+        this.descriptorFactory = descriptorFactory;
+        this.marshaller = marshaller;
+    }
+
+    public ClassDescriptorFactoryContext descriptorRegistry() {
+        return descriptorRegistry;
+    }
+
+    public ClassDescriptorFactory descriptorFactory() {
+        return descriptorFactory;
+    }
+
+    public UserObjectMarshaller marshaller() {
+        return marshaller;
+    }
+}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/ErrorMessage.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/UserObjectSerializationException.java
similarity index 56%
copy from modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/ErrorMessage.java
copy to modules/network/src/main/java/org/apache/ignite/internal/network/serialization/UserObjectSerializationException.java
index 3eada38..862498c 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/ErrorMessage.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/UserObjectSerializationException.java
@@ -15,31 +15,22 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.sql.engine.message;
+package org.apache.ignite.internal.network.serialization;
 
-import java.io.Serializable;
-import java.util.UUID;
-import org.apache.ignite.network.NetworkMessage;
-import org.apache.ignite.network.annotations.Transferable;
+import org.apache.ignite.internal.network.serialization.marshal.UserObjectMarshaller;
 
 /**
- * ErrorMessage interface.
- * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
+ * User object serialization exception that can be thrown when {@link UserObjectMarshaller} is unable to
+ * marshall or unmarshall an object.
  */
-@Transferable(value = SqlQueryMessageGroup.ERROR_MESSAGE, autoSerializable = false)
-public interface ErrorMessage extends NetworkMessage, Serializable {
+public class UserObjectSerializationException extends RuntimeException {
     /**
-     * Get query ID.
+     * Constructor.
+     *
+     * @param message Exception message.
+     * @param cause Cause throwable.
      */
-    UUID queryId();
-
-    /**
-     * Get fragment ID.
-     */
-    long fragmentId();
-
-    /**
-     * Get error.
-     */
-    Throwable error();
+    public UserObjectSerializationException(String message, Throwable cause) {
+        super(message, cause);
+    }
 }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/UserObjectSerializer.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/UserObjectSerializer.java
deleted file mode 100644
index ffddbba..0000000
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/UserObjectSerializer.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.network.serialization;
-
-import java.util.Map;
-
-/** User object serializer. */
-public interface UserObjectSerializer {
-    /**
-     * Reads a marshallable object from a byte array using a map of descriptors.
-     *
-     * @param descriptors Descriptor map.
-     * @param array       Byte array.
-     * @param <T>         Object type.
-     * @return Unmarshalled object.
-     */
-    <T> T read(Map<Integer, ClassDescriptor> descriptors, byte[] array);
-
-    /**
-     * Marshalls object.
-     *
-     * @param object Object.
-     * @param <T> Object's type.
-     * @return {@link SerializationResult}.
-     */
-    <T> SerializationResult write(T object);
-
-    ClassDescriptor getClassDescriptor(int typeDescriptorId);
-
-    ClassDescriptor getClassDescriptor(String typeName);
-
-    boolean shouldBeBuiltIn(int typeDescriptorId);
-}
diff --git a/modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeClusterServiceFactory.java b/modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeClusterServiceFactory.java
index 3aa9a83..90a7dc0 100644
--- a/modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeClusterServiceFactory.java
+++ b/modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeClusterServiceFactory.java
@@ -34,7 +34,11 @@ import org.apache.ignite.internal.network.NetworkMessagesFactory;
 import org.apache.ignite.internal.network.netty.ConnectionManager;
 import org.apache.ignite.internal.network.recovery.RecoveryClientHandshakeManager;
 import org.apache.ignite.internal.network.recovery.RecoveryServerHandshakeManager;
+import org.apache.ignite.internal.network.serialization.ClassDescriptorFactory;
+import org.apache.ignite.internal.network.serialization.ClassDescriptorFactoryContext;
 import org.apache.ignite.internal.network.serialization.SerializationService;
+import org.apache.ignite.internal.network.serialization.UserObjectSerializationContext;
+import org.apache.ignite.internal.network.serialization.marshal.DefaultUserObjectMarshaller;
 import org.apache.ignite.network.AbstractClusterService;
 import org.apache.ignite.network.ClusterLocalConfiguration;
 import org.apache.ignite.network.ClusterService;
@@ -74,7 +78,13 @@ public class ScaleCubeClusterServiceFactory {
             public void start() {
                 String consistentId = context.getName();
 
-                var serializationService = new SerializationService(context.getSerializationRegistry(), null);
+                var userObjectDescriptorRegistry = new ClassDescriptorFactoryContext();
+                var userObjectDescriptorFactory = new ClassDescriptorFactory(userObjectDescriptorRegistry);
+                var userObjectMarshaller = new DefaultUserObjectMarshaller(userObjectDescriptorRegistry, userObjectDescriptorFactory);
+                var userObjectSerialization = new UserObjectSerializationContext(userObjectDescriptorRegistry, userObjectDescriptorFactory,
+                        userObjectMarshaller);
+
+                var serializationService = new SerializationService(context.getSerializationRegistry(), userObjectSerialization);
 
                 UUID launchId = UUID.randomUUID();
 
diff --git a/modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java b/modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
index 0268b84..16bb286 100644
--- a/modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
+++ b/modules/network/src/main/java/org/apache/ignite/network/scalecube/ScaleCubeDirectMarshallerTransport.java
@@ -20,11 +20,6 @@ package org.apache.ignite.network.scalecube;
 import io.scalecube.cluster.transport.api.Message;
 import io.scalecube.cluster.transport.api.Transport;
 import io.scalecube.net.Address;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
@@ -32,6 +27,7 @@ import java.util.Map;
 import java.util.Objects;
 import org.apache.ignite.internal.network.NetworkMessagesFactory;
 import org.apache.ignite.internal.network.message.ScaleCubeMessage;
+import org.apache.ignite.internal.network.message.ScaleCubeMessageBuilder;
 import org.apache.ignite.internal.network.netty.ConnectionManager;
 import org.apache.ignite.lang.IgniteInternalException;
 import org.apache.ignite.lang.IgniteLogger;
@@ -206,16 +202,16 @@ class ScaleCubeDirectMarshallerTransport implements Transport {
      */
     private NetworkMessage fromMessage(Message message) throws IgniteInternalException {
         Object dataObj = message.data();
-        ByteArrayOutputStream stream = new ByteArrayOutputStream();
 
-        try (ObjectOutputStream oos = new ObjectOutputStream(stream)) {
-            oos.writeObject(dataObj);
-        } catch (IOException e) {
-            throw new IgniteInternalException(e);
+        ScaleCubeMessageBuilder scaleCubeMessageBuilder = messageFactory.scaleCubeMessage();
+
+        if (dataObj instanceof NetworkMessage) {
+            scaleCubeMessageBuilder.message((NetworkMessage) dataObj);
+        } else {
+            scaleCubeMessageBuilder.data(dataObj);
         }
 
-        return messageFactory.scaleCubeMessage()
-                .array(stream.toByteArray())
+        return scaleCubeMessageBuilder
                 .headers(message.headers())
                 .build();
     }
@@ -234,15 +230,13 @@ class ScaleCubeDirectMarshallerTransport implements Transport {
 
             Map<String, String> headers = msg.headers();
 
-            Object obj;
+            Object obj = msg.data();
+
+            NetworkMessage message = msg.message();
 
-            try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(msg.array()))) {
-                obj = ois.readObject();
-            } catch (Exception e) {
-                throw new IgniteInternalException(e);
-            }
+            Object data = obj != null ? obj : message;
 
-            return Message.withHeaders(headers).data(obj).build();
+            return Message.withHeaders(headers).data(data).build();
         }
         return null;
     }
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/netty/InboundDecoderTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/netty/InboundDecoderTest.java
index 694274f..3f7dd02 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/netty/InboundDecoderTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/netty/InboundDecoderTest.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.network.netty;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
 
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.UnpooledByteBufAllocator;
@@ -38,6 +39,7 @@ import org.apache.ignite.internal.network.NestedMessageMessage;
 import org.apache.ignite.internal.network.direct.DirectMessageWriter;
 import org.apache.ignite.internal.network.serialization.PerSessionSerializationService;
 import org.apache.ignite.internal.network.serialization.SerializationService;
+import org.apache.ignite.internal.network.serialization.UserObjectSerializationContext;
 import org.apache.ignite.network.NetworkMessage;
 import org.apache.ignite.network.TestMessage;
 import org.apache.ignite.network.TestMessageSerializationRegistryImpl;
@@ -93,7 +95,7 @@ public class InboundDecoderTest {
      * Serializes and then deserializes the given message.
      */
     private <T extends NetworkMessage> T sendAndReceive(T msg) {
-        var serializationService = new SerializationService(registry, null);
+        var serializationService = new SerializationService(registry, mock(UserObjectSerializationContext.class));
         var perSessionSerializationService = new PerSessionSerializationService(serializationService);
         var channel = new EmbeddedChannel(new InboundDecoder(perSessionSerializationService));
 
@@ -131,7 +133,7 @@ public class InboundDecoderTest {
      */
     @Test
     public void testPartialHeader() throws Exception {
-        var serializationService = new SerializationService(registry, null);
+        var serializationService = new SerializationService(registry, mock(UserObjectSerializationContext.class));
         var perSessionSerializationService = new PerSessionSerializationService(serializationService);
         var channel = new EmbeddedChannel(new InboundDecoder(perSessionSerializationService));
 
@@ -164,7 +166,7 @@ public class InboundDecoderTest {
 
         Mockito.doReturn(channel).when(ctx).channel();
 
-        var serializationService = new SerializationService(registry, null);
+        var serializationService = new SerializationService(registry, mock(UserObjectSerializationContext.class));
         var perSessionSerializationService = new PerSessionSerializationService(serializationService);
         final var decoder = new InboundDecoder(perSessionSerializationService);
 
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/netty/NettyServerTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/netty/NettyServerTest.java
index c80ec5e..fbd5088 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/netty/NettyServerTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/netty/NettyServerTest.java
@@ -44,6 +44,7 @@ import org.apache.ignite.internal.configuration.testframework.InjectConfiguratio
 import org.apache.ignite.internal.network.handshake.HandshakeAction;
 import org.apache.ignite.internal.network.handshake.HandshakeManager;
 import org.apache.ignite.internal.network.serialization.SerializationService;
+import org.apache.ignite.internal.network.serialization.UserObjectSerializationContext;
 import org.apache.ignite.lang.IgniteInternalException;
 import org.apache.ignite.network.NettyBootstrapFactory;
 import org.apache.ignite.network.NetworkMessage;
@@ -183,7 +184,7 @@ public class NettyServerTest {
                 },
                 (socketAddress, message) -> {
                 },
-                new SerializationService(registry, null),
+                new SerializationService(registry, mock(UserObjectSerializationContext.class)),
                 bootstrapFactory
         );
 
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/MarshallableTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/MarshallableTest.java
index b056187..50f893c 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/MarshallableTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/MarshallableTest.java
@@ -41,11 +41,16 @@ import java.util.Map;
 import org.apache.ignite.internal.network.direct.DirectMessageWriter;
 import org.apache.ignite.internal.network.netty.ConnectionManager;
 import org.apache.ignite.internal.network.netty.InboundDecoder;
+import org.apache.ignite.internal.network.serialization.marshal.MarshalException;
+import org.apache.ignite.internal.network.serialization.marshal.MarshalledObject;
+import org.apache.ignite.internal.network.serialization.marshal.UnmarshalException;
+import org.apache.ignite.internal.network.serialization.marshal.UserObjectMarshaller;
 import org.apache.ignite.network.NetworkMessage;
 import org.apache.ignite.network.TestMessageSerializationRegistryImpl;
 import org.apache.ignite.network.TestMessagesFactory;
 import org.apache.ignite.network.serialization.MessageSerializationRegistry;
 import org.apache.ignite.network.serialization.MessageSerializer;
+import org.jetbrains.annotations.Nullable;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -151,29 +156,28 @@ public class MarshallableTest {
 
     /** Helper class that holds classes needed for serialization. */
     private class Serialization {
-        private final ClassDescriptorFactoryContext descriptorContext;
-        private final ClassDescriptorFactory factory;
-        private final ClassDescriptor descriptor;
-        private final StubSerializer userObjectSerializer;
-        private final SerializationService serializationService;
         private final PerSessionSerializationService perSessionSerializationService;
 
+        private final ClassDescriptor descriptor;
+
         Serialization() {
-            this.descriptorContext = new ClassDescriptorFactoryContext();
-            this.factory = new ClassDescriptorFactory(descriptorContext);
+            var descriptorContext = new ClassDescriptorFactoryContext();
+            var factory = new ClassDescriptorFactory(descriptorContext);
 
             // Create descriptor for SimpleSerializableObject
             this.descriptor = factory.create(SimpleSerializableObject.class);
 
-            this.userObjectSerializer = new StubSerializer(descriptorContext, descriptor);
+            var userObjectSerializer = new StubSerializer(descriptorContext, descriptor);
+
+            var ser = new UserObjectSerializationContext(descriptorContext, factory, userObjectSerializer);
 
-            this.serializationService = new SerializationService(registry, userObjectSerializer);
+            var serializationService = new SerializationService(registry, ser);
             this.perSessionSerializationService = new PerSessionSerializationService(serializationService);
         }
     }
 
     /** Stub implementation of the serializer, uses standard JDK serializable serialization to actually marshall an object. */
-    private static class StubSerializer implements UserObjectSerializer {
+    private static class StubSerializer implements UserObjectMarshaller {
 
         private final ClassDescriptorFactoryContext descriptorContext;
 
@@ -184,46 +188,24 @@ public class MarshallableTest {
             this.descriptor = descriptor;
         }
 
-        /** {@inheritDoc} */
-        @Override
-        public <T> T read(Map<Integer, ClassDescriptor> descriptor, byte[] array) {
-            try (ByteArrayInputStream bais = new ByteArrayInputStream(array); ObjectInputStream ois = new ObjectInputStream(bais)) {
-                return (T) ois.readObject();
-            } catch (Exception e) {
-                throw new RuntimeException(e);
-            }
-        }
-
-        /** {@inheritDoc} */
         @Override
-        public <T> SerializationResult write(T object) {
+        public MarshalledObject marshal(@Nullable Object object, Class<?> declaredClass) throws MarshalException {
             try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos)) {
                 oos.writeObject(object);
                 oos.close();
-                return new SerializationResult(baos.toByteArray(), Collections.singletonList(descriptor.descriptorId()));
+                return new MarshalledObject(baos.toByteArray(), Collections.singleton(descriptor));
             } catch (Exception e) {
                 throw new RuntimeException(e);
             }
         }
 
-        /** {@inheritDoc} */
         @Override
-        public ClassDescriptor getClassDescriptor(int typeDescriptorId) {
-            return descriptorContext.getDescriptor(typeDescriptorId);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public ClassDescriptor getClassDescriptor(String typeName) {
-            assertEquals(descriptor.className(), typeName);
-
-            return descriptor;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public boolean shouldBeBuiltIn(int typeDescriptorId) {
-            return ClassDescriptorFactoryContext.shouldBeBuiltIn(typeDescriptorId);
+        public <T> @Nullable T unmarshal(byte[] bytes, IdIndexedDescriptors mergedDescriptors) throws UnmarshalException {
+            try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bais)) {
+                return (T) ois.readObject();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
         }
     }
 }
diff --git a/modules/raft/src/integrationTest/java/org/apache/ignite/internal/raft/ItLozaTest.java b/modules/raft/src/integrationTest/java/org/apache/ignite/internal/raft/ItLozaTest.java
index 2c80c9c..9a21632 100644
--- a/modules/raft/src/integrationTest/java/org/apache/ignite/internal/raft/ItLozaTest.java
+++ b/modules/raft/src/integrationTest/java/org/apache/ignite/internal/raft/ItLozaTest.java
@@ -37,6 +37,7 @@ import org.apache.ignite.internal.testframework.WorkDirectory;
 import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
 import org.apache.ignite.network.ClusterNode;
 import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.MessageSerializationRegistryImpl;
 import org.apache.ignite.network.MessagingService;
 import org.apache.ignite.network.NetworkAddress;
 import org.apache.ignite.network.NetworkMessage;
@@ -44,6 +45,7 @@ import org.apache.ignite.network.StaticNodeFinder;
 import org.apache.ignite.network.scalecube.TestScaleCubeClusterServiceFactory;
 import org.apache.ignite.raft.client.service.RaftGroupListener;
 import org.apache.ignite.raft.client.service.RaftGroupService;
+import org.apache.ignite.raft.jraft.RaftMessagesSerializationRegistryInitializer;
 import org.apache.ignite.utils.ClusterServiceTestUtils;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInfo;
@@ -57,6 +59,12 @@ public class ItLozaTest {
     /** Network factory. */
     private static final TestScaleCubeClusterServiceFactory NETWORK_FACTORY = new TestScaleCubeClusterServiceFactory();
 
+    private static final MessageSerializationRegistryImpl SERIALIZATION_REGISTRY = new MessageSerializationRegistryImpl();
+
+    static {
+        RaftMessagesSerializationRegistryInitializer.registerFactories(SERIALIZATION_REGISTRY);
+    }
+
     /** Server port offset. */
     private static final int PORT = 20010;
 
@@ -87,7 +95,8 @@ public class ItLozaTest {
                 testInfo,
                 port,
                 new StaticNodeFinder(srvs),
-                NETWORK_FACTORY
+                NETWORK_FACTORY,
+                SERIALIZATION_REGISTRY
         );
 
         network.start();
diff --git a/modules/raft/src/integrationTest/java/org/apache/ignite/raft/client/service/ItAbstractListenerSnapshotTest.java b/modules/raft/src/integrationTest/java/org/apache/ignite/raft/client/service/ItAbstractListenerSnapshotTest.java
index 5bceb02..9b1993d 100644
--- a/modules/raft/src/integrationTest/java/org/apache/ignite/raft/client/service/ItAbstractListenerSnapshotTest.java
+++ b/modules/raft/src/integrationTest/java/org/apache/ignite/raft/client/service/ItAbstractListenerSnapshotTest.java
@@ -38,11 +38,13 @@ import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
 import org.apache.ignite.internal.thread.NamedThreadFactory;
 import org.apache.ignite.internal.util.IgniteUtils;
 import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.MessageSerializationRegistryImpl;
 import org.apache.ignite.network.NetworkAddress;
 import org.apache.ignite.network.StaticNodeFinder;
 import org.apache.ignite.network.scalecube.TestScaleCubeClusterServiceFactory;
 import org.apache.ignite.raft.client.Peer;
 import org.apache.ignite.raft.jraft.RaftMessagesFactory;
+import org.apache.ignite.raft.jraft.RaftMessagesSerializationRegistryInitializer;
 import org.apache.ignite.raft.jraft.rpc.impl.RaftGroupServiceImpl;
 import org.apache.ignite.utils.ClusterServiceTestUtils;
 import org.junit.jupiter.api.AfterEach;
@@ -76,6 +78,12 @@ public abstract class ItAbstractListenerSnapshotTest<T extends RaftGroupListener
     /** Factory. */
     private static final RaftMessagesFactory FACTORY = new RaftMessagesFactory();
 
+    protected static final MessageSerializationRegistryImpl SERIALIZATION_REGISTRY = new MessageSerializationRegistryImpl();
+
+    static {
+        RaftMessagesSerializationRegistryInitializer.registerFactories(SERIALIZATION_REGISTRY);
+    }
+
     /** Network factory. */
     private static final TestScaleCubeClusterServiceFactory NETWORK_FACTORY = new TestScaleCubeClusterServiceFactory();
 
@@ -349,7 +357,8 @@ public abstract class ItAbstractListenerSnapshotTest<T extends RaftGroupListener
                 testInfo,
                 port,
                 new StaticNodeFinder(List.of(otherPeer)),
-                NETWORK_FACTORY
+                NETWORK_FACTORY,
+                SERIALIZATION_REGISTRY
         );
 
         network.start();
diff --git a/modules/raft/src/integrationTest/java/org/apache/ignite/raft/jraft/core/ItCliServiceTest.java b/modules/raft/src/integrationTest/java/org/apache/ignite/raft/jraft/core/ItCliServiceTest.java
index bc9c640..7353410 100644
--- a/modules/raft/src/integrationTest/java/org/apache/ignite/raft/jraft/core/ItCliServiceTest.java
+++ b/modules/raft/src/integrationTest/java/org/apache/ignite/raft/jraft/core/ItCliServiceTest.java
@@ -45,12 +45,14 @@ import org.apache.ignite.internal.testframework.WorkDirectory;
 import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
 import org.apache.ignite.lang.IgniteLogger;
 import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.MessageSerializationRegistryImpl;
 import org.apache.ignite.network.NetworkAddress;
 import org.apache.ignite.network.StaticNodeFinder;
 import org.apache.ignite.network.scalecube.TestScaleCubeClusterServiceFactory;
 import org.apache.ignite.raft.jraft.CliService;
 import org.apache.ignite.raft.jraft.JRaftUtils;
 import org.apache.ignite.raft.jraft.Node;
+import org.apache.ignite.raft.jraft.RaftMessagesSerializationRegistryInitializer;
 import org.apache.ignite.raft.jraft.Status;
 import org.apache.ignite.raft.jraft.conf.Configuration;
 import org.apache.ignite.raft.jraft.entity.PeerId;
@@ -127,11 +129,15 @@ public class ItCliServiceTest {
                 .map(JRaftUtils::addressFromEndpoint)
                 .collect(toList());
 
+        var serializationRegistry = new MessageSerializationRegistryImpl();
+        RaftMessagesSerializationRegistryInitializer.registerFactories(serializationRegistry);
+
         ClusterService clientSvc = ClusterServiceTestUtils.clusterService(
                 testInfo,
                 TestUtils.INIT_PORT - 1,
                 new StaticNodeFinder(addressList),
-                new TestScaleCubeClusterServiceFactory()
+                new TestScaleCubeClusterServiceFactory(),
+                serializationRegistry
         );
 
         clientSvc.start();
diff --git a/modules/raft/src/integrationTest/java/org/apache/ignite/raft/jraft/core/ItNodeTest.java b/modules/raft/src/integrationTest/java/org/apache/ignite/raft/jraft/core/ItNodeTest.java
index 4cb08e7..b577252 100644
--- a/modules/raft/src/integrationTest/java/org/apache/ignite/raft/jraft/core/ItNodeTest.java
+++ b/modules/raft/src/integrationTest/java/org/apache/ignite/raft/jraft/core/ItNodeTest.java
@@ -57,6 +57,7 @@ import org.apache.ignite.internal.testframework.WorkDirectory;
 import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
 import org.apache.ignite.lang.IgniteLogger;
 import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.MessageSerializationRegistryImpl;
 import org.apache.ignite.network.NetworkAddress;
 import org.apache.ignite.network.NodeFinder;
 import org.apache.ignite.network.StaticNodeFinder;
@@ -66,6 +67,7 @@ import org.apache.ignite.raft.jraft.JRaftUtils;
 import org.apache.ignite.raft.jraft.Node;
 import org.apache.ignite.raft.jraft.NodeManager;
 import org.apache.ignite.raft.jraft.RaftGroupService;
+import org.apache.ignite.raft.jraft.RaftMessagesSerializationRegistryInitializer;
 import org.apache.ignite.raft.jraft.StateMachine;
 import org.apache.ignite.raft.jraft.Status;
 import org.apache.ignite.raft.jraft.closure.JoinableClosure;
@@ -3538,11 +3540,15 @@ public class ItNodeTest {
 
         var nodeManager = new NodeManager();
 
+        var serializationRegistry = new MessageSerializationRegistryImpl();
+        RaftMessagesSerializationRegistryInitializer.registerFactories(serializationRegistry);
+
         ClusterService clusterService = ClusterServiceTestUtils.clusterService(
                 testInfo,
                 peerId.getEndpoint().getPort(),
                 new StaticNodeFinder(addressList),
-                new TestScaleCubeClusterServiceFactory()
+                new TestScaleCubeClusterServiceFactory(),
+                serializationRegistry
         );
 
         ExecutorService requestExecutor = JRaftUtils.createRequestExecutor(nodeOptions);
@@ -3574,11 +3580,15 @@ public class ItNodeTest {
      * Creates a non-started {@link ClusterService}.
      */
     private ClusterService createClusterService(Endpoint endpoint, NodeFinder nodeFinder) {
+        var serializationRegistry = new MessageSerializationRegistryImpl();
+        RaftMessagesSerializationRegistryInitializer.registerFactories(serializationRegistry);
+
        return ClusterServiceTestUtils.clusterService(
                 testInfo,
                 endpoint.getPort(),
                 nodeFinder,
-                new TestScaleCubeClusterServiceFactory()
+                new TestScaleCubeClusterServiceFactory(),
+                serializationRegistry
         );
     }
 
diff --git a/modules/raft/src/integrationTest/java/org/apache/ignite/raft/server/RaftServerAbstractTest.java b/modules/raft/src/integrationTest/java/org/apache/ignite/raft/server/RaftServerAbstractTest.java
index 6df91cd..6226735 100644
--- a/modules/raft/src/integrationTest/java/org/apache/ignite/raft/server/RaftServerAbstractTest.java
+++ b/modules/raft/src/integrationTest/java/org/apache/ignite/raft/server/RaftServerAbstractTest.java
@@ -21,10 +21,12 @@ import java.util.ArrayList;
 import java.util.List;
 import org.apache.ignite.lang.IgniteLogger;
 import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.MessageSerializationRegistryImpl;
 import org.apache.ignite.network.NetworkAddress;
 import org.apache.ignite.network.StaticNodeFinder;
 import org.apache.ignite.network.scalecube.TestScaleCubeClusterServiceFactory;
 import org.apache.ignite.raft.jraft.RaftMessagesFactory;
+import org.apache.ignite.raft.jraft.RaftMessagesSerializationRegistryInitializer;
 import org.apache.ignite.utils.ClusterServiceTestUtils;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
@@ -38,6 +40,12 @@ abstract class RaftServerAbstractTest {
 
     protected static final RaftMessagesFactory FACTORY = new RaftMessagesFactory();
 
+    private static final MessageSerializationRegistryImpl SERIALIZATION_REGISTRY = new MessageSerializationRegistryImpl();
+
+    static {
+        RaftMessagesSerializationRegistryInitializer.registerFactories(SERIALIZATION_REGISTRY);
+    }
+
     /** Network factory. */
     protected static final TestScaleCubeClusterServiceFactory NETWORK_FACTORY = new TestScaleCubeClusterServiceFactory();
 
@@ -73,7 +81,8 @@ abstract class RaftServerAbstractTest {
                 testInfo,
                 port,
                 new StaticNodeFinder(servers),
-                NETWORK_FACTORY
+                NETWORK_FACTORY,
+                SERIALIZATION_REGISTRY
         );
 
         if (start) {
diff --git a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/core/CliServiceImpl.java b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/core/CliServiceImpl.java
index 48ba115..aaa99d9 100644
--- a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/core/CliServiceImpl.java
+++ b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/core/CliServiceImpl.java
@@ -664,7 +664,7 @@ public class CliServiceImpl implements CliService {
             if (result instanceof GetPeersResponse) {
                 final GetPeersResponse resp = (GetPeersResponse) result;
                 final List<PeerId> peerIdList = new ArrayList<>();
-                final List<String> responsePeers = returnLearners ? resp.learnersList() : resp.peersList();
+                final Collection<String> responsePeers = returnLearners ? resp.learnersList() : resp.peersList();
                 for (final String peerIdStr : responsePeers) {
                     final PeerId newPeer = new PeerId();
                     newPeer.parse(peerIdStr);
diff --git a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/entity/LocalFileMetaOutter.java b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/entity/LocalFileMetaOutter.java
index 95acc04..391b0f0 100644
--- a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/entity/LocalFileMetaOutter.java
+++ b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/entity/LocalFileMetaOutter.java
@@ -19,8 +19,9 @@
 
 package org.apache.ignite.raft.jraft.entity;
 
-import org.apache.ignite.raft.jraft.RaftMessageGroup;
+import org.apache.ignite.network.annotations.Marshallable;
 import org.apache.ignite.network.annotations.Transferable;
+import org.apache.ignite.raft.jraft.RaftMessageGroup;
 import org.apache.ignite.raft.jraft.rpc.Message;
 
 public final class LocalFileMetaOutter {
@@ -69,8 +70,9 @@ public final class LocalFileMetaOutter {
         }
     }
 
-    @Transferable(value = RaftMessageGroup.RaftOutterMessageGroup.LOCAL_FILE_META, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RaftOutterMessageGroup.LOCAL_FILE_META)
     public interface LocalFileMeta extends Message {
+        @Marshallable
         FileSource source();
 
         String checksum();
diff --git a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/entity/LocalStorageOutter.java b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/entity/LocalStorageOutter.java
index 3be9298..287cdf8 100644
--- a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/entity/LocalStorageOutter.java
+++ b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/entity/LocalStorageOutter.java
@@ -20,28 +20,32 @@
 package org.apache.ignite.raft.jraft.entity;
 
 import java.util.List;
-import org.apache.ignite.raft.jraft.RaftMessageGroup;
+import org.apache.ignite.network.annotations.Marshallable;
 import org.apache.ignite.network.annotations.Transferable;
+import org.apache.ignite.raft.jraft.RaftMessageGroup;
 import org.apache.ignite.raft.jraft.rpc.Message;
 
 public final class LocalStorageOutter {
-    @Transferable(value = RaftMessageGroup.RaftOutterMessageGroup.STABLE_PB_META, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RaftOutterMessageGroup.STABLE_PB_META)
     public interface StablePBMeta extends Message {
         long term();
 
         String votedFor();
     }
 
-    @Transferable(value = RaftMessageGroup.RaftOutterMessageGroup.LOCAL_SNAPSHOT_PB_META, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RaftOutterMessageGroup.LOCAL_SNAPSHOT_PB_META)
     public interface LocalSnapshotPbMeta extends Message {
+        @Marshallable
         RaftOutter.SnapshotMeta meta();
 
+        @Marshallable
         List<LocalStorageOutter.LocalSnapshotPbMeta.File> filesList();
 
-        @Transferable(value = RaftMessageGroup.RaftOutterMessageGroup.LOCAL_SNAPSHOT_META_FILE, autoSerializable = false)
+        @Transferable(value = RaftMessageGroup.RaftOutterMessageGroup.LOCAL_SNAPSHOT_META_FILE)
         interface File extends Message {
             String name();
 
+            @Marshallable
             LocalFileMetaOutter.LocalFileMeta meta();
         }
     }
diff --git a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/entity/RaftOutter.java b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/entity/RaftOutter.java
index 3b1abb7..a09df27 100644
--- a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/entity/RaftOutter.java
+++ b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/entity/RaftOutter.java
@@ -19,29 +19,31 @@
 
 package org.apache.ignite.raft.jraft.entity;
 
-import java.util.List;
-import org.apache.ignite.raft.jraft.RaftMessageGroup;
+import java.util.Collection;
+import org.apache.ignite.network.annotations.Marshallable;
 import org.apache.ignite.network.annotations.Transferable;
+import org.apache.ignite.raft.jraft.RaftMessageGroup;
 import org.apache.ignite.raft.jraft.rpc.Message;
 
 public final class RaftOutter {
-    @Transferable(value = RaftMessageGroup.RaftOutterMessageGroup.ENTRY_META, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RaftOutterMessageGroup.ENTRY_META)
     public interface EntryMeta extends Message {
         long term();
 
+        @Marshallable
         EnumOutter.EntryType type();
 
-        List<String> peersList();
+        Collection<String> peersList();
 
         long dataLen();
 
-        List<String> oldPeersList();
+        Collection<String> oldPeersList();
 
         long checksum();
 
-        List<String> learnersList();
+        Collection<String> learnersList();
 
-        List<String> oldLearnersList();
+        Collection<String> oldLearnersList();
 
         /**
          * @return True when the entry has a checksum, false otherwise.
@@ -49,18 +51,18 @@ public final class RaftOutter {
         boolean hasChecksum();
     }
 
-    @Transferable(value = RaftMessageGroup.RaftOutterMessageGroup.SNAPSHOT_META, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RaftOutterMessageGroup.SNAPSHOT_META)
     public interface SnapshotMeta extends Message {
         long lastIncludedIndex();
 
         long lastIncludedTerm();
 
-        List<String> peersList();
+        Collection<String> peersList();
 
-        List<String> oldPeersList();
+        Collection<String> oldPeersList();
 
-        List<String> learnersList();
+        Collection<String> learnersList();
 
-        List<String> oldLearnersList();
+        Collection<String> oldLearnersList();
     }
 }
diff --git a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/ActionRequest.java b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/ActionRequest.java
index cb7448f..9ab4b9f 100644
--- a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/ActionRequest.java
+++ b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/ActionRequest.java
@@ -17,15 +17,15 @@
 
 package org.apache.ignite.raft.jraft.rpc;
 
+import org.apache.ignite.network.annotations.Marshallable;
 import org.apache.ignite.network.annotations.Transferable;
 import org.apache.ignite.raft.client.Command;
 import org.apache.ignite.raft.jraft.RaftMessageGroup;
-import org.apache.ignite.raft.jraft.rpc.Message;
 
 /**
  * Submit an action to a replication group.
  */
-@Transferable(value = RaftMessageGroup.RpcActionMessageGroup.ACTION_REQUEST, autoSerializable = false)
+@Transferable(value = RaftMessageGroup.RpcActionMessageGroup.ACTION_REQUEST)
 public interface ActionRequest extends Message {
     /**
      * @return Group id.
@@ -35,6 +35,7 @@ public interface ActionRequest extends Message {
     /**
      * @return Action's command.
      */
+    @Marshallable
     Command command();
 
     /**
diff --git a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/ActionResponse.java b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/ActionResponse.java
index e2c1154..a066d1a 100644
--- a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/ActionResponse.java
+++ b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/ActionResponse.java
@@ -17,17 +17,18 @@
 
 package org.apache.ignite.raft.jraft.rpc;
 
+import org.apache.ignite.network.annotations.Marshallable;
 import org.apache.ignite.network.annotations.Transferable;
 import org.apache.ignite.raft.jraft.RaftMessageGroup;
-import org.apache.ignite.raft.jraft.rpc.Message;
 
 /**
  * The result of an action.
  */
-@Transferable(value = RaftMessageGroup.RpcActionMessageGroup.ACTION_RESPONSE, autoSerializable = false)
+@Transferable(value = RaftMessageGroup.RpcActionMessageGroup.ACTION_RESPONSE)
 public interface ActionResponse extends Message {
     /**
      * @return A result for this request, can be of any type.
      */
+    @Marshallable
     Object result();
 }
diff --git a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/CliRequests.java b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/CliRequests.java
index daff213..f005ed2 100644
--- a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/CliRequests.java
+++ b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/CliRequests.java
@@ -19,12 +19,12 @@
 
 package org.apache.ignite.raft.jraft.rpc;
 
-import java.util.List;
-import org.apache.ignite.raft.jraft.RaftMessageGroup;
+import java.util.Collection;
 import org.apache.ignite.network.annotations.Transferable;
+import org.apache.ignite.raft.jraft.RaftMessageGroup;
 
 public final class CliRequests {
-    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.ADD_PEER_REQUEST, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.ADD_PEER_REQUEST)
     public interface AddPeerRequest extends Message {
         String groupId();
 
@@ -33,14 +33,14 @@ public final class CliRequests {
         String peerId();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.ADD_PEER_RESPONSE, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.ADD_PEER_RESPONSE)
     public interface AddPeerResponse extends Message {
-        List<String> oldPeersList();
+        Collection<String> oldPeersList();
 
-        List<String> newPeersList();
+        Collection<String> newPeersList();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.REMOVE_PEER_REQUEST, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.REMOVE_PEER_REQUEST)
     public interface RemovePeerRequest extends Message {
         String groupId();
 
@@ -49,48 +49,48 @@ public final class CliRequests {
         String peerId();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.REMOVE_PEER_RESPONSE, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.REMOVE_PEER_RESPONSE)
     public interface RemovePeerResponse extends Message {
-        List<String> oldPeersList();
+        Collection<String> oldPeersList();
 
-        List<String> newPeersList();
+        Collection<String> newPeersList();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.CHANGE_PEERS_REQUEST, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.CHANGE_PEERS_REQUEST)
     public interface ChangePeersRequest extends Message {
         String groupId();
 
         String leaderId();
 
-        List<String> newPeersList();
+        Collection<String> newPeersList();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.CHANGE_PEERS_RESPONSE, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.CHANGE_PEERS_RESPONSE)
     public interface ChangePeersResponse extends Message {
-        List<String> oldPeersList();
+        Collection<String> oldPeersList();
 
-        List<String> newPeersList();
+        Collection<String> newPeersList();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.SNAPSHOT_REQUEST, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.SNAPSHOT_REQUEST)
     public interface SnapshotRequest extends Message {
         String groupId();
 
         String peerId();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.RESET_PEER_REQUEST, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.RESET_PEER_REQUEST)
     public interface ResetPeerRequest extends Message {
         String groupId();
 
         String peerId();
 
-        List<String> oldPeersList();
+        Collection<String> oldPeersList();
 
-        List<String> newPeersList();
+        Collection<String> newPeersList();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.TRANSFER_LEADER_REQUEST, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.TRANSFER_LEADER_REQUEST)
     public interface TransferLeaderRequest extends Message {
         String groupId();
 
@@ -99,21 +99,21 @@ public final class CliRequests {
         String peerId();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.GET_LEADER_REQUEST, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.GET_LEADER_REQUEST)
     public interface GetLeaderRequest extends Message {
         String groupId();
 
         String peerId();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.GET_LEADER_RESPONSE, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.GET_LEADER_RESPONSE)
     public interface GetLeaderResponse extends Message {
         String leaderId();
 
         long currentTerm();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.GET_PEERS_REQUEST, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.GET_PEERS_REQUEST)
     public interface GetPeersRequest extends Message {
         String groupId();
 
@@ -122,44 +122,44 @@ public final class CliRequests {
         boolean onlyAlive();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.GET_PEERS_RESPONSE, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.GET_PEERS_RESPONSE)
     public interface GetPeersResponse extends Message {
-        List<String> peersList();
+        Collection<String> peersList();
 
-        List<String> learnersList();
+        Collection<String> learnersList();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.ADD_LEARNERS_REQUEST, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.ADD_LEARNERS_REQUEST)
     public interface AddLearnersRequest extends Message {
         String groupId();
 
         String leaderId();
 
-        List<String> learnersList();
+        Collection<String> learnersList();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.REMOVE_LEARNERS_REQUEST, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.REMOVE_LEARNERS_REQUEST)
     public interface RemoveLearnersRequest extends Message {
         String groupId();
 
         String leaderId();
 
-        List<String> learnersList();
+        Collection<String> learnersList();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.RESET_LEARNERS_REQUEST, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.RESET_LEARNERS_REQUEST)
     public interface ResetLearnersRequest extends Message {
         String groupId();
 
         String leaderId();
 
-        List<String> learnersList();
+        Collection<String> learnersList();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.LEARNERS_OP_RESPONSE, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcClientMessageGroup.LEARNERS_OP_RESPONSE)
     public interface LearnersOpResponse extends Message {
-        List<String> oldLearnersList();
+        Collection<String> oldLearnersList();
 
-        List<String> newLearnersList();
+        Collection<String> newLearnersList();
     }
 }
diff --git a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/RpcRequests.java b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/RpcRequests.java
index 1324c52..e003f6c 100644
--- a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/RpcRequests.java
+++ b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/RpcRequests.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.raft.jraft.rpc;
 
 import java.util.List;
+import org.apache.ignite.network.annotations.Marshallable;
 import org.apache.ignite.network.annotations.Transferable;
 import org.apache.ignite.raft.jraft.RaftMessageGroup;
 import org.apache.ignite.raft.jraft.entity.RaftOutter;
@@ -30,7 +31,7 @@ public final class RpcRequests {
     private RpcRequests() {
     }
 
-    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.PING_REQUEST, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.PING_REQUEST)
     public interface PingRequest extends Message {
         /**
          * <code>required int64 send_timestamp = 1;</code>
@@ -38,7 +39,7 @@ public final class RpcRequests {
         long sendTimestamp();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.ERROR_RESPONSE, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.ERROR_RESPONSE)
     public interface ErrorResponse extends Message {
         /**
          * Error code.
@@ -65,15 +66,16 @@ public final class RpcRequests {
         @Nullable String leaderId();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.SM_ERROR_RESPONSE, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.SM_ERROR_RESPONSE)
     public interface SMErrorResponse extends Message {
         /**
          * @return Throwable from client's state machine logic.
          */
+        @Marshallable
         SMThrowable error();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.INSTALL_SNAPSHOT_REQUEST, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.INSTALL_SNAPSHOT_REQUEST)
     public interface InstallSnapshotRequest extends Message {
         String groupId();
 
@@ -83,19 +85,20 @@ public final class RpcRequests {
 
         long term();
 
+        @Marshallable
         RaftOutter.SnapshotMeta meta();
 
         String uri();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.INSTALL_SNAPSHOT_RESPONSE, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.INSTALL_SNAPSHOT_RESPONSE)
     public interface InstallSnapshotResponse extends Message {
         long term();
 
         boolean success();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.TIMEOUT_NOW_REQUEST, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.TIMEOUT_NOW_REQUEST)
     public interface TimeoutNowRequest extends Message {
         String groupId();
 
@@ -106,7 +109,7 @@ public final class RpcRequests {
         long term();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.TIMEOUT_NOW_RESPONSE, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.TIMEOUT_NOW_RESPONSE)
     public interface TimeoutNowResponse extends Message {
         /**
          * <code>required int64 term = 1;</code>
@@ -119,7 +122,7 @@ public final class RpcRequests {
         boolean success();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.REQUEST_VOTE_REQUEST, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.REQUEST_VOTE_REQUEST)
     public interface RequestVoteRequest extends Message {
         String groupId();
 
@@ -136,7 +139,7 @@ public final class RpcRequests {
         boolean preVote();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.REQUEST_VOTE_RESPONSE, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.REQUEST_VOTE_RESPONSE)
     public interface RequestVoteResponse extends Message {
         /**
          * <code>required int64 term = 1;</code>
@@ -149,7 +152,7 @@ public final class RpcRequests {
         boolean granted();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.APPEND_ENTRIES_REQUEST, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.APPEND_ENTRIES_REQUEST)
     public interface AppendEntriesRequest extends Message {
         String groupId();
 
@@ -163,14 +166,16 @@ public final class RpcRequests {
 
         long prevLogIndex();
 
+        @Marshallable
         List<RaftOutter.EntryMeta> entriesList();
 
         long committedIndex();
 
+        @Marshallable
         ByteString data();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.APPEND_ENTRIES_RESPONSE, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.APPEND_ENTRIES_RESPONSE)
     public interface AppendEntriesResponse extends Message {
         long term();
 
@@ -179,7 +184,7 @@ public final class RpcRequests {
         long lastLogIndex();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.GET_FILE_REQUEST, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.GET_FILE_REQUEST)
     public interface GetFileRequest extends Message {
         long readerId();
 
@@ -192,27 +197,29 @@ public final class RpcRequests {
         boolean readPartly();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.GET_FILE_RESPONSE, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.GET_FILE_RESPONSE)
     public interface GetFileResponse extends Message {
         boolean eof();
 
         long readSize();
 
+        @Marshallable
         ByteString data();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.READ_INDEX_REQUEST, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.READ_INDEX_REQUEST)
     public interface ReadIndexRequest extends Message {
         String groupId();
 
         String serverId();
 
+        @Marshallable
         List<ByteString> entriesList();
 
         String peerId();
     }
 
-    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.READ_INDEX_RESPONSE, autoSerializable = false)
+    @Transferable(value = RaftMessageGroup.RpcRequestsMessageGroup.READ_INDEX_RESPONSE)
     public interface ReadIndexResponse extends Message {
         long index();
 
diff --git a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/impl/RaftGroupServiceImpl.java b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/impl/RaftGroupServiceImpl.java
index 0711b2d..e4721c6 100644
--- a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/impl/RaftGroupServiceImpl.java
+++ b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/impl/RaftGroupServiceImpl.java
@@ -17,31 +17,11 @@
 
 package org.apache.ignite.raft.jraft.rpc.impl;
 
-import static java.lang.System.currentTimeMillis;
-import static java.util.Objects.requireNonNull;
-import static java.util.concurrent.ThreadLocalRandom.current;
-import static org.apache.ignite.raft.jraft.rpc.CliRequests.AddLearnersRequest;
-import static org.apache.ignite.raft.jraft.rpc.CliRequests.AddPeerRequest;
-import static org.apache.ignite.raft.jraft.rpc.CliRequests.AddPeerResponse;
-import static org.apache.ignite.raft.jraft.rpc.CliRequests.ChangePeersRequest;
-import static org.apache.ignite.raft.jraft.rpc.CliRequests.ChangePeersResponse;
-import static org.apache.ignite.raft.jraft.rpc.CliRequests.GetLeaderRequest;
-import static org.apache.ignite.raft.jraft.rpc.CliRequests.GetLeaderResponse;
-import static org.apache.ignite.raft.jraft.rpc.CliRequests.GetPeersRequest;
-import static org.apache.ignite.raft.jraft.rpc.CliRequests.GetPeersResponse;
-import static org.apache.ignite.raft.jraft.rpc.CliRequests.LearnersOpResponse;
-import static org.apache.ignite.raft.jraft.rpc.CliRequests.RemoveLearnersRequest;
-import static org.apache.ignite.raft.jraft.rpc.CliRequests.RemovePeerRequest;
-import static org.apache.ignite.raft.jraft.rpc.CliRequests.RemovePeerResponse;
-import static org.apache.ignite.raft.jraft.rpc.CliRequests.ResetLearnersRequest;
-import static org.apache.ignite.raft.jraft.rpc.CliRequests.SnapshotRequest;
-import static org.apache.ignite.raft.jraft.rpc.CliRequests.TransferLeaderRequest;
-
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
@@ -66,6 +46,26 @@ import org.apache.ignite.raft.jraft.rpc.ActionResponse;
 import org.apache.ignite.raft.jraft.rpc.RpcRequests;
 import org.jetbrains.annotations.NotNull;
 
+import static java.lang.System.currentTimeMillis;
+import static java.util.Objects.requireNonNull;
+import static java.util.concurrent.ThreadLocalRandom.current;
+import static org.apache.ignite.raft.jraft.rpc.CliRequests.AddLearnersRequest;
+import static org.apache.ignite.raft.jraft.rpc.CliRequests.AddPeerRequest;
+import static org.apache.ignite.raft.jraft.rpc.CliRequests.AddPeerResponse;
+import static org.apache.ignite.raft.jraft.rpc.CliRequests.ChangePeersRequest;
+import static org.apache.ignite.raft.jraft.rpc.CliRequests.ChangePeersResponse;
+import static org.apache.ignite.raft.jraft.rpc.CliRequests.GetLeaderRequest;
+import static org.apache.ignite.raft.jraft.rpc.CliRequests.GetLeaderResponse;
+import static org.apache.ignite.raft.jraft.rpc.CliRequests.GetPeersRequest;
+import static org.apache.ignite.raft.jraft.rpc.CliRequests.GetPeersResponse;
+import static org.apache.ignite.raft.jraft.rpc.CliRequests.LearnersOpResponse;
+import static org.apache.ignite.raft.jraft.rpc.CliRequests.RemoveLearnersRequest;
+import static org.apache.ignite.raft.jraft.rpc.CliRequests.RemovePeerRequest;
+import static org.apache.ignite.raft.jraft.rpc.CliRequests.RemovePeerResponse;
+import static org.apache.ignite.raft.jraft.rpc.CliRequests.ResetLearnersRequest;
+import static org.apache.ignite.raft.jraft.rpc.CliRequests.SnapshotRequest;
+import static org.apache.ignite.raft.jraft.rpc.CliRequests.TransferLeaderRequest;
+
 /**
  * The implementation of {@link RaftGroupService}
  */
@@ -655,7 +655,7 @@ public class RaftGroupServiceImpl implements RaftGroupService {
      * @param peers List of {@link PeerId} string representations.
      * @return List of {@link PeerId}
      */
-    private List<Peer> parsePeerList(List<String> peers) {
+    private List<Peer> parsePeerList(Collection<String> peers) {
         if (peers == null)
             return null;
 
diff --git a/modules/raft/src/test/java/org/apache/ignite/raft/jraft/core/TestCluster.java b/modules/raft/src/test/java/org/apache/ignite/raft/jraft/core/TestCluster.java
index 7e07c49..fc12687 100644
--- a/modules/raft/src/test/java/org/apache/ignite/raft/jraft/core/TestCluster.java
+++ b/modules/raft/src/test/java/org/apache/ignite/raft/jraft/core/TestCluster.java
@@ -41,6 +41,7 @@ import java.util.stream.Stream;
 import org.apache.ignite.internal.util.IgniteUtils;
 import org.apache.ignite.lang.IgniteLogger;
 import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.MessageSerializationRegistryImpl;
 import org.apache.ignite.network.NetworkAddress;
 import org.apache.ignite.network.StaticNodeFinder;
 import org.apache.ignite.network.scalecube.TestScaleCubeClusterServiceFactory;
@@ -49,6 +50,7 @@ import org.apache.ignite.raft.jraft.JRaftUtils;
 import org.apache.ignite.raft.jraft.Node;
 import org.apache.ignite.raft.jraft.NodeManager;
 import org.apache.ignite.raft.jraft.RaftGroupService;
+import org.apache.ignite.raft.jraft.RaftMessagesSerializationRegistryInitializer;
 import org.apache.ignite.raft.jraft.conf.Configuration;
 import org.apache.ignite.raft.jraft.entity.PeerId;
 import org.apache.ignite.raft.jraft.option.NodeOptions;
@@ -247,11 +249,15 @@ public class TestCluster {
 
             NodeManager nodeManager = new NodeManager();
 
+            var serializationRegistry = new MessageSerializationRegistryImpl();
+            RaftMessagesSerializationRegistryInitializer.registerFactories(serializationRegistry);
+
             ClusterService clusterService = ClusterServiceTestUtils.clusterService(
                     testInfo,
                     listenAddr.getPort(),
                     new StaticNodeFinder(addressList),
-                    new TestScaleCubeClusterServiceFactory()
+                    new TestScaleCubeClusterServiceFactory(),
+                    serializationRegistry
             );
 
             var rpcClient = new IgniteRpcClient(clusterService);
diff --git a/modules/raft/src/test/java/org/apache/ignite/raft/jraft/rpc/AbstractRpcTest.java b/modules/raft/src/test/java/org/apache/ignite/raft/jraft/rpc/AbstractRpcTest.java
index 5ebc4ac..42dc2b7 100644
--- a/modules/raft/src/test/java/org/apache/ignite/raft/jraft/rpc/AbstractRpcTest.java
+++ b/modules/raft/src/test/java/org/apache/ignite/raft/jraft/rpc/AbstractRpcTest.java
@@ -248,28 +248,28 @@ public abstract class AbstractRpcTest {
     }
 
     /** */
-    @Transferable(value = 0, autoSerializable = false)
+    @Transferable(value = 0)
     public static interface Request1 extends Message {
         /** */
         int val();
     }
 
     /** */
-    @Transferable(value = 1, autoSerializable = false)
+    @Transferable(value = 1)
     public static interface Request2 extends Message {
         /** */
         int val();
     }
 
     /** */
-    @Transferable(value = 2, autoSerializable = false)
+    @Transferable(value = 2)
     public static interface Response1 extends Message {
         /** */
         int val();
     }
 
     /** */
-    @Transferable(value = 3, autoSerializable = false)
+    @Transferable(value = 3)
     public static interface Response2 extends Message {
         /** */
         int val();
diff --git a/modules/raft/src/test/java/org/apache/ignite/raft/jraft/rpc/IgniteRpcTest.java b/modules/raft/src/test/java/org/apache/ignite/raft/jraft/rpc/IgniteRpcTest.java
index 1666ef7..727318e 100644
--- a/modules/raft/src/test/java/org/apache/ignite/raft/jraft/rpc/IgniteRpcTest.java
+++ b/modules/raft/src/test/java/org/apache/ignite/raft/jraft/rpc/IgniteRpcTest.java
@@ -24,10 +24,12 @@ import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.MessageSerializationRegistryImpl;
 import org.apache.ignite.network.StaticNodeFinder;
 import org.apache.ignite.network.scalecube.TestScaleCubeClusterServiceFactory;
 import org.apache.ignite.raft.jraft.JRaftUtils;
 import org.apache.ignite.raft.jraft.NodeManager;
+import org.apache.ignite.raft.jraft.RaftMessagesSerializationRegistryInitializer;
 import org.apache.ignite.raft.jraft.option.NodeOptions;
 import org.apache.ignite.raft.jraft.rpc.impl.IgniteRpcClient;
 import org.apache.ignite.raft.jraft.util.Endpoint;
@@ -64,11 +66,16 @@ public class IgniteRpcTest extends AbstractRpcTest {
 
     /** {@inheritDoc} */
     @Override public RpcServer<?> createServer(Endpoint endpoint) {
+        var serializationRegistry = new MessageSerializationRegistryImpl();
+        RaftMessagesSerializationRegistryInitializer.registerFactories(serializationRegistry);
+        TestRaftMessagesSerializationRegistryInitializer.registerFactories(serializationRegistry);
+
         ClusterService service = ClusterServiceTestUtils.clusterService(
                 testInfo,
                 endpoint.getPort(),
                 new StaticNodeFinder(Collections.emptyList()),
-                new TestScaleCubeClusterServiceFactory()
+                new TestScaleCubeClusterServiceFactory(),
+                serializationRegistry
         );
 
         NodeOptions nodeOptions = new NodeOptions();
@@ -92,11 +99,16 @@ public class IgniteRpcTest extends AbstractRpcTest {
     @Override public RpcClient createClient0() {
         int i = cntr.incrementAndGet();
 
+        var serializationRegistry = new MessageSerializationRegistryImpl();
+        RaftMessagesSerializationRegistryInitializer.registerFactories(serializationRegistry);
+        TestRaftMessagesSerializationRegistryInitializer.registerFactories(serializationRegistry);
+
         ClusterService service = ClusterServiceTestUtils.clusterService(
                 testInfo,
                 endpoint.getPort() - i,
                 new StaticNodeFinder(List.of(addressFromEndpoint(endpoint))),
-                new TestScaleCubeClusterServiceFactory()
+                new TestScaleCubeClusterServiceFactory(),
+                serializationRegistry
         );
 
         IgniteRpcClient client = new IgniteRpcClient(service) {
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java
index de684e6..d6e1786 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java
@@ -49,13 +49,16 @@ import org.apache.ignite.internal.metastorage.server.SimpleInMemoryKeyValueStora
 import org.apache.ignite.internal.raft.Loza;
 import org.apache.ignite.internal.testframework.WorkDirectory;
 import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
+import org.apache.ignite.internal.tx.message.TxMessagesSerializationRegistryInitializer;
 import org.apache.ignite.internal.util.IgniteUtils;
 import org.apache.ignite.internal.vault.VaultManager;
 import org.apache.ignite.internal.vault.inmemory.InMemoryVaultService;
 import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.MessageSerializationRegistryImpl;
 import org.apache.ignite.network.NetworkAddress;
 import org.apache.ignite.network.StaticNodeFinder;
 import org.apache.ignite.network.scalecube.TestScaleCubeClusterServiceFactory;
+import org.apache.ignite.raft.jraft.RaftMessagesSerializationRegistryInitializer;
 import org.apache.ignite.utils.ClusterServiceTestUtils;
 import org.jetbrains.annotations.NotNull;
 import org.junit.jupiter.api.AfterEach;
@@ -111,11 +114,16 @@ public class ItDistributedConfigurationPropertiesTest {
 
             vaultManager = new VaultManager(new InMemoryVaultService());
 
+            var serializationRegistry = new MessageSerializationRegistryImpl();
+            RaftMessagesSerializationRegistryInitializer.registerFactories(serializationRegistry);
+            TxMessagesSerializationRegistryInitializer.registerFactories(serializationRegistry);
+
             clusterService = ClusterServiceTestUtils.clusterService(
                     testInfo,
                     addr.port(),
                     new StaticNodeFinder(memberAddrs),
-                    new TestScaleCubeClusterServiceFactory()
+                    new TestScaleCubeClusterServiceFactory(),
+                    serializationRegistry
             );
 
             raftManager = new Loza(clusterService, workDir);
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java
index 58b4ea4..c2f0d4b 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java
@@ -44,12 +44,15 @@ import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
 import org.apache.ignite.internal.tx.LockManager;
 import org.apache.ignite.internal.tx.TxManager;
 import org.apache.ignite.internal.tx.impl.HeapLockManager;
+import org.apache.ignite.internal.tx.message.TxMessagesSerializationRegistryInitializer;
 import org.apache.ignite.internal.vault.VaultManager;
 import org.apache.ignite.internal.vault.persistence.PersistentVaultService;
 import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.MessageSerializationRegistryImpl;
 import org.apache.ignite.network.NetworkAddress;
 import org.apache.ignite.network.StaticNodeFinder;
 import org.apache.ignite.network.scalecube.TestScaleCubeClusterServiceFactory;
+import org.apache.ignite.raft.jraft.RaftMessagesSerializationRegistryInitializer;
 import org.apache.ignite.utils.ClusterServiceTestUtils;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInfo;
@@ -92,11 +95,16 @@ public class ItDistributedConfigurationStorageTest {
 
             vaultManager = new VaultManager(new PersistentVaultService(workDir.resolve("vault")));
 
+            var serializationRegistry = new MessageSerializationRegistryImpl();
+            RaftMessagesSerializationRegistryInitializer.registerFactories(serializationRegistry);
+            TxMessagesSerializationRegistryInitializer.registerFactories(serializationRegistry);
+
             clusterService = ClusterServiceTestUtils.clusterService(
                     testInfo,
                     addr.port(),
                     new StaticNodeFinder(List.of(addr)),
-                    new TestScaleCubeClusterServiceFactory()
+                    new TestScaleCubeClusterServiceFactory(),
+                    serializationRegistry
             );
 
             lockManager = new HeapLockManager();
diff --git a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
index b146534..a98bc0c 100644
--- a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
+++ b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
@@ -46,11 +46,13 @@ import org.apache.ignite.internal.metastorage.server.persistence.RocksDbKeyValue
 import org.apache.ignite.internal.raft.Loza;
 import org.apache.ignite.internal.sql.engine.QueryProcessor;
 import org.apache.ignite.internal.sql.engine.SqlQueryProcessor;
+import org.apache.ignite.internal.sql.engine.message.SqlQueryMessagesSerializationRegistryInitializer;
 import org.apache.ignite.internal.table.distributed.TableManager;
 import org.apache.ignite.internal.table.distributed.TableTxManagerImpl;
 import org.apache.ignite.internal.tx.TxManager;
 import org.apache.ignite.internal.tx.impl.HeapLockManager;
 import org.apache.ignite.internal.tx.impl.IgniteTransactionsImpl;
+import org.apache.ignite.internal.tx.message.TxMessagesSerializationRegistryInitializer;
 import org.apache.ignite.internal.vault.VaultManager;
 import org.apache.ignite.internal.vault.VaultService;
 import org.apache.ignite.internal.vault.persistence.PersistentVaultService;
@@ -63,6 +65,7 @@ import org.apache.ignite.network.ClusterService;
 import org.apache.ignite.network.MessageSerializationRegistryImpl;
 import org.apache.ignite.network.NettyBootstrapFactory;
 import org.apache.ignite.network.scalecube.ScaleCubeClusterServiceFactory;
+import org.apache.ignite.raft.jraft.RaftMessagesSerializationRegistryInitializer;
 import org.apache.ignite.rest.RestModule;
 import org.apache.ignite.table.manager.IgniteTables;
 import org.apache.ignite.tx.IgniteTransactions;
@@ -166,7 +169,12 @@ public class IgniteImpl implements Ignite {
 
         NetworkConfiguration networkConfiguration = nodeCfgMgr.configurationRegistry().getConfiguration(NetworkConfiguration.KEY);
 
-        var clusterLocalConfiguration = new ClusterLocalConfiguration(name, new MessageSerializationRegistryImpl());
+        MessageSerializationRegistryImpl serializationRegistry = new MessageSerializationRegistryImpl();
+        RaftMessagesSerializationRegistryInitializer.registerFactories(serializationRegistry);
+        SqlQueryMessagesSerializationRegistryInitializer.registerFactories(serializationRegistry);
+        TxMessagesSerializationRegistryInitializer.registerFactories(serializationRegistry);
+
+        var clusterLocalConfiguration = new ClusterLocalConfiguration(name, serializationRegistry);
 
         nettyBootstrapFactory = new NettyBootstrapFactory(networkConfiguration, clusterLocalConfiguration.getName());
 
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/ErrorMessage.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/ErrorMessage.java
index 3eada38..2694b59 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/ErrorMessage.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/ErrorMessage.java
@@ -20,13 +20,14 @@ package org.apache.ignite.internal.sql.engine.message;
 import java.io.Serializable;
 import java.util.UUID;
 import org.apache.ignite.network.NetworkMessage;
+import org.apache.ignite.network.annotations.Marshallable;
 import org.apache.ignite.network.annotations.Transferable;
 
 /**
  * ErrorMessage interface.
  * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
  */
-@Transferable(value = SqlQueryMessageGroup.ERROR_MESSAGE, autoSerializable = false)
+@Transferable(value = SqlQueryMessageGroup.ERROR_MESSAGE)
 public interface ErrorMessage extends NetworkMessage, Serializable {
     /**
      * Get query ID.
@@ -41,5 +42,6 @@ public interface ErrorMessage extends NetworkMessage, Serializable {
     /**
      * Get error.
      */
+    @Marshallable
     Throwable error();
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/QueryBatchMessage.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/QueryBatchMessage.java
index 3d120e9..c30118a 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/QueryBatchMessage.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/QueryBatchMessage.java
@@ -18,13 +18,14 @@
 package org.apache.ignite.internal.sql.engine.message;
 
 import java.util.List;
+import org.apache.ignite.network.annotations.Marshallable;
 import org.apache.ignite.network.annotations.Transferable;
 
 /**
  * QueryBatchMessage interface.
  * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
  */
-@Transferable(value = SqlQueryMessageGroup.QUERY_BATCH_MESSAGE, autoSerializable = false)
+@Transferable(value = SqlQueryMessageGroup.QUERY_BATCH_MESSAGE)
 public interface QueryBatchMessage extends ExecutionContextAwareMessage {
     /**
      * Get exchange ID.
@@ -44,5 +45,6 @@ public interface QueryBatchMessage extends ExecutionContextAwareMessage {
     /**
      * Get rows.
      */
+    @Marshallable
     List<Object> rows();
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/QueryStartRequest.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/QueryStartRequest.java
index 3de7004..5c525f2 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/QueryStartRequest.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/QueryStartRequest.java
@@ -18,12 +18,13 @@
 package org.apache.ignite.internal.sql.engine.message;
 
 import org.apache.ignite.internal.sql.engine.metadata.FragmentDescription;
+import org.apache.ignite.network.annotations.Marshallable;
 import org.apache.ignite.network.annotations.Transferable;
 
 /**
  * QueryStartRequest interface.
  */
-@Transferable(value = SqlQueryMessageGroup.QUERY_START_REQUEST, autoSerializable = false)
+@Transferable(value = SqlQueryMessageGroup.QUERY_START_REQUEST)
 public interface QueryStartRequest extends ExecutionContextAwareMessage {
     /**
      * Get schema name.
@@ -33,6 +34,7 @@ public interface QueryStartRequest extends ExecutionContextAwareMessage {
     /**
      * Get fragment description.
      */
+    @Marshallable
     FragmentDescription fragmentDescription();
 
     /**
@@ -48,5 +50,6 @@ public interface QueryStartRequest extends ExecutionContextAwareMessage {
     /**
      * Get query parameters.
      */
+    @Marshallable
     Object[] parameters();
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/QueryStartResponse.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/QueryStartResponse.java
index f245f8f..a7ce03a 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/QueryStartResponse.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/QueryStartResponse.java
@@ -20,13 +20,14 @@ package org.apache.ignite.internal.sql.engine.message;
 import java.io.Serializable;
 import java.util.UUID;
 import org.apache.ignite.network.NetworkMessage;
+import org.apache.ignite.network.annotations.Marshallable;
 import org.apache.ignite.network.annotations.Transferable;
 
 /**
  * QueryStartResponse interface.
  * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
  */
-@Transferable(value = SqlQueryMessageGroup.QUERY_START_RESPONSE, autoSerializable = false)
+@Transferable(value = SqlQueryMessageGroup.QUERY_START_RESPONSE)
 public interface QueryStartResponse extends NetworkMessage, Serializable {
     /**
      * Get query ID.
@@ -41,5 +42,6 @@ public interface QueryStartResponse extends NetworkMessage, Serializable {
     /**
      * Get error.
      */
+    @Marshallable
     Throwable error();
 }
diff --git a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItInternalTableScanTest.java b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItInternalTableScanTest.java
index 1eddc47..ebe2348 100644
--- a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItInternalTableScanTest.java
+++ b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItInternalTableScanTest.java
@@ -65,18 +65,21 @@ import org.apache.ignite.internal.tx.Timestamp;
 import org.apache.ignite.internal.tx.TxManager;
 import org.apache.ignite.internal.tx.impl.HeapLockManager;
 import org.apache.ignite.internal.tx.impl.TxManagerImpl;
+import org.apache.ignite.internal.tx.message.TxMessagesSerializationRegistryInitializer;
 import org.apache.ignite.internal.util.ByteUtils;
 import org.apache.ignite.internal.util.Cursor;
 import org.apache.ignite.internal.util.IgniteUtils;
 import org.apache.ignite.internal.util.Pair;
 import org.apache.ignite.lang.IgniteUuid;
 import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.MessageSerializationRegistryImpl;
 import org.apache.ignite.network.NetworkAddress;
 import org.apache.ignite.network.StaticNodeFinder;
 import org.apache.ignite.network.scalecube.TestScaleCubeClusterServiceFactory;
 import org.apache.ignite.raft.client.Peer;
 import org.apache.ignite.raft.client.service.RaftGroupService;
 import org.apache.ignite.raft.jraft.RaftMessagesFactory;
+import org.apache.ignite.raft.jraft.RaftMessagesSerializationRegistryInitializer;
 import org.apache.ignite.raft.jraft.rpc.impl.RaftGroupServiceImpl;
 import org.apache.ignite.utils.ClusterServiceTestUtils;
 import org.jetbrains.annotations.NotNull;
@@ -96,6 +99,13 @@ import org.mockito.junit.jupiter.MockitoExtension;
 public class ItInternalTableScanTest {
     private static final TestScaleCubeClusterServiceFactory NETWORK_FACTORY = new TestScaleCubeClusterServiceFactory();
 
+    private static final MessageSerializationRegistryImpl SERIALIZATION_REGISTRY = new MessageSerializationRegistryImpl();
+
+    static {
+        RaftMessagesSerializationRegistryInitializer.registerFactories(SERIALIZATION_REGISTRY);
+        TxMessagesSerializationRegistryInitializer.registerFactories(SERIALIZATION_REGISTRY);
+    }
+
     private static final RaftMessagesFactory FACTORY = new RaftMessagesFactory();
 
     private static final String TEST_TABLE_NAME = "testTbl";
@@ -139,7 +149,8 @@ public class ItInternalTableScanTest {
                 testInfo,
                 20_000,
                 new StaticNodeFinder(List.of(nodeNetworkAddress)),
-                NETWORK_FACTORY
+                NETWORK_FACTORY,
+                SERIALIZATION_REGISTRY
         );
 
         network.start();
diff --git a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTablePersistenceTest.java b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTablePersistenceTest.java
index 330de6c..9420cf2 100644
--- a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTablePersistenceTest.java
+++ b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTablePersistenceTest.java
@@ -45,6 +45,7 @@ import org.apache.ignite.internal.table.distributed.storage.VersionedRowStore;
 import org.apache.ignite.internal.tx.TxManager;
 import org.apache.ignite.internal.tx.impl.HeapLockManager;
 import org.apache.ignite.internal.tx.impl.TxManagerImpl;
+import org.apache.ignite.internal.tx.message.TxMessagesSerializationRegistryInitializer;
 import org.apache.ignite.lang.IgniteUuid;
 import org.apache.ignite.network.ClusterService;
 import org.apache.ignite.network.NetworkAddress;
@@ -71,6 +72,10 @@ public class ItTablePersistenceTest extends ItAbstractListenerSnapshotTest<Parti
 
     private static final Row SECOND_VALUE = createKeyValueRow(1, 1);
 
+    static {
+        TxMessagesSerializationRegistryInitializer.registerFactories(ItAbstractListenerSnapshotTest.SERIALIZATION_REGISTRY);
+    }
+
     /**
      * Paths for created partition listeners.
      */
diff --git a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTxDistributedTestSingleNode.java b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTxDistributedTestSingleNode.java
index 46c22de..d4b3512 100644
--- a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTxDistributedTestSingleNode.java
+++ b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTxDistributedTestSingleNode.java
@@ -49,11 +49,13 @@ import org.apache.ignite.internal.tx.TxManager;
 import org.apache.ignite.internal.tx.impl.HeapLockManager;
 import org.apache.ignite.internal.tx.impl.IgniteTransactionsImpl;
 import org.apache.ignite.internal.tx.impl.TxManagerImpl;
+import org.apache.ignite.internal.tx.message.TxMessagesSerializationRegistryInitializer;
 import org.apache.ignite.internal.util.IgniteUtils;
 import org.apache.ignite.lang.IgniteUuid;
 import org.apache.ignite.lang.IgniteUuidGenerator;
 import org.apache.ignite.network.ClusterNode;
 import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.MessageSerializationRegistryImpl;
 import org.apache.ignite.network.NetworkAddress;
 import org.apache.ignite.network.NodeFinder;
 import org.apache.ignite.network.StaticNodeFinder;
@@ -61,6 +63,7 @@ import org.apache.ignite.network.scalecube.TestScaleCubeClusterServiceFactory;
 import org.apache.ignite.raft.client.Peer;
 import org.apache.ignite.raft.client.service.RaftGroupService;
 import org.apache.ignite.raft.jraft.RaftMessagesFactory;
+import org.apache.ignite.raft.jraft.RaftMessagesSerializationRegistryInitializer;
 import org.apache.ignite.raft.jraft.rpc.impl.RaftGroupServiceImpl;
 import org.apache.ignite.table.Table;
 import org.apache.ignite.utils.ClusterServiceTestUtils;
@@ -79,6 +82,13 @@ public class ItTxDistributedTestSingleNode extends TxAbstractTest {
 
     private static final TestScaleCubeClusterServiceFactory NETWORK_FACTORY = new TestScaleCubeClusterServiceFactory();
 
+    private static final MessageSerializationRegistryImpl SERIALIZATION_REGISTRY = new MessageSerializationRegistryImpl();
+
+    static {
+        RaftMessagesSerializationRegistryInitializer.registerFactories(SERIALIZATION_REGISTRY);
+        TxMessagesSerializationRegistryInitializer.registerFactories(SERIALIZATION_REGISTRY);
+    }
+
     private ClusterService client;
 
     protected Map<ClusterNode, Loza> raftServers;
@@ -377,7 +387,8 @@ public class ItTxDistributedTestSingleNode extends TxAbstractTest {
                 testInfo,
                 port,
                 nodeFinder,
-                NETWORK_FACTORY
+                NETWORK_FACTORY,
+                SERIALIZATION_REGISTRY
         );
 
         network.start();
diff --git a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxFinishRequest.java b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxFinishRequest.java
index b7a2270..de3554d 100644
--- a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxFinishRequest.java
+++ b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxFinishRequest.java
@@ -21,18 +21,20 @@ import java.io.Serializable;
 import java.util.Set;
 import org.apache.ignite.internal.tx.Timestamp;
 import org.apache.ignite.network.NetworkMessage;
+import org.apache.ignite.network.annotations.Marshallable;
 import org.apache.ignite.network.annotations.Transferable;
 
 /**
  * Submit an action to a replication group.
  */
-@Transferable(value = TxMessageGroup.TX_FINISH_REQUEST, autoSerializable = false)
+@Transferable(value = TxMessageGroup.TX_FINISH_REQUEST)
 public interface TxFinishRequest extends NetworkMessage, Serializable {
     /**
      * Returns the timestamp.
      *
      * @return The timestamp.
      */
+    @Marshallable
     Timestamp timestamp();
 
     /**
@@ -47,5 +49,6 @@ public interface TxFinishRequest extends NetworkMessage, Serializable {
      *
      * @return Enlisted partition groups.
      */
+    @Marshallable
     Set<String> groups();
 }
diff --git a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxFinishResponse.java b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxFinishResponse.java
index 51c4cb3..eec2522 100644
--- a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxFinishResponse.java
+++ b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxFinishResponse.java
@@ -25,7 +25,7 @@ import org.jetbrains.annotations.Nullable;
 /**
  * The result of an action.
  */
-@Transferable(value = TxMessageGroup.TX_FINISH_RESPONSE, autoSerializable = false)
+@Transferable(value = TxMessageGroup.TX_FINISH_RESPONSE)
 public interface TxFinishResponse extends NetworkMessage, Serializable {
     /**
      * Returns the error message.

[ignite-3] 10/27: - add a test to make sure that constructors are not invoked for arbitrary objects

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit a1e83629cd95619a0341b44e2b031a197bf42ec7
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 11:49:39 2022 +0400

    - add a test to make sure that constructors are not invoked for arbitrary objects
---
 ...erObjectMarshallerWithArbitraryObjectsTest.java | 95 ++++++++++------------
 1 file changed, 45 insertions(+), 50 deletions(-)

diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
index 20f07ba..6e83d63 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
@@ -28,6 +28,7 @@ import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
 import static org.hamcrest.Matchers.sameInstance;
 import static org.hamcrest.Matchers.startsWith;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.io.ByteArrayInputStream;
@@ -42,6 +43,7 @@ import org.apache.ignite.internal.network.serialization.BuiltinType;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorFactory;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorFactoryContext;
 import org.apache.ignite.internal.network.serialization.IdIndexedDescriptors;
+import org.jetbrains.annotations.NotNull;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
@@ -55,15 +57,22 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
 
     private final DefaultUserObjectMarshaller marshaller = new DefaultUserObjectMarshaller(descriptorRegistry, descriptorFactory);
 
+    private static boolean constructorCalled;
+
     @Test
     void marshalsAndUnmarshalsSimpleClassInstances() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(new Simple(42));
-
-        Simple unmarshalled = unmarshalNonNull(marshalled);
+        Simple unmarshalled = marshalAndUnmarshalNonNull(new Simple(42));
 
         assertThat(unmarshalled.value, is(42));
     }
 
+    @NotNull
+    private <T> T marshalAndUnmarshalNonNull(Object object) throws MarshalException, UnmarshalException {
+        MarshalledObject marshalled = marshaller.marshal(object);
+        return unmarshalNonNull(marshalled);
+    }
+
+    @NotNull
     private <T> T unmarshalNonNull(MarshalledObject marshalled) throws UnmarshalException {
         T unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptors);
 
@@ -97,9 +106,7 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
 
     @Test
     void marshalsAndUnmarshalsClassInstancesInvolvingSuperclasses() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(new Child("answer", 42));
-
-        Child unmarshalled = unmarshalNonNull(marshalled);
+        Child unmarshalled = marshalAndUnmarshalNonNull(new Child("answer", 42));
 
         assertThat(unmarshalled.parentValue(), is("answer"));
         assertThat(unmarshalled.childValue(), is(42));
@@ -117,9 +124,7 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
 
     @Test
     void marshalsAndUnmarshalsClassInstancesHavingNestedArbitraryObjects() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(new WithArbitraryClassField(new Simple(42)));
-
-        WithArbitraryClassField unmarshalled = unmarshalNonNull(marshalled);
+        WithArbitraryClassField unmarshalled = marshalAndUnmarshalNonNull(new WithArbitraryClassField(new Simple(42)));
 
         assertThat(unmarshalled.nested, is(notNullValue()));
         assertThat(unmarshalled.nested.value, is(42));
@@ -127,9 +132,7 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
 
     @Test
     void marshalsAndUnmarshalsClassInstancesHavingCollectionsOfArbitraryObjects() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(withArbitraryObjectInArrayList(new Simple(42)));
-
-        WithArbitraryObjectInList unmarshalled = unmarshalNonNull(marshalled);
+        WithArbitraryObjectInList unmarshalled = marshalAndUnmarshalNonNull(withArbitraryObjectInArrayList(new Simple(42)));
 
         assertThat(unmarshalled.list, hasSize(1));
         assertThat(unmarshalled.list.get(0).value, is(42));
@@ -142,9 +145,7 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
 
     @Test
     void marshalsAndUnmarshalsClassInstancesHavingPolymorphicNestedArbitraryObjects() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(new WithArbitraryClassField(new ChildOfSimple(42)));
-
-        WithArbitraryClassField unmarshalled = unmarshalNonNull(marshalled);
+        WithArbitraryClassField unmarshalled = marshalAndUnmarshalNonNull(new WithArbitraryClassField(new ChildOfSimple(42)));
 
         assertThat(unmarshalled.nested, is(instanceOf(ChildOfSimple.class)));
         assertThat(unmarshalled.nested.value, is(42));
@@ -152,9 +153,9 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
 
     @Test
     void marshalsAndUnmarshalsClassInstancesHavingCollectionsOfPolymorphicArbitraryObjects() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(withArbitraryObjectInArrayList(new ChildOfSimple(42)));
+        WithArbitraryObjectInList object = withArbitraryObjectInArrayList(new ChildOfSimple(42));
 
-        WithArbitraryObjectInList unmarshalled = unmarshalNonNull(marshalled);
+        WithArbitraryObjectInList unmarshalled = marshalAndUnmarshalNonNull(object);
 
         assertThat(unmarshalled.list, hasSize(1));
         assertThat(unmarshalled.list.get(0), is(instanceOf(ChildOfSimple.class)));
@@ -163,36 +164,28 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
 
     @Test
     void restoresConcreteCollectionTypeCorrectlyWhenUnmarshalls() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(withArbitraryObjectInArrayList(new Simple(42)));
-
-        WithArbitraryObjectInList unmarshalled = unmarshalNonNull(marshalled);
+        WithArbitraryObjectInList unmarshalled = marshalAndUnmarshalNonNull(withArbitraryObjectInArrayList(new Simple(42)));
 
         assertThat(unmarshalled.list, is(instanceOf(ArrayList.class)));
     }
 
     @Test
     void ignoresTransientFields() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(new WithTransientFields("Hi"));
-
-        WithTransientFields unmarshalled = unmarshalNonNull(marshalled);
+        WithTransientFields unmarshalled = marshalAndUnmarshalNonNull(new WithTransientFields("Hi"));
 
         assertThat(unmarshalled.value, is(nullValue()));
     }
 
     @Test
     void supportsFinalFields() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(new WithFinalFields(42));
-
-        WithFinalFields unmarshalled = unmarshalNonNull(marshalled);
+        WithFinalFields unmarshalled = marshalAndUnmarshalNonNull(new WithFinalFields(42));
 
         assertThat(unmarshalled.value, is(42));
     }
 
     @Test
     void supportsNonCapturingAnonymousClassInstances() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(nonCapturingAnonymousInstance());
-
-        Callable<String> unmarshalled = unmarshalNonNull(marshalled);
+        Callable<String> unmarshalled = marshalAndUnmarshalNonNull(nonCapturingAnonymousInstance());
 
         assertThat(unmarshalled.call(), is("Hi!"));
     }
@@ -209,9 +202,7 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
 
     @Test
     void supportsNonCapturingLambdas() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(nonCapturingLambda());
-
-        Callable<String> unmarshalled = unmarshalNonNull(marshalled);
+        Callable<String> unmarshalled = marshalAndUnmarshalNonNull(nonCapturingLambda());
 
         assertThat(unmarshalled.call(), is("Hi!"));
     }
@@ -224,9 +215,7 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
     @Disabled("IGNITE-16165")
     // TODO: IGNITE-16165 - enable this test when we are able to work with serializable lambdas
     void supportsNonCapturingSerializableLambdas() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(nonCapturingSerializableLambda());
-
-        Callable<String> unmarshalled = unmarshalNonNull(marshalled);
+        Callable<String> unmarshalled = marshalAndUnmarshalNonNull(nonCapturingSerializableLambda());
 
         assertThat(unmarshalled.call(), is("Hi!"));
     }
@@ -299,9 +288,7 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
 
     @Test
     void supportsNonCapturingLocalClassInstances() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(nonCapturingLocalClassInstance());
-
-        Callable<String> unmarshalled = unmarshalNonNull(marshalled);
+        Callable<String> unmarshalled = marshalAndUnmarshalNonNull(nonCapturingLocalClassInstance());
 
         assertThat(unmarshalled.call(), is("Hi!"));
     }
@@ -335,18 +322,14 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
 
     @Test
     void supportsClassesWithoutNoArgConstructor() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(new WithoutNoArgConstructor(42));
-
-        WithoutNoArgConstructor unmarshalled = unmarshalNonNull(marshalled);
+        WithoutNoArgConstructor unmarshalled = marshalAndUnmarshalNonNull(new WithoutNoArgConstructor(42));
 
         assertThat(unmarshalled.value, is(42));
     }
 
     @Test
     void supportsInstancesDirectlyContainingThemselvesInFields() throws Exception {
-        MarshalledObject marshalled = marshaller.marshal(new WithInfiniteCycleViaField(42));
-
-        WithInfiniteCycleViaField unmarshalled = unmarshalNonNull(marshalled);
+        WithInfiniteCycleViaField unmarshalled = marshalAndUnmarshalNonNull(new WithInfiniteCycleViaField(42));
 
         assertThat(unmarshalled.value, is(42));
         assertThat(unmarshalled.myself, is(sameInstance(unmarshalled)));
@@ -359,9 +342,7 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
         first.part = second;
         second.part = first;
 
-        MarshalledObject marshalled = marshaller.marshal(first);
-
-        WithFirstCyclePart unmarshalled = unmarshalNonNull(marshalled);
+        WithFirstCyclePart unmarshalled = marshalAndUnmarshalNonNull(first);
 
         assertThat(unmarshalled.part.part, is(sameInstance(unmarshalled)));
     }
@@ -373,13 +354,21 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
         object.contents = container;
         container.add(object);
 
-        MarshalledObject marshalled = marshaller.marshal(object);
-
-        WithObjectList unmarshalled = unmarshalNonNull(marshalled);
+        WithObjectList unmarshalled = marshalAndUnmarshalNonNull(object);
 
         assertThat(unmarshalled.contents.get(0), is(sameInstance(unmarshalled)));
     }
 
+    @Test
+    void doesNotInvokeNoArgConstructorOfArbitraryClassOnUnmarshalling() throws Exception {
+        WithSideEffectInConstructor object = new WithSideEffectInConstructor();
+        constructorCalled = false;
+
+        marshalAndUnmarshalNonNull(object);
+
+        assertFalse(constructorCalled);
+    }
+
     private static class Simple {
         private int value;
 
@@ -514,4 +503,10 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
     private static class WithObjectList {
         private List<Object> contents;
     }
+
+    private static class WithSideEffectInConstructor implements Serializable {
+        public WithSideEffectInConstructor() {
+            constructorCalled = true;
+        }
+    }
 }

[ignite-3] 07/27: - add tests to make sure Serializable constructor is not called on unmarshalling, while first non-serializable parent constructor gets called

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit d7a3c01febdbd0e96bb0a12a73f85ea6aa899a59
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 10:47:07 2022 +0400

    - add tests to make sure Serializable constructor is not called on unmarshalling, while first non-serializable parent constructor gets called
---
 ...ltUserObjectMarshallerWithSerializableTest.java | 40 ++++++++++++++++++++++
 1 file changed, 40 insertions(+)

diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java
index 0ac491c..1947a51 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java
@@ -29,6 +29,7 @@ import static org.hamcrest.Matchers.nullValue;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -82,6 +83,9 @@ class DefaultUserObjectMarshallerWithSerializableTest {
     /** Static access to the registry (for using in parameterized tests) */
     private static ClassDescriptorFactoryContext staticDescriptorRegistry;
 
+    private static boolean nonSerializableParentConstructorCalled;
+    private static boolean constructorCalled;
+
     @BeforeEach
     void initStatics() {
         staticMarshaller = marshaller;
@@ -447,6 +451,26 @@ class DefaultUserObjectMarshallerWithSerializableTest {
         assertThat(result.childValue, is(42 + CHILD_WRITE_OBJECT_INCREMENT + CHILD_READ_OBJECT_INCREMENT));
     }
 
+    @Test
+    void invokesNoArgConstructorOfNonSerializableParentOnUnmarshalling() throws Exception {
+        SerializableWithSideEffectInParentConstructor object = new SerializableWithSideEffectInParentConstructor();
+        nonSerializableParentConstructorCalled = false;
+
+        marshalAndUnmarshalNonNull(object);
+
+        assertTrue(nonSerializableParentConstructorCalled);
+    }
+
+    @Test
+    void doesNotInvokeNoArgConstructorOfSerializableOnUnmarshalling() throws Exception {
+        SerializableWithSideEffectInConstructor object = new SerializableWithSideEffectInConstructor();
+        constructorCalled = false;
+
+        marshalAndUnmarshalNonNull(object);
+
+        assertFalse(constructorCalled);
+    }
+
     /**
      * An {@link Serializable} that does not have {@code writeReplace()}/{@code readResolve()} methods or other customizations.
      */
@@ -808,4 +832,20 @@ class DefaultUserObjectMarshallerWithSerializableTest {
             childValue = ois.readInt() + CHILD_READ_OBJECT_INCREMENT;
         }
     }
+
+    private static class NonSerializableParentWithSideEffectInConstructor {
+        protected NonSerializableParentWithSideEffectInConstructor() {
+            nonSerializableParentConstructorCalled = true;
+        }
+    }
+
+    private static class SerializableWithSideEffectInParentConstructor extends NonSerializableParentWithSideEffectInConstructor
+            implements Serializable {
+    }
+
+    private static class SerializableWithSideEffectInConstructor implements Serializable {
+        public SerializableWithSideEffectInConstructor() {
+            constructorCalled = true;
+        }
+    }
 }

[ignite-3] 26/27: - support List.of(), Set.of(), Map.of()

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit d013a48f2eb66ac4b947d7d5a86b11e40bce8357
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 21:50:20 2022 +0400

    - support List.of(), Set.of(), Map.of()
---
 .../serialization/ClassDescriptorFactory.java      |  8 +++----
 .../serialization/ClassDescriptorFactoryTest.java  | 26 ----------------------
 ...erObjectMarshallerWithArbitraryObjectsTest.java | 24 ++++++++++++++++++++
 3 files changed, 28 insertions(+), 30 deletions(-)

diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactory.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactory.java
index d8559e3..6d45f34 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactory.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactory.java
@@ -210,10 +210,10 @@ public class ClassDescriptorFactory {
         boolean hasReadObject = hasReadObject(clazz);
         boolean hasWriteObject = hasWriteObject(clazz);
 
-        if (hasWriteObject != hasReadObject) {
-            throw new IllegalArgumentException("Class must either have both writeObject() and readObject() methods or neither of them: "
-                    + clazz.getName());
-        }
+//        if (hasWriteObject != hasReadObject) {
+//            throw new IllegalArgumentException("Class must either have both writeObject() and readObject() methods or neither of them: "
+//                    + clazz.getName());
+//        }
     }
 
     private boolean hasReadResolve(Class<? extends Serializable> clazz) {
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryTest.java
index 73b651e..d9adb3d 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryTest.java
@@ -493,20 +493,6 @@ public class ClassDescriptorFactoryTest {
         assertDoesNotThrow(() -> context.getRequiredDescriptor(Parent.class));
     }
 
-    @Test
-    void failsWhenTryingToParseSerializableWithWriteObjectButWithoutReadObject() {
-        Throwable ex = assertThrows(IllegalArgumentException.class, () -> factory.create(SerializableWithWriteObjectOnly.class));
-        assertThat(ex.getMessage(), is("Class must either have both writeObject() and readObject() methods or neither of them: "
-                + SerializableWithWriteObjectOnly.class.getName()));
-    }
-
-    @Test
-    void failsWhenTryingToParseSerializableWithReadObjectButWithoutWriteObject() {
-        Throwable ex = assertThrows(IllegalArgumentException.class, () -> factory.create(SerializableWithReadObjectOnly.class));
-        assertThat(ex.getMessage(), is("Class must either have both writeObject() and readObject() methods or neither of them: "
-                + SerializableWithReadObjectOnly.class.getName()));
-    }
-
     private static class Parent {
         @SuppressWarnings("unused")
         private String value;
@@ -524,18 +510,6 @@ public class ClassDescriptorFactoryTest {
         private int apple;
     }
 
-    private static class SerializableWithWriteObjectOnly implements Serializable {
-        private void writeObject(ObjectOutputStream oos) {
-            // no-op
-        }
-    }
-
-    private static class SerializableWithReadObjectOnly implements Serializable {
-        private void readObject(ObjectInputStream ois) {
-            // no-op
-        }
-    }
-
     private static class ExtendsObject {
     }
 }
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
index 4edf235..c7abd48 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
@@ -19,6 +19,8 @@ package org.apache.ignite.internal.network.serialization.marshal;
 
 import static java.util.Collections.singletonList;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasItems;
 import static org.hamcrest.Matchers.hasSize;
@@ -37,6 +39,7 @@ import java.io.IOException;
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Callable;
 import org.apache.ignite.internal.network.serialization.BuiltinType;
@@ -370,6 +373,27 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
         assertFalse(constructorCalled);
     }
 
+    @Test
+    void supportsListOf() throws Exception {
+        List<Integer> list = marshalAndUnmarshalNonNull(List.of(1, 2, 3));
+
+        assertThat(list, contains(1, 2, 3));
+    }
+
+    @Test
+    void supportsSetOf() throws Exception {
+        Set<Integer> set = marshalAndUnmarshalNonNull(Set.of(1, 2, 3));
+
+        assertThat(set, containsInAnyOrder(1, 2, 3));
+    }
+
+    @Test
+    void supportsMapOf() throws Exception {
+        Map<?, ?> map = marshalAndUnmarshalNonNull(Map.of(1, 2, 3, 4));
+
+        assertThat(map, is(equalTo(Map.of(1, 2, 3, 4))));
+    }
+
     private static class Simple {
         private int value;
 

[ignite-3] 22/27: - improve javadocs

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 43a84a74c97f60ac101afbbc6bef95ca83665a8a
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 16:28:53 2022 +0400

    - improve javadocs
---
 .../marshal/UosObjectOutputStream.java              | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectOutputStream.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectOutputStream.java
index 3f99d41..69cd29a 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectOutputStream.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectOutputStream.java
@@ -44,76 +44,91 @@ class UosObjectOutputStream extends ObjectOutputStream {
         this.context = context;
     }
 
+    /** {@inheritDoc} */
     @Override
     public void write(int val) throws IOException {
         output.write(val);
     }
 
+    /** {@inheritDoc} */
     @Override
     public void write(byte[] buf) throws IOException {
         output.write(buf);
     }
 
+    /** {@inheritDoc} */
     @Override
     public void write(byte[] buf, int off, int len) throws IOException {
         output.write(buf, off, len);
     }
 
+    /** {@inheritDoc} */
     @Override
     public void writeByte(int val) throws IOException {
         output.writeByte(val);
     }
 
+    /** {@inheritDoc} */
     @Override
     public void writeShort(int val) throws IOException {
         output.writeShort(val);
     }
 
+    /** {@inheritDoc} */
     @Override
     public void writeInt(int val) throws IOException {
         output.writeInt(val);
     }
 
+    /** {@inheritDoc} */
     @Override
     public void writeLong(long val) throws IOException {
         output.writeLong(val);
     }
 
+    /** {@inheritDoc} */
     @Override
     public void writeFloat(float val) throws IOException {
         output.writeFloat(val);
     }
 
+    /** {@inheritDoc} */
     @Override
     public void writeDouble(double val) throws IOException {
         output.writeDouble(val);
     }
 
+    /** {@inheritDoc} */
     @Override
     public void writeChar(int val) throws IOException {
         output.writeChar(val);
     }
 
+    /** {@inheritDoc} */
     @Override
     public void writeBoolean(boolean val) throws IOException {
         output.writeBoolean(val);
     }
 
+    /** {@inheritDoc} */
     @Override
     public void writeBytes(String str) throws IOException {
         output.writeBytes(str);
     }
 
+    /** {@inheritDoc} */
     @Override
     public void writeChars(String str) throws IOException {
         output.writeChars(str);
     }
 
+    /** {@inheritDoc} */
     @Override
     public void writeUTF(String str) throws IOException {
         output.writeUTF(str);
     }
 
+    /** {@inheritDoc} */
     @Override
     protected void writeObjectOverride(Object obj) throws IOException {
         writeObject0(obj);
@@ -127,12 +142,14 @@ class UosObjectOutputStream extends ObjectOutputStream {
         }
     }
 
+    /** {@inheritDoc} */
     @Override
     public void writeUnshared(Object obj) throws IOException {
         // TODO: IGNITE-16257 - implement 'unshared' logic?
         writeObject0(obj);
     }
 
+    /** {@inheritDoc} */
     @Override
     public void defaultWriteObject() throws IOException {
         try {
@@ -147,21 +164,25 @@ class UosObjectOutputStream extends ObjectOutputStream {
         }
     }
 
+    /** {@inheritDoc} */
     @Override
     public void useProtocolVersion(int version) {
         // no op
     }
 
+    /** {@inheritDoc} */
     @Override
     public void reset() throws IOException {
         // TODO: IGNITE-16165 - erase information about references?
     }
 
+    /** {@inheritDoc} */
     @Override
     public void flush() throws IOException {
         output.flush();
     }
 
+    /** {@inheritDoc} */
     @Override
     public void close() throws IOException {
         flush();

[ignite-3] 17/27: - postpone 2 TODOs

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 595839a8c99384f738b333122e9dab93b07a6a6d
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 15:01:06 2022 +0400

    - postpone 2 TODOs
---
 .../internal/network/serialization/marshal/UosObjectInputStream.java    | 2 +-
 .../internal/network/serialization/marshal/UosObjectOutputStream.java   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectInputStream.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectInputStream.java
index e884b20..2a5ede8 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectInputStream.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectInputStream.java
@@ -138,7 +138,7 @@ class UosObjectInputStream extends ObjectInputStream {
 
     @Override
     public Object readUnshared() throws IOException {
-        // TODO: IGNITE-16165 - implement 'unshared' logic?
+        // TODO: IGNITE-16257 - implement 'unshared' logic?
         return doReadObject();
     }
 
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectOutputStream.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectOutputStream.java
index ebf12d1..3f99d41 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectOutputStream.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectOutputStream.java
@@ -129,7 +129,7 @@ class UosObjectOutputStream extends ObjectOutputStream {
 
     @Override
     public void writeUnshared(Object obj) throws IOException {
-        // TODO: IGNITE-16165 - implement 'unshared' logic?
+        // TODO: IGNITE-16257 - implement 'unshared' logic?
         writeObject0(obj);
     }
 

[ignite-3] 15/27: - remove a TODO

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 83643851dc05316914612dc1cad7f1300c5196d9
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 14:19:31 2022 +0400

    - remove a TODO
---
 .../network/serialization/marshal/SerializableInstantiationTest.java    | 2 --
 1 file changed, 2 deletions(-)

diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/SerializableInstantiationTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/SerializableInstantiationTest.java
index c0dbe03..3556ef9 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/SerializableInstantiationTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/SerializableInstantiationTest.java
@@ -55,8 +55,6 @@ class SerializableInstantiationTest {
         assertTrue(instantiation.supports(WithReadResolve.class));
     }
 
-    // TODO: IGNITE-16165 - test that it does not support instantiation of Serializable classes with writeObject()/readObject()
-
     @Test
     void instantiatesSerializableClassesWithoutNoArgConstructor() throws Exception {
         Object instance = instantiation.newInstance(SerializableWithoutNoArgConstructor.class);

[ignite-3] 16/27: - resolve a few TODOs

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit b22f02261a3b0e4d0dd87bb571f865c920ef0176
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 14:57:44 2022 +0400

    - resolve a few TODOs
---
 .../marshal/DefaultUserObjectMarshaller.java       |  4 +--
 .../marshal/UncheckedMarshalException.java         | 27 ++++++++++++++++++++
 .../marshal/UncheckedUnmarshalException.java       | 27 ++++++++++++++++++++
 .../marshal/UosObjectInputStream.java              |  6 ++---
 .../marshal/UosObjectOutputStream.java             |  6 ++---
 ...UserObjectMarshallerWithExternalizableTest.java |  8 ------
 ...shallerWithSerializableOverrideStreamsTest.java | 22 ++++++++++++++++
 .../network/serialization/marshal/IntHolder.java   | 29 ++++++++++++++++++++++
 8 files changed, 111 insertions(+), 18 deletions(-)

diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
index e1dc6a1..c9828bb 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
@@ -335,7 +335,7 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
         if (canParticipateInCycles(descriptor)) {
             readObject = readCycleable(input, context, descriptor);
         } else {
-            readObject = readObject(input, descriptor, context);
+            readObject = readNonCycleable(input, descriptor, context);
         }
 
         @SuppressWarnings("unchecked") T resolvedObject = (T) applyReadResolveIfNeeded(descriptor, readObject);
@@ -425,7 +425,7 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
     }
 
     @Nullable
-    private Object readObject(DataInputStream input, ClassDescriptor descriptor, UnmarshallingContext context)
+    private Object readNonCycleable(DataInputStream input, ClassDescriptor descriptor, UnmarshallingContext context)
             throws IOException, UnmarshalException {
         if (isBuiltInNonContainer(descriptor)) {
             return builtInNonContainerMarshallers.readBuiltIn(descriptor, input, context);
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UncheckedMarshalException.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UncheckedMarshalException.java
new file mode 100644
index 0000000..1996b5b
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UncheckedMarshalException.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+/**
+ * An unchecked variation of {@link MarshalException}.
+ */
+class UncheckedMarshalException extends RuntimeException {
+    public UncheckedMarshalException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UncheckedUnmarshalException.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UncheckedUnmarshalException.java
new file mode 100644
index 0000000..72954be
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UncheckedUnmarshalException.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+/**
+ * An unchecked variation of {@link UnmarshalException}.
+ */
+class UncheckedUnmarshalException extends RuntimeException {
+    public UncheckedUnmarshalException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectInputStream.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectInputStream.java
index 853562e..e884b20 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectInputStream.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectInputStream.java
@@ -132,8 +132,7 @@ class UosObjectInputStream extends ObjectInputStream {
         try {
             return valueReader.read(input, context);
         } catch (UnmarshalException e) {
-            // TODO: IGNITE-16165 -  pass exception correctly
-            throw new RuntimeException("Cannot read", e);
+            throw new UncheckedUnmarshalException("Cannot read object", e);
         }
     }
 
@@ -153,8 +152,7 @@ class UosObjectInputStream extends ObjectInputStream {
                     context
             );
         } catch (UnmarshalException e) {
-            // TODO: IGNITE-16165 -  pass exception correctly
-            throw new RuntimeException("Cannot read", e);
+            throw new UncheckedUnmarshalException("Cannot read fields in a default way", e);
         }
     }
 
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectOutputStream.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectOutputStream.java
index 1ce3b28..ebf12d1 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectOutputStream.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectOutputStream.java
@@ -123,8 +123,7 @@ class UosObjectOutputStream extends ObjectOutputStream {
         try {
             valueWriter.write(obj, objectClass(obj), output, context);
         } catch (MarshalException e) {
-            // TODO: IGNITE-16165 -  pass exception correctly
-            throw new RuntimeException("Cannot write", e);
+            throw new UncheckedMarshalException("Cannot write object", e);
         }
     }
 
@@ -144,8 +143,7 @@ class UosObjectOutputStream extends ObjectOutputStream {
                     context
             );
         } catch (MarshalException e) {
-            // TODO: IGNITE-16165 -  pass exception correctly
-            throw new RuntimeException("Cannot write", e);
+            throw new UncheckedMarshalException("Cannot write fields in a default way", e);
         }
     }
 
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithExternalizableTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithExternalizableTest.java
index 6f1765a..dcc2848 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithExternalizableTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithExternalizableTest.java
@@ -534,14 +534,6 @@ class DefaultUserObjectMarshallerWithExternalizableTest {
         }
     }
 
-    private static class IntHolder {
-        private final int value;
-
-        private IntHolder(int value) {
-            this.value = value;
-        }
-    }
-
     private static class WithSideEffectInConstructor implements Externalizable {
         public WithSideEffectInConstructor() {
             constructorCalled = true;
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest.java
index 2a6a4d9..e1ee1b2 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest.java
@@ -23,6 +23,7 @@ import static org.hamcrest.Matchers.lessThan;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -33,6 +34,7 @@ import java.io.InputStream;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
@@ -313,6 +315,16 @@ class DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest {
         assertThat(result.nested.value, is(100));
     }
 
+    @Test
+    void passesUnmarshalExceptionTriggeredInsideReadObjectToTheCaller() throws Exception {
+        readerAndWriter = new ReaderAndWriter<>(oos -> oos.writeObject(new ThrowingFromReadObject()), ObjectInputStream::readObject);
+
+        WithCustomizableOverride<?> original = new WithCustomizableOverride<>();
+        MarshalledObject marshalled = marshaller.marshal(original);
+
+        assertThrows(UnmarshalException.class, () -> unmarshalNonNull(marshalled));
+    }
+
     private interface ContentsWriter {
         void writeTo(ObjectOutputStream stream) throws IOException;
     }
@@ -469,4 +481,14 @@ class DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest {
             stream.defaultReadObject();
         }
     }
+
+    private static class ThrowingFromReadObject implements Serializable {
+        private void writeObject(ObjectOutputStream stream) {
+            // no-op
+        }
+
+        private void readObject(ObjectInputStream stream) {
+            throw new RuntimeException("Oops");
+        }
+    }
 }
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/IntHolder.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/IntHolder.java
new file mode 100644
index 0000000..2a07715
--- /dev/null
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/IntHolder.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+/**
+ * Holds an int value.
+ */
+class IntHolder {
+    final int value;
+
+    IntHolder(int value) {
+        this.value = value;
+    }
+}

[ignite-3] 05/27: - fix null marshalling

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 67c494a4b2991f8da72d40265e188101b0e33bac
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Mon Jan 10 19:23:34 2022 +0400

    - fix null marshalling
---
 .../serialization/marshal/DefaultUserObjectMarshaller.java  |  4 ++++
 .../DefaultUserObjectMarshallerWithBuiltinsTest.java        | 13 +++++++++++--
 2 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
index 146ada9..64c4e17 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
@@ -163,6 +163,10 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
     }
 
     private DescribedObject applyWriteReplaceIfNeeded(@Nullable Object originalObject, Class<?> declaredClass) throws MarshalException {
+        if (originalObject == null) {
+            return new DescribedObject(originalObject, descriptorRegistry.getNullDescriptor());
+        }
+
         // object class is not a subclass of the declared class for primitives
         // for enums we don't need the specific classes at all
         Class<?> classToQueryForOriginalDescriptor = isInstanceOfSubclass(originalObject, declaredClass)
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java
index 937986f..68d2557 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java
@@ -23,6 +23,7 @@ import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
 import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.jupiter.api.Assumptions.assumingThat;
 
@@ -104,6 +105,15 @@ class DefaultUserObjectMarshallerWithBuiltinsTest {
     }
 
     @Test
+    void marshalsAndUnmarshalsNullThrowable() throws Exception {
+        MarshalledObject marshalled = marshaller.marshal(null, Throwable.class);
+
+        Throwable unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptorRegistry);
+
+        assertThat(unmarshalled, is(nullValue()));
+    }
+
+    @Test
     void marshalsObjectArrayUsingExactlyDescriptorsOfObjectArrayAndComponents() throws Exception {
         MarshalledObject marshalled = marshaller.marshal(new Object[]{42, "abc"});
 
@@ -200,8 +210,7 @@ class DefaultUserObjectMarshallerWithBuiltinsTest {
                         BuiltinType.ENUM_ARRAY
                 ),
                 builtInTypeValue(BitSet.valueOf(new long[]{42, 43}), BitSet.class, BuiltinType.BIT_SET),
-                builtInTypeValue(null, Null.class, BuiltinType.NULL),
-                builtInTypeValue(null, Void.class, BuiltinType.VOID)
+                builtInTypeValue(null, Null.class, BuiltinType.NULL)
         ).map(Arguments::of);
     }
 

[ignite-3] 06/27: - make sure Externalizable constructor is called on unmarshalling

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 6183eeba7f4023542dc59265e4dd785324ebe67a
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 10:36:30 2022 +0400

    - make sure Externalizable constructor is called on unmarshalling
---
 ...UserObjectMarshallerWithExternalizableTest.java | 29 ++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithExternalizableTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithExternalizableTest.java
index c46c078..6f1765a 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithExternalizableTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithExternalizableTest.java
@@ -27,6 +27,7 @@ import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.ByteArrayInputStream;
 import java.io.DataInputStream;
@@ -57,6 +58,8 @@ class DefaultUserObjectMarshallerWithExternalizableTest {
     private static final int WRITE_REPLACE_INCREMENT = 1_000_000;
     private static final int READ_RESOLVE_INCREMENT = 1_000;
 
+    private static boolean constructorCalled;
+
     @Test
     void usesExactlyOneDescriptorWhenMarshallingExternalizable() throws Exception {
         MarshalledObject marshalled = marshaller.marshal(new SimpleExternalizable(42));
@@ -234,6 +237,16 @@ class DefaultUserObjectMarshallerWithExternalizableTest {
         assertThat(unmarshalled.intHolder.value, is(42));
     }
 
+    @Test
+    void invokesDefaultConstructorOnExternalizableUnmarshalling() throws Exception {
+        WithSideEffectInConstructor object = new WithSideEffectInConstructor();
+        constructorCalled = false;
+
+        marshalAndUnmarshalNonNull(object);
+
+        assertTrue(constructorCalled);
+    }
+
     /**
      * An {@link Externalizable} that does not have {@code writeReplace()}/{@code readResolve()} methods.
      */
@@ -528,4 +541,20 @@ class DefaultUserObjectMarshallerWithExternalizableTest {
             this.value = value;
         }
     }
+
+    private static class WithSideEffectInConstructor implements Externalizable {
+        public WithSideEffectInConstructor() {
+            constructorCalled = true;
+        }
+
+        @Override
+        public void writeExternal(ObjectOutput out) {
+            // no-op
+        }
+
+        @Override
+        public void readExternal(ObjectInput in) {
+            // no-op
+        }
+    }
 }

[ignite-3] 12/27: - clean up code

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit c86131ba583a3ce505ac7803626140bd1ee86170
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 12:00:14 2022 +0400

    - clean up code
---
 .../marshal/DefaultUserObjectMarshallerWithSerializableTest.java  | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java
index 1947a51..011bfa2 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableTest.java
@@ -638,10 +638,7 @@ class DefaultUserObjectMarshallerWithSerializableTest {
     }
 
     private static class SerializableWithNoOpWriteReadOverride implements Serializable {
-        private int value;
-
-        public SerializableWithNoOpWriteReadOverride() {
-        }
+        private final int value;
 
         public SerializableWithNoOpWriteReadOverride(int value) {
             this.value = value;
@@ -816,9 +813,6 @@ class DefaultUserObjectMarshallerWithSerializableTest {
     private static class SubclassWithWriteReadOverride extends SerializableWithWriteReadOverride {
         private int childValue;
 
-        public SubclassWithWriteReadOverride() {
-        }
-
         public SubclassWithWriteReadOverride(int value) {
             super(value);
             this.childValue = value;

[ignite-3] 18/27: - postpone one more TODO

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 7b4063e46e6ff0a0ee90faf87365c0c71b9fc43e
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 15:13:53 2022 +0400

    - postpone one more TODO
---
 .../marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java  | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
index e476540..625d940 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
@@ -213,8 +213,8 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
     }
 
     @Test
-    @Disabled("IGNITE-16165")
-    // TODO: IGNITE-16165 - enable this test when we are able to work with serializable lambdas
+    @Disabled("IGNITE-16258")
+    // TODO: IGNITE-16258 - enable this test when we are able to work with serializable lambdas
     void supportsNonCapturingSerializableLambdas() throws Exception {
         Callable<String> unmarshalled = marshalAndUnmarshalNonNull(nonCapturingSerializableLambda());
 

[ignite-3] 11/27: - improve javadoc

Posted by sd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch ignite-16250
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit 27915ab5d4e729ead19780aa1fdd17ecdeb5d546
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 11 11:50:19 2022 +0400

    - improve javadoc
---
 .../marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java     | 1 +
 1 file changed, 1 insertion(+)

diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
index 6e83d63..e476540 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
@@ -49,6 +49,7 @@ import org.junit.jupiter.api.Test;
 
 /**
  * Tests for how {@link DefaultUserObjectMarshaller} handles arbitrary objects.
+ * An arbitrary object is an object of a class that is not built-in, not a {@link Serializable} and not an {@link java.io.Externalizable}.
  */
 class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
     private final ClassDescriptorFactoryContext descriptorRegistry = new ClassDescriptorFactoryContext();