You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by am...@apache.org on 2020/12/16 22:00:17 UTC

[ignite-3] 01/01: Rewrite to VarHandles. Improve serializer API.

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

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

commit 0a56da1bf408b21e36887450d525583340b5b7b6
Author: Andrew Mashenkov <an...@gmail.com>
AuthorDate: Wed Dec 16 23:34:08 2020 +0300

    Rewrite to VarHandles.
    Improve serializer API.
---
 modules/commons/pom.xml                            |  12 +-
 .../apache/ignite/internal/schema/NativeType.java  |   8 +
 .../schema/marshaller/AbstractSerializer.java      | 108 ++++++
 .../internal/schema/marshaller/BinaryMode.java     |  20 +-
 .../internal/schema/marshaller/CompilerUtils.java  | 344 ++++++++++++++++++
 .../internal/schema/marshaller/MarshallerUtil.java |  16 +-
 .../schema/marshaller/SerializationException.java  |   1 -
 .../internal/schema/marshaller/Serializer.java     |   8 +-
 .../schema/marshaller/SerializerFactory.java       |   9 +-
 .../generator/FieldAccessExprGenerator.java        | 360 -------------------
 .../IdentityObjectMarshallerExprGenerator.java     |  39 ++-
 .../generator/JaninoSerializerGenerator.java       | 346 -------------------
 .../generator/MarshallerCodeGenerator.java         |  47 +++
 .../generator/MarshallerExprGenerator.java         |  94 -----
 .../generator/ObjectMarshallerCodeGenerator.java   | 124 +++++++
 .../marshaller/generator/SerializerGenerator.java  | 368 ++++++++++++++++++++
 .../generator/TupleColumnAccessCodeGenerator.java  | 257 ++++++++++++++
 ...UnsafeFieldAccessor.java => FieldAccessor.java} | 383 ++++++++++++++-------
 .../marshaller/reflection/JavaSerializer.java      | 186 +---------
 .../schema/marshaller/reflection/Marshaller.java   |  28 +-
 .../ignite/internal/util/IgniteUnsafeUtils.java    | 273 ---------------
 .../apache/ignite/internal/util/ObjectFactory.java |   8 -
 .../org/apache/ignite/lang/IgniteExperimental.java |  38 ++
 .../benchmarks/SerializerBenchmarkTest.java        |  51 +--
 .../schema/marshaller/JavaSerializerTest.java      |  96 +++++-
 .../marshaller/reflection/FieldAccessorTest.java   |  26 +-
 pom.xml                                            |   9 +-
 27 files changed, 1778 insertions(+), 1481 deletions(-)

diff --git a/modules/commons/pom.xml b/modules/commons/pom.xml
index fc78231..a5b64e8 100644
--- a/modules/commons/pom.xml
+++ b/modules/commons/pom.xml
@@ -29,7 +29,7 @@
         <artifactId>apache-ignite</artifactId>
         <groupId>org.apache.ignite</groupId>
         <version>3.0.0-SNAPSHOT</version>
-        <relativePath>../..</relativePath>
+        <relativePath>../../pom.xml</relativePath>
     </parent>
 
     <artifactId>ignite-commons</artifactId>
@@ -43,12 +43,11 @@
             <version>${jetbrains.annotations.version}</version>
         </dependency>
         <dependency>
-            <groupId>org.codehaus.janino</groupId>
-            <artifactId>janino</artifactId>
-            <version>${janino.version}</version>
+            <groupId>com.squareup</groupId>
+            <artifactId>javapoet</artifactId>
+            <version>${javapoet.version}</version>
         </dependency>
 
-
         <!-- Test dependencies -->
         <dependency>
             <groupId>org.junit.jupiter</groupId>
@@ -56,11 +55,10 @@
             <version>${junit.jupiter.version}</version>
             <scope>test</scope>
         </dependency>
-
         <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
-            <version>${mockito.version}</version>
+            <version>${mockito.core.version}</version>
             <scope>test</scope>
         </dependency>
 
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/NativeType.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/NativeType.java
index 211a1e1..e5d40c7 100644
--- a/modules/commons/src/main/java/org/apache/ignite/internal/schema/NativeType.java
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/NativeType.java
@@ -133,4 +133,12 @@ public class NativeType implements Comparable<NativeType> {
 
         return typeSpec.name().compareTo(o.typeSpec.name());
     }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return "NativeType{" +
+            "typeSpec=" + typeSpec +
+            ", len=" + len +
+            '}';
+    }
 }
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/AbstractSerializer.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/AbstractSerializer.java
new file mode 100644
index 0000000..5ac127c
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/AbstractSerializer.java
@@ -0,0 +1,108 @@
+/*
+ * 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.schema.marshaller;
+
+import java.util.Objects;
+import org.apache.ignite.internal.schema.ByteBufferTuple;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.Tuple;
+import org.apache.ignite.internal.schema.TupleAssembler;
+import org.apache.ignite.internal.util.Pair;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Base serializer class.
+ */
+public abstract class AbstractSerializer implements Serializer {
+    /** Schema descriptor. */
+    protected final SchemaDescriptor schema;
+
+    /**
+     * Constructor.
+     *
+     * @param schema Schema descriptor.
+     */
+    protected AbstractSerializer(SchemaDescriptor schema) {
+        this.schema = schema;
+    }
+
+    /** {@inheritDoc} */
+    @Override public byte[] serialize(Object key, Object val) throws SerializationException {
+        final TupleAssembler assembler = createAssembler(Objects.requireNonNull(key), val);
+
+        return serialize0(assembler, key, val);
+    }
+
+    /** {@inheritDoc} */
+    @Override public <K> K deserializeKey(byte[] data) throws SerializationException {
+        final Tuple tuple = new ByteBufferTuple(schema, data);
+
+        return (K)deserializeKey0(tuple);
+    }
+
+    /** {@inheritDoc} */
+    @Override public <V> V deserializeValue(byte[] data) throws SerializationException {
+        final Tuple tuple = new ByteBufferTuple(schema, data);
+
+        return (V)deserializeValue0(tuple);
+    }
+
+    /** {@inheritDoc} */
+    @Override public <K, V> Pair<K, V> deserialize(byte[] data) throws SerializationException {
+        final Tuple tuple = new ByteBufferTuple(schema, data);
+
+        return new Pair<>((K)deserializeKey0(tuple), (V)deserializeValue0(tuple));
+    }
+
+    /**
+     * Tuple assembler factory method.
+     *
+     * @param key Key object.
+     * @param val Value object.
+     */
+    protected abstract TupleAssembler createAssembler(Object key, @Nullable Object val);
+
+    /**
+     * Internal serialization method.
+     *
+     * @param asm Tuple assembler.
+     * @param key Key object.
+     * @param val Value object.
+     * @return Serialized pair.
+     * @throws SerializationException If failed.
+     */
+    protected abstract byte[] serialize0(TupleAssembler asm, Object key, Object val) throws SerializationException;
+
+    /**
+     * Extract key object from tuple.
+     *
+     * @param tuple Tuple.
+     * @return Deserialized key object.
+     * @throws SerializationException If failed.
+     */
+    protected abstract Object deserializeKey0(Tuple tuple) throws SerializationException;
+
+    /**
+     * Extract value object from tuple.
+     *
+     * @param tuple Tuple.
+     * @return Deserialized value object.
+     * @throws SerializationException If failed.
+     */
+    protected abstract Object deserializeValue0(Tuple tuple) throws SerializationException;
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/BinaryMode.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/BinaryMode.java
index db5a77f..6a0c6ab 100644
--- a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/BinaryMode.java
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/BinaryMode.java
@@ -41,34 +41,34 @@ public enum BinaryMode {
     /** Primitive int. */
     P_DOUBLE(NativeTypeSpec.DOUBLE),
 
-    /** */
+    /** Boxed byte. */
     BYTE(NativeTypeSpec.BYTE),
 
-    /** */
+    /** Boxed short. */
     SHORT(NativeTypeSpec.SHORT),
 
-    /** */
+    /** Boxed int. */
     INT(NativeTypeSpec.INTEGER),
 
-    /** */
+    /** Boxed long. */
     LONG(NativeTypeSpec.LONG),
 
-    /** */
+    /** Boxed float. */
     FLOAT(NativeTypeSpec.FLOAT),
 
-    /** */
+    /** Boxed double. */
     DOUBLE(NativeTypeSpec.DOUBLE),
 
-    /** */
+    /** String. */
     STRING(NativeTypeSpec.STRING),
 
-    /** */
+    /** Uuid. */
     UUID(NativeTypeSpec.UUID),
 
-    /** */
+    /** Raw byte array. */
     BYTE_ARR(NativeTypeSpec.BYTES),
 
-    /** */
+    /** BitSet.*/
     BITSET(NativeTypeSpec.BITMASK);
 
     /** Natove type spec. */
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/CompilerUtils.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/CompilerUtils.java
new file mode 100644
index 0000000..0edaf68
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/CompilerUtils.java
@@ -0,0 +1,344 @@
+/*
+ * 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.schema.marshaller;
+
+import com.squareup.javapoet.JavaFile;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticCollector;
+import javax.tools.FileObject;
+import javax.tools.ForwardingJavaFileManager;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Compiler utility class.
+ */
+public final class CompilerUtils {
+    /**
+     * @return Creates classloader for dynamically loaded in-memory classes.
+     */
+    public static ClassLoader dynamicClassLoader() {
+        return AccessController.doPrivileged(new PrivilegedAction<>() {
+            @Override public ClassLoader run() {
+                return new MemoryClassLoader(new ConcurrentHashMap<>(), ClassLoader.getSystemClassLoader());
+            }
+        });
+    }
+
+    /**
+     * Compiles code and load compiled classes.
+     *
+     * @param javafile Java file representation.
+     * @return Classloader with compiled classes.
+     */
+    public static ClassLoader compileCode(JavaFile javafile) {
+        final JavaCompiler cmp = ToolProvider.getSystemJavaCompiler();
+
+        final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+
+        ConcurrentHashMap<String, byte[]> classes = classLoader instanceof MemoryClassLoader ?
+            ((MemoryClassLoader)classLoader).classBytes : new ConcurrentHashMap<>();
+
+        try (final MemoryJavaFileManager fileManager = new MemoryJavaFileManager(cmp.getStandardFileManager(null, null, null), classes)) {
+            DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
+
+            JavaCompiler.CompilationTask task = cmp.getTask(null, fileManager, diagnostics, null, null, Collections.singletonList(javafile.toJavaFileObject()));
+
+            if (task.call())
+                return classLoader instanceof MemoryClassLoader ? classLoader :
+                    new MemoryClassLoader(classes, ClassLoader.getSystemClassLoader());
+
+            // TODO: write to log.
+            for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
+                System.out.println(diagnostic.getCode());
+                System.out.println(diagnostic.getKind());
+                System.out.println(diagnostic.getPosition());
+                System.out.println(diagnostic.getStartPosition());
+                System.out.println(diagnostic.getEndPosition());
+                System.out.println(diagnostic.getSource());
+                System.out.println(diagnostic.getMessage(null));
+            }
+
+            throw new IllegalStateException("Failed to compile code:\n" + javafile.toString());
+        }
+        catch (IOException ex) {
+            throw new IllegalStateException("Failed to compile code.", ex);
+        }
+    }
+
+    /**
+     * @param iterables Iterables.
+     * @return Concated iterable.
+     */
+    private static <E> Iterable<E> concat(Iterable<? extends E>... iterables) {
+        return new Iterable<>() {
+            private final Iterator<Iterable<? extends E>> it = Arrays.asList(iterables).iterator();
+
+            /** {@inheritDoc} */
+            @Override public Iterator<E> iterator() {
+                return new Iterator<>() {
+                    private Iterator<? extends E> curIt = Collections.emptyIterator();
+
+                    /** {@inheritDoc} */
+                    @Override public boolean hasNext() {
+                        if (curIt == null || !curIt.hasNext())
+                            advance();
+
+                        return curIt.hasNext();
+                    }
+
+                    /** Switches to next iterable. */
+                    private void advance() {
+                        while (it.hasNext() && !curIt.hasNext())
+                            curIt = it.next().iterator();
+                    }
+
+                    /** {@inheritDoc} */
+                    @Override public E next() {
+                        if (!hasNext())
+                            throw new NoSuchElementException();
+
+                        return curIt.next();
+                    }
+
+                    /** {@inheritDoc} */
+                    @Override public void remove() {
+                        throw new UnsupportedOperationException();
+                    }
+                };
+            }
+        };
+    }
+
+    /**
+     * In-memory java file manager.
+     */
+    private static class MemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
+        /** Classes code. */
+        private final Map<String, byte[]> classesBytes;
+
+        /**
+         * Constructor.
+         *
+         * @param fileManager Java file manager.
+         * @param classesBytes Classes code.
+         */
+        public MemoryJavaFileManager(JavaFileManager fileManager, Map<String, byte[]> classesBytes) {
+            super(fileManager);
+            this.classesBytes = classesBytes;
+        }
+
+        /**
+         * Java '.class' in-memory file.
+         */
+        private static class MemoryJavaFileObject extends SimpleJavaFileObject {
+            /** Class code. */
+            private final byte[] classBytes;
+
+            /**
+             * Constructor.
+             *
+             * @param className Class name.
+             * @param classBytes Class code.
+             */
+            MemoryJavaFileObject(String className, byte[] classBytes) {
+                super(URI.create(className + Kind.CLASS.extension), Kind.CLASS);
+                this.classBytes = classBytes;
+            }
+
+            /** {@inheritDoc} */
+            @Override public InputStream openInputStream() {
+                return new ByteArrayInputStream(classBytes);
+            }
+        }
+
+        /**
+         * A file object that stores Java bytecode into the classBytes map.
+         */
+        private class JavaClassOutputFile extends SimpleJavaFileObject {
+            /** Class name. */
+            private final String classname;
+
+            /**
+             * Constructor.
+             *
+             * @param classname Class name.
+             */
+            JavaClassOutputFile(String classname) {
+                super(URI.create(classname), Kind.CLASS);
+                this.classname = classname;
+            }
+
+            /** {@inheritDoc} */
+            @Override public OutputStream openOutputStream() {
+                return new ByteArrayOutputStream() {
+                    @Override public void close() throws IOException {
+                        super.close();
+
+                        classesBytes.put(classname, toByteArray());
+                    }
+                };
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override public Iterable<JavaFileObject> list(Location location, String packageName,
+            Set<JavaFileObject.Kind> kinds,
+            boolean recurse) throws IOException {
+            final Iterable<JavaFileObject> it = super.list(location, packageName, kinds, recurse);
+
+            if (location == StandardLocation.CLASS_PATH) {
+                assert kinds.contains(JavaFileObject.Kind.CLASS);
+
+                Iterable<JavaFileObject> localClasses = new Iterable<>() {
+                    @Override public Iterator<JavaFileObject> iterator() {
+                        return classesBytes.keySet().stream()
+                            .filter(cn -> cn.startsWith(packageName))
+                            .map(cn -> getJavaFileObjectByName(cn))
+                            .filter(Objects::nonNull).iterator();
+                    }
+                };
+
+                return concat(localClasses, it);
+            }
+            else
+                return it;
+        }
+
+        /** {@inheritDoc} */
+        @Override public String inferBinaryName(Location location, JavaFileObject jfo) {
+            if (!(jfo instanceof MemoryJavaFileObject)) {
+                String result = super.inferBinaryName(location, jfo);
+                assert result != null;
+                return result;
+            }
+
+            // A [Java]FileObject's "name" looks like this: "/orc/codehaus/commons/compiler/Foo.java".
+            // A [Java]FileObject's "binary name" looks like "java.lang.annotation.Retention".
+
+            String bn = jfo.getName();
+            if (bn.startsWith("/"))
+                bn = bn.substring(1);
+
+            if (!bn.endsWith(jfo.getKind().extension)) {
+                throw new AssertionError(
+                    "Name \"" + jfo.getName() + "\" does not match kind \"" + jfo.getKind() + "\""
+                );
+            }
+            bn = bn.substring(0, bn.length() - jfo.getKind().extension.length());
+
+            bn = bn.replace('/', '.');
+
+            return bn;
+        }
+
+        /** {@inheritDoc} */
+        @Override public JavaFileObject getJavaFileForInput(Location location, String className,
+            JavaFileObject.Kind kind) throws IOException {
+            if (location == StandardLocation.CLASS_OUTPUT) {
+                JavaFileObject javaFileObject = getJavaFileObjectByName(className);
+
+                if (javaFileObject != null)
+                    return javaFileObject;
+            }
+
+            return fileManager.getJavaFileForInput(location, className, kind);
+        }
+
+        /** {@inheritDoc} */
+        @Nullable private JavaFileObject getJavaFileObjectByName(String className) {
+            final byte[] bytes = classesBytes.get(className);
+
+            if (bytes != null)
+                return new MemoryJavaFileObject(className, bytes);
+
+            return null;
+        }
+
+        /** {@inheritDoc} */
+        @Override public JavaFileObject getJavaFileForOutput(Location location,
+            String className,
+            JavaFileObject.Kind kind,
+            FileObject sibling) throws IOException {
+            if (kind == JavaFileObject.Kind.CLASS)
+                return new JavaClassOutputFile(className);
+            else
+                return super.getJavaFileForOutput(location, className, kind, sibling);
+        }
+    }
+
+    /**
+     * Classloader for runtime compiled classes.
+     */
+    private static final class MemoryClassLoader extends URLClassLoader {
+        /** Empty array. */
+        private static final URL[] EMPTY_URLS = new URL[0];
+
+        /** Classes code. */
+        private final ConcurrentHashMap<String, byte[]> classBytes;
+
+        /**
+         * Constructor.
+         *
+         * @param classBytes Classes code holder.
+         * @param parent Parent classloader.
+         */
+        MemoryClassLoader(ConcurrentHashMap<String, byte[]> classBytes, ClassLoader parent) {
+            super(EMPTY_URLS, parent);
+
+            this.classBytes = classBytes;
+        }
+
+        /** {@inheritDoc} */
+        @Override protected Class<?> findClass(String className) throws ClassNotFoundException {
+            byte[] buf = classBytes.get(className); // clear the bytes in map -- we don't need it anymore
+
+            if (buf != null)
+                return defineClass(className, buf, 0, buf.length);
+            else
+                return super.findClass(className);
+        }
+    }
+
+    /** Stub. */
+    private CompilerUtils() {
+    }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/MarshallerUtil.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/MarshallerUtil.java
index c111ba8..9361fa0 100644
--- a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/MarshallerUtil.java
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/MarshallerUtil.java
@@ -21,6 +21,7 @@ import java.util.BitSet;
 import java.util.UUID;
 import org.apache.ignite.internal.schema.NativeType;
 import org.apache.ignite.internal.schema.TupleAssembler;
+import org.apache.ignite.internal.util.ObjectFactory;
 
 /**
  * Marshaller utility class.
@@ -97,7 +98,18 @@ public final class MarshallerUtil {
     }
 
     /**
-     * Stub.
+     * Creates object factory for class.
+     * @param tClass Target type.
+     * @return Object factory.
      */
-    private MarshallerUtil() {}
+    public static <T> ObjectFactory<T> factoryForClass(Class<T> tClass) {
+        if (mode(tClass) == null)
+            return new ObjectFactory<>(tClass);
+        else
+            return null;
+    }
+
+    /** Stub. */
+    private MarshallerUtil() {
+    }
 }
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializationException.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializationException.java
index e690710..f87f91a 100644
--- a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializationException.java
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializationException.java
@@ -27,7 +27,6 @@ public class SerializationException extends Exception {
      * @param cause Cause.
      */
     public SerializationException(Throwable cause) {
-        // Used by serializers generated with Janino.
         super(cause);
     }
 
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/Serializer.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/Serializer.java
index 21a1560..f809036 100644
--- a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/Serializer.java
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/Serializer.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.internal.schema.marshaller;
 
+import org.apache.ignite.internal.util.Pair;
+
 /**
  * Key-value objects (de)serializer.
  */
@@ -33,10 +35,12 @@ public interface Serializer {
     /**
      * @return Key object.
      */
-    Object deserializeKey(byte[] data) throws SerializationException;
+    <K> K deserializeKey(byte[] data) throws SerializationException;
 
     /**
      * @return Value object.
      */
-    Object deserializeValue(byte[] data) throws SerializationException;
+    <V> V deserializeValue(byte[] data) throws SerializationException;
+
+    <K, V> Pair<K,V> deserialize(byte[] data) throws SerializationException;
 }
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializerFactory.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializerFactory.java
index d9a8295..cf006c4 100644
--- a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializerFactory.java
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializerFactory.java
@@ -18,8 +18,10 @@
 package org.apache.ignite.internal.schema.marshaller;
 
 import org.apache.ignite.internal.schema.SchemaDescriptor;
-import org.apache.ignite.internal.schema.marshaller.generator.JaninoSerializerGenerator;
+import org.apache.ignite.internal.schema.marshaller.generator.SerializerGenerator;
 import org.apache.ignite.internal.schema.marshaller.reflection.JavaSerializerFactory;
+import org.apache.ignite.lang.IgniteExperimental;
+import org.jetbrains.annotations.ApiStatus;
 
 /**
  * (De)Serializer factory interface.
@@ -29,13 +31,14 @@ public interface SerializerFactory {
     /**
      * @return Serializer factory back by code generator.
      */
-    public static SerializerFactory createJaninoSerializerFactory() {
-        return new JaninoSerializerGenerator();
+    public static SerializerFactory createGeneratedSerializerFactory() {
+        return new SerializerGenerator();
     }
 
     /**
      * @return Reflection-based serializer factory.
      */
+    @IgniteExperimental
     public static SerializerFactory createJavaSerializerFactory() {
         return new JavaSerializerFactory();
     }
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/FieldAccessExprGenerator.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/FieldAccessExprGenerator.java
deleted file mode 100644
index fb3736a..0000000
--- a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/FieldAccessExprGenerator.java
+++ /dev/null
@@ -1,360 +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.schema.marshaller.generator;
-
-import org.apache.ignite.internal.schema.marshaller.BinaryMode;
-import org.jetbrains.annotations.Nullable;
-
-import static org.apache.ignite.internal.schema.marshaller.generator.JaninoSerializerGenerator.LF;
-
-/**
- * Object field access expression generators.
- */
-class FieldAccessExprGenerator {
-    /** Append null expression. */
-    private static final String WRITE_NULL_EXPR = "asm.appendNull();";
-
-    /**
-     * Created object access expressions generator.
-     *
-     * @param mode Field access binary mode.
-     * @param colIdx Column absolute index in schema.
-     * @return Object field access expressions generator.
-     */
-    static FieldAccessExprGenerator createIdentityAccessor(BinaryMode mode, int colIdx) {
-        return createAccessor(mode, colIdx, -1L);
-    }
-
-    /**
-     * Created object field access expressions generator.
-     *
-     * @param mode Field access binary mode.
-     * @param colIdx Column absolute index in schema.
-     * @param offset Object field offset.
-     * @return Object field access expressions generator.
-     */
-    static FieldAccessExprGenerator createAccessor(BinaryMode mode, int colIdx, long offset) {
-        switch (mode) {
-            case BYTE:
-                return new FieldAccessExprGenerator(
-                    colIdx,
-                    "Byte",
-                    "tuple.byteValueBoxed",
-                    "asm.appendByte",
-                    offset);
-
-            case P_BYTE:
-                return new FieldAccessExprGenerator(
-                    colIdx,
-                    "tuple.byteValue",
-                    "asm.appendByte",
-                    offset,
-                    "IgniteUnsafeUtils.getByteField",
-                    "IgniteUnsafeUtils.putByteField"
-                );
-
-            case SHORT:
-                return new FieldAccessExprGenerator(
-                    colIdx,
-                    "Short",
-                    "tuple.shortValueBoxed",
-                    "asm.appendShort",
-                    offset);
-
-            case P_SHORT:
-                return new FieldAccessExprGenerator(
-                    colIdx,
-                    "tuple.shortValue",
-                    "asm.appendShort",
-                    offset,
-                    "IgniteUnsafeUtils.getShortField",
-                    "IgniteUnsafeUtils.putShortField"
-                );
-
-            case INT:
-                return new FieldAccessExprGenerator(
-                    colIdx,
-                    "Integer",
-                    "tuple.intValueBoxed",
-                    "asm.appendInt",
-                    offset);
-
-            case P_INT:
-                return new FieldAccessExprGenerator(
-                    colIdx,
-                    "tuple.intValue",
-                    "asm.appendInt",
-                    offset,
-                    "IgniteUnsafeUtils.getIntField",
-                    "IgniteUnsafeUtils.putIntField"
-                );
-
-            case LONG:
-                return new FieldAccessExprGenerator(
-                    colIdx,
-                    "Long",
-                    "tuple.longValueBoxed",
-                    "asm.appendLong",
-                    offset);
-
-            case P_LONG:
-                return new FieldAccessExprGenerator(
-                    colIdx,
-                    "tuple.longValue",
-                    "asm.appendLong",
-                    offset,
-                    "IgniteUnsafeUtils.getLongField",
-                    "IgniteUnsafeUtils.putLongField"
-                );
-
-            case FLOAT:
-                return new FieldAccessExprGenerator(
-                    colIdx,
-                    "Float",
-                    "tuple.floatValueBoxed",
-                    "asm.appendFloat",
-                    offset);
-
-            case P_FLOAT:
-                return new FieldAccessExprGenerator(
-                    colIdx,
-                    "tuple.floatValue",
-                    "asm.appendFloat",
-                    offset,
-                    "IgniteUnsafeUtils.getFloatField",
-                    "IgniteUnsafeUtils.putFloatField"
-                );
-
-            case DOUBLE:
-                return new FieldAccessExprGenerator(
-                    colIdx,
-                    "Double",
-                    "tuple.doubleValueBoxed",
-                    "asm.appendDouble",
-                    offset);
-
-            case P_DOUBLE:
-                return new FieldAccessExprGenerator(
-                    colIdx,
-                    "tuple.doubleValue",
-                    "asm.appendDouble",
-                    offset,
-                    "IgniteUnsafeUtils.getDoubleField",
-                    "IgniteUnsafeUtils.putDoubleField"
-                );
-
-            case UUID:
-                return new FieldAccessExprGenerator(
-                    colIdx,
-                    "UUID",
-                    "tuple.uuidValue", "asm.appendUuid",
-                    offset);
-
-            case BITSET:
-                return new FieldAccessExprGenerator(
-                    colIdx,
-                    "BitSet",
-                    "tuple.bitmaskValue", "asm.appendBitmask",
-                    offset);
-
-            case STRING:
-                return new FieldAccessExprGenerator(
-                    colIdx,
-                    "String",
-                    "tuple.stringValue", "asm.appendString",
-                    offset);
-
-            case BYTE_ARR:
-                return new FieldAccessExprGenerator(
-                    colIdx,
-                    "byte[]",
-                    "tuple.bytesValue", "asm.appendBytes",
-                    offset);
-            default:
-                throw new IllegalStateException("Unsupportd binary mode");
-        }
-    }
-
-    /** Object field offset or {@code -1} for identity accessor. */
-    private final long offset;
-
-    /** Absolute schema index. */
-    private final int colIdx;
-
-    /** Class cast expression. */
-    private final String classExpr;
-
-    /** Write column value expression. */
-    private final String writeColMethod;
-
-    /** Read column value expression. */
-    private final String readColMethod;
-
-    /** Read object field expression. */
-    private final String getFieldMethod;
-
-    /** Write object field expression. */
-    private final String putFieldMethod;
-
-    /**
-     * Constructor.
-     *
-     * @param colIdx Absolute schema index in schema.
-     * @param castClassExpr Class cast expression
-     * @param readColMethod Read column value expression.
-     * @param writeColMethod Write column value expression.
-     * @param offset Field offset or {@code -1} for identity accessor.
-     */
-    private FieldAccessExprGenerator(
-        int colIdx,
-        String castClassExpr,
-        String readColMethod,
-        String writeColMethod,
-        long offset
-    ) {
-        this(colIdx, castClassExpr, readColMethod, writeColMethod, offset,
-            "IgniteUnsafeUtils.getObjectField", "IgniteUnsafeUtils.putObjectField");
-    }
-
-    /**
-     * Constructor.
-     *
-     * @param colIdx Absolute schema index in schema.
-     * @param readColMethod Read column value expression.
-     * @param writeColMethod Write column value expression.
-     * @param offset Field offset or {@code -1} for identity accessor.
-     * @param getFieldMethod Read object field expression.
-     * @param putFieldMethod Read object field expression.
-     */
-    public FieldAccessExprGenerator(
-        int colIdx,
-        String readColMethod,
-        String writeColMethod,
-        long offset,
-        String getFieldMethod,
-        String putFieldMethod
-    ) {
-        this(colIdx, null /* primitive type */, readColMethod, writeColMethod, offset, getFieldMethod, putFieldMethod);
-    }
-
-    /**
-     * Constructor.
-     *
-     * @param colIdx Absolute schema index in schema.
-     * @param castClassExpr Class cast expression or {@code null} if not applicable.
-     * @param readColMethod Read column value expression.
-     * @param writeColMethod Write column value expression.
-     * @param offset Field offset or {@code -1} for identity accessor.
-     * @param getFieldMethod Read object field expression.
-     * @param putFieldMethod Read object field expression.
-     */
-    private FieldAccessExprGenerator(
-        int colIdx,
-        @Nullable String castClassExpr,
-        String readColMethod,
-        String writeColMethod,
-        long offset,
-        String getFieldMethod,
-        String putFieldMethod
-    ) {
-        this.offset = offset;
-        this.colIdx = colIdx;
-        this.classExpr = castClassExpr;
-        this.putFieldMethod = putFieldMethod;
-        this.getFieldMethod = getFieldMethod;
-        this.writeColMethod = writeColMethod;
-        this.readColMethod = readColMethod;
-    }
-
-    /**
-     * @return {@code true} if it is primitive typed field accessor, {@code false} otherwise.
-     */
-    private boolean isPrimitive() {
-        return classExpr == null;
-    }
-
-    /**
-     * @return {@code true} if is identity accessor, {@code false} otherwise.
-     */
-    private boolean isIdentityAccessor() {
-        return offset == -1;
-    }
-
-    /**
-     * @return Object field value access expression or object value expression for simple types.
-     */
-    public String getFieldExpr() {
-        if (isIdentityAccessor())
-            return "obj"; // Identity accessor.
-
-        return getFieldMethod + "(obj, " + offset + ')';
-    }
-
-    /**
-     * Appends write value to field expression.
-     *
-     * @param sb String bulder.
-     * @param valueExpression Value expression.
-     * @param indent Line indentation.
-     */
-    public final void appendPutFieldExpr(StringBuilder sb, String valueExpression, String indent) {
-        sb.append(indent).append(putFieldMethod).append("(obj, ").append(offset).append(", ").append(valueExpression).append(')');
-        sb.append(";" + LF);
-    }
-
-    /**
-     * Appends write value to column expression.
-     *
-     * @param sb String bulder.
-     * @param valueExpr Value expression.
-     * @param indent Line indentation.
-     */
-    public final void appendWriteColumnExpr(StringBuilder sb, String valueExpr, String indent) {
-        if (isPrimitive() || isIdentityAccessor()) {
-            // Translate to:
-            // asm.appendX((T) %value%);
-            // or for primitive value:
-            // asm.appendX(%value%);
-            sb.append(indent).append(writeColMethod).append('(');
-
-            if (classExpr != null)
-                sb.append("(").append(classExpr).append(")");
-
-            sb.append(valueExpr).append(");" + LF);
-
-            return;
-        }
-
-        assert classExpr != null;
-
-        // Translate to:
-        // { T fVal = (T)%value%;
-        //  if (fVal == null) asm.appendNull() else asm.appendX(fVal); }
-        sb.append(indent).append("{ ").append(classExpr).append(" fVal = (").append(classExpr).append(')').append(valueExpr).append(";" + LF);
-        sb.append(indent).append("if (fVal == null) " + WRITE_NULL_EXPR + LF);
-        sb.append(indent).append("else ").append(writeColMethod).append("(fVal); }" + LF);
-
-    }
-
-    /**
-     * @return Column value read expression.
-     */
-    public String readColumnExpr() {
-        return readColMethod + "(" + colIdx + ")";
-    }
-}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/IdentityObjectMarshallerExprGenerator.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/IdentityObjectMarshallerExprGenerator.java
index e574e61..5778a5a 100644
--- a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/IdentityObjectMarshallerExprGenerator.java
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/IdentityObjectMarshallerExprGenerator.java
@@ -17,29 +17,50 @@
 
 package org.apache.ignite.internal.schema.marshaller.generator;
 
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.TypeSpec;
 import org.apache.ignite.internal.schema.marshaller.Serializer;
 
 /**
  * Generate {@link Serializer} method's bodies for simple types.
  */
-class IdentityObjectMarshallerExprGenerator extends MarshallerExprGenerator {
+class IdentityObjectMarshallerExprGenerator implements MarshallerCodeGenerator {
+    /** Tuple column accessor. */
+    private final TupleColumnAccessCodeGenerator columnAccessor;
+
     /**
      * Constructor.
      *
-     * @param accessor Object field access expression generators.
+     * @param columnAccessor Tuple column code generator.
      */
-    IdentityObjectMarshallerExprGenerator(FieldAccessExprGenerator accessor) {
-        super(null /* no instantiation needed */, new FieldAccessExprGenerator[] {accessor});
+    IdentityObjectMarshallerExprGenerator(TupleColumnAccessCodeGenerator columnAccessor) {
+        this.columnAccessor = columnAccessor;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isSimpleType() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public CodeBlock unmarshallObjectCode(String tupleExpr) {
+        return CodeBlock.builder()
+            .addStatement("return $L", columnAccessor.read(tupleExpr))
+            .build();
+    }
+
+    /** {@inheritDoc} */
+    @Override public CodeBlock marshallObjectCode(String asm, String objVar) {
+        return columnAccessor.write(asm, objVar);
     }
 
     /** {@inheritDoc} */
-    @Override public void appendMarshallObjectExpr(StringBuilder sb, String indent) {
-        for (int i = 0; i < accessors.length; i++)
-            accessors[i].appendWriteColumnExpr(sb, "obj", indent);
+    @Override public CodeBlock getValueCode(String objVar, int colIdx) {
+        return CodeBlock.of(objVar);
     }
 
     /** {@inheritDoc} */
-    @Override public void appendUnmarshallObjectExpr(StringBuilder sb, String indent) {
-        sb.append(indent).append("Object obj = ").append(accessors[0].readColumnExpr()).append(";" + JaninoSerializerGenerator.LF);
+    @Override public void initStaticHandlers(TypeSpec.Builder builder, CodeBlock.Builder staticBuilder) {
+        throw new UnsupportedOperationException("Static handlers are not applicable to simple types.");
     }
 }
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/JaninoSerializerGenerator.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/JaninoSerializerGenerator.java
deleted file mode 100644
index 654bf97..0000000
--- a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/JaninoSerializerGenerator.java
+++ /dev/null
@@ -1,346 +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.schema.marshaller.generator;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import org.apache.ignite.internal.schema.Columns;
-import org.apache.ignite.internal.schema.SchemaDescriptor;
-import org.apache.ignite.internal.schema.marshaller.BinaryMode;
-import org.apache.ignite.internal.schema.marshaller.MarshallerUtil;
-import org.apache.ignite.internal.schema.marshaller.Serializer;
-import org.apache.ignite.internal.schema.marshaller.SerializerFactory;
-import org.apache.ignite.internal.util.IgniteUnsafeUtils;
-import org.codehaus.commons.compiler.CompilerFactoryFactory;
-import org.codehaus.commons.compiler.IClassBodyEvaluator;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * {@link Serializer} code generator backed with Janino.
- */
-public class JaninoSerializerGenerator implements SerializerFactory {
-    /** Tabulate. */
-    static final String TAB = "    ";
-
-    /** Line feed. */
-    static final char LF = '\n';
-
-    /** String buffer initial size. */
-    public static final int INITIAL_BUFFER_SIZE = 8 * 1024;
-
-    /** Debug flag. */
-    private static final boolean enabledDebug = true;
-
-    /** {@inheritDoc} */
-    @Override public Serializer create(
-        SchemaDescriptor schema,
-        Class<?> keyClass,
-        Class<?> valClass
-    ) {
-        try {
-            final IClassBodyEvaluator ce = CompilerFactoryFactory.getDefaultCompilerFactory().newClassBodyEvaluator();
-
-            // Generate Serializer code.
-            String code = generateSerializerClassCode(ce, schema, keyClass, valClass);
-
-            //TODO: pass code to logger on trace level.
-
-            if (enabledDebug) {
-                ce.setDebuggingInformation(true, true, true);
-
-                //TODO: dump code to log.
-//                System.out.println(code);
-            }
-
-            try {  // Compile and load class.
-                ce.setParentClassLoader(getClass().getClassLoader());
-                ce.cook(code);
-
-                // Create and return Serializer instance.
-                final Constructor<Serializer> ctor = (Constructor<Serializer>)ce.getClazz()
-                    .getDeclaredConstructor(schema.getClass(), Class.class, Class.class);
-
-                return ctor.newInstance(schema, keyClass, valClass);
-            }
-            catch (Exception ex) {
-                if (enabledDebug)
-                    throw new IllegalStateException("Failed to compile/instantiate generated Serializer: code=" +
-                        LF + code + LF, ex);
-                else
-                    throw new IllegalStateException("Failed to compile/instantiate generated Serializer.", ex);
-            }
-        }
-        catch (Exception ex) {
-            //TODO: fallback to java serializer?
-            throw new IllegalStateException(ex);
-        }
-    }
-
-    /**
-     * Generates serializer code.
-     *
-     * @param ce Class body evaluator.
-     * @param schema Schema descriptor.
-     * @param keyClass Key class.
-     * @param valClass Value class.
-     * @return Generated class code.
-     */
-    private String generateSerializerClassCode(
-        IClassBodyEvaluator ce,
-        SchemaDescriptor schema,
-        Class<?> keyClass,
-        Class<?> valClass
-    ) {
-        final String packageName = "org.apache.ignite.internal.schema.marshaller.";
-        final String className = "JaninoSerializerForSchema_" + schema.version();
-
-        // Prerequisites.
-        ce.setClassName(packageName + className);
-        ce.setImplementedInterfaces(new Class[] {Serializer.class});
-        ce.setDefaultImports(
-            "java.util.UUID",
-            "java.util.BitSet",
-
-            "org.apache.ignite.internal.schema.ByteBufferTuple",
-            "org.apache.ignite.internal.schema.Columns",
-            "org.apache.ignite.internal.schema.SchemaDescriptor",
-            "org.apache.ignite.internal.schema.Tuple",
-            "org.apache.ignite.internal.schema.TupleAssembler",
-            "org.apache.ignite.internal.util.IgniteUnsafeUtils",
-            "org.apache.ignite.internal.util.ObjectFactory"
-        );
-
-        // Build field accessor generators.
-        final MarshallerExprGenerator keyMarsh = createObjectMarshaller(keyClass, "keyFactory", schema.keyColumns(), 0);
-        final MarshallerExprGenerator valMarsh = createObjectMarshaller(valClass, "valFactory", schema.valueColumns(), schema.keyColumns().length());
-
-        // Create buffer.
-        final StringBuilder sb = new StringBuilder(INITIAL_BUFFER_SIZE);
-
-        // Append class fields desctiption.
-        sb.append("private final SchemaDescriptor schema;" + LF);
-
-        if (!keyMarsh.isSimpleTypeMarshaller())
-            sb.append("private final ObjectFactory keyFactory;" + LF);
-        if (!valMarsh.isSimpleTypeMarshaller())
-            sb.append("private final ObjectFactory valFactory;" + LF);
-
-        // Append constructor code.
-        sb.append(LF + "public ").append(className).append("(SchemaDescriptor schema, Class kClass, Class vClass) {" + LF);
-        sb.append(TAB + "this.schema = schema; " + LF);
-        if (!keyMarsh.isSimpleTypeMarshaller())
-            sb.append(TAB + "keyFactory = new ObjectFactory(kClass);" + LF);
-        if (!valMarsh.isSimpleTypeMarshaller())
-            sb.append(TAB + "valFactory = new ObjectFactory(vClass);" + LF);
-        sb.append("}" + LF);
-
-        // Generate and append helper-methods.
-        generateTupleFactoryMethod(sb, schema, keyMarsh, valMarsh);
-
-        // Generate and append Serializer interface methods.
-        appendSerializeMethod(sb, keyMarsh, valMarsh);
-        writeDeserializeKeyMethod(sb, keyMarsh);
-        writeDeserializeValueMethod(sb, valMarsh);
-
-        return sb.toString();
-    }
-
-    /**
-     * Creates marshal/unmarshall expressions generator for object.
-     *
-     * @param aClass Object class.
-     * @param factoryRefExpr Factory reference expression.
-     * @param columns Columns that aClass mapped to.
-     * @param firstColIdx First column absolute index in schema.
-     * @return Marshal/unmarshall expression generator.
-     */
-    private MarshallerExprGenerator createObjectMarshaller(
-        Class<?> aClass,
-        @Nullable String factoryRefExpr,
-        Columns columns,
-        int firstColIdx
-    ) {
-        BinaryMode mode = MarshallerUtil.mode(aClass);
-
-        if (mode != null)
-            return new IdentityObjectMarshallerExprGenerator(FieldAccessExprGenerator.createIdentityAccessor(mode, firstColIdx));
-
-        FieldAccessExprGenerator[] accessors = new FieldAccessExprGenerator[columns.length()];
-        try {
-            for (int i = 0; i < columns.length(); i++) {
-                final Field field = aClass.getDeclaredField(columns.column(i).name());
-
-                accessors[i] = FieldAccessExprGenerator.createAccessor(
-                    MarshallerUtil.mode(field.getType()),
-                    firstColIdx + i /* schma absolute index. */,
-                    IgniteUnsafeUtils.objectFieldOffset(field));
-            }
-        }
-        catch (NoSuchFieldException ex) {
-            throw new IllegalStateException(ex);
-        }
-
-        return new MarshallerExprGenerator(factoryRefExpr, accessors);
-    }
-
-    /**
-     * Appends {@link Serializer#serialize(Object, Object)} method code.
-     *
-     * @param sb String buffer to append to.
-     * @param keyMarsh Marshall expression generator for key.
-     * @param valMarsh Marshall expression generator for value.
-     */
-    private void appendSerializeMethod(
-        StringBuilder sb,
-        MarshallerExprGenerator keyMarsh,
-        MarshallerExprGenerator valMarsh
-    ) {
-        // Mehtod signature.
-        sb.append(LF + "@Override public byte[] serialize(Object key, Object val) throws SerializationException {" + LF);
-        sb.append(TAB + "TupleAssembler asm = createAssembler(key, val);" + LF);
-
-        // Key marshal script.
-        sb.append(TAB + "{" + LF);
-        sb.append(TAB + TAB + "Object obj = key;" + LF);
-        keyMarsh.appendMarshallObjectExpr(sb, TAB + TAB);
-        sb.append(TAB + "}" + LF);
-
-        // Value marshal script.
-        sb.append(TAB + " {" + LF);
-        sb.append(TAB + TAB + "Object obj = val;" + LF);
-        valMarsh.appendMarshallObjectExpr(sb, TAB + TAB);
-        sb.append(TAB + "}" + LF);
-
-        // Return statement.
-        sb.append(TAB + "return asm.build();" + LF);
-        sb.append("}" + LF);
-    }
-
-    /**
-     * Appends {@link Serializer#deserializeKey(byte[])} method code.
-     *
-     * @param sb String buffer to append to.
-     * @param keyMarsh Unmarshall expression generator for key.
-     */
-    private void writeDeserializeKeyMethod(StringBuilder sb, MarshallerExprGenerator keyMarsh) {
-        // Mehtod signature.
-        sb.append(LF + "@Override public Object deserializeKey(byte[] data) throws SerializationException {" + LF);
-        sb.append(TAB + "Tuple tuple = new ByteBufferTuple(schema, data);" + LF);
-
-        // Key unmarshal script.
-        keyMarsh.appendUnmarshallObjectExpr(sb, TAB);
-
-        // Return statement.
-        sb.append(TAB + "return obj;" + LF);
-        sb.append("}" + LF);
-    }
-
-    /**
-     * Appends {@link Serializer#deserializeValue(byte[])} method code.
-     *
-     * @param sb String buffer to append to.
-     * @param valMarsh Unmarshall expression generator for value.
-     */
-    private void writeDeserializeValueMethod(StringBuilder sb, MarshallerExprGenerator valMarsh) {
-        // Mehtod signature.
-        sb.append(LF + "@Override public Object deserializeValue(byte[] data) throws SerializationException {" + LF);
-        sb.append(TAB + "Tuple tuple = new ByteBufferTuple(schema, data);" + LF);
-
-        // Key unmarshal script.
-        valMarsh.appendUnmarshallObjectExpr(sb, TAB);
-
-        // Return statement.
-        sb.append(TAB + "return obj;" + LF);
-        sb.append("}" + LF);
-    }
-
-    /**
-     * Appends helper methods code.
-     *
-     * @param sb String buffer to append to.
-     * @param schema Schema descriptor.
-     * @param keyMarsh Marshall expression generator for key.
-     * @param valMarsh Marshall expression generator for value.
-     */
-    private void generateTupleFactoryMethod(
-        StringBuilder sb,
-        SchemaDescriptor schema,
-        MarshallerExprGenerator keyMarsh,
-        MarshallerExprGenerator valMarsh
-    ) {
-        // Method signature.
-        sb.append(LF + "TupleAssembler createAssembler(Object key, Object val) {" + LF);
-        // Local variables.
-        sb.append(TAB + "int nonNullVarlenKeys = 0; int nonNullVarlenValues = 0;" + LF);
-        sb.append(TAB + "int nonNullVarlenKeysSize = 0; int nonNullVarlenValuesSize = 0;" + LF);
-        sb.append(LF);
-        sb.append(TAB + "Columns keyCols = schema.keyColumns();" + LF);
-        sb.append(TAB + "Columns valCols = schema.valueColumns();" + LF);
-        sb.append(LF);
-
-        Columns keyCols = schema.keyColumns();
-        if (keyCols.firstVarlengthColumn() >= 0) {
-            // Appends key analyzer code-block.
-            sb.append(TAB + "{" + LF);
-            sb.append(TAB + TAB + "Object fVal, obj = key;" + LF); // Temporary vars.
-
-            for (int i = keyCols.firstVarlengthColumn(); i < keyCols.length(); i++) {
-                assert !keyCols.column(i).type().spec().fixedLength();
-
-                sb.append(TAB + TAB + "assert !keyCols.column(").append(i).append(").type().spec().fixedLength();" + LF);
-                sb.append(TAB + TAB + "fVal = ").append(keyMarsh.accessors[i].getFieldExpr()).append(";" + LF);
-                sb.append(TAB + TAB + "if (fVal != null) {" + LF);
-                sb.append(TAB + TAB + TAB + "nonNullVarlenKeysSize += MarshallerUtil.getValueSize(fVal, keyCols.column(").append(i).append(").type());").append(LF);
-                sb.append(TAB + TAB + TAB + "nonNullVarlenKeys++;" + LF);
-                sb.append(TAB + TAB + "}" + LF);
-            }
-
-            sb.append(TAB + "}" + LF);
-        }
-
-        Columns valCols = schema.valueColumns();
-        if (valCols.firstVarlengthColumn() >= 0) {
-            // Appends value analyzer code-block.
-            sb.append(TAB + "{" + LF);
-            sb.append(TAB + TAB + "Object fVal, obj = val;" + LF); // Temporary vars.
-
-            for (int i = valCols.firstVarlengthColumn(); i < valCols.length(); i++) {
-                assert !valCols.column(i).type().spec().fixedLength();
-
-                sb.append(TAB + TAB + "assert !valCols.column(").append(i).append(").type().spec().fixedLength();" + LF);
-                sb.append(TAB + TAB + "fVal = ").append(valMarsh.accessors[i].getFieldExpr()).append(";" + LF);
-                sb.append(TAB + TAB + "if (fVal != null) {" + LF);
-                sb.append(TAB + TAB + TAB + "nonNullVarlenValuesSize += MarshallerUtil.getValueSize(fVal, valCols.column(").append(i).append(").type());" + LF);
-                sb.append(TAB + TAB + TAB + "nonNullVarlenValues++;" + LF);
-                sb.append(TAB + TAB + "}" + LF);
-            }
-            sb.append(TAB + "}" + LF);
-        }
-
-        // Calculate tuple size.
-        sb.append(LF);
-        sb.append(TAB + "int size = TupleAssembler.tupleSize(" + LF);
-        sb.append(TAB + TAB + "keyCols, nonNullVarlenKeys, nonNullVarlenKeysSize, " + LF);
-        sb.append(TAB + TAB + "valCols, nonNullVarlenValues, nonNullVarlenValuesSize); " + LF);
-        sb.append(LF);
-
-        // Return statement.
-        sb.append(TAB + "return new TupleAssembler(schema, size, nonNullVarlenKeys, nonNullVarlenValues);" + LF);
-        sb.append("}" + LF);
-    }
-}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/MarshallerCodeGenerator.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/MarshallerCodeGenerator.java
new file mode 100644
index 0000000..bca3256
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/MarshallerCodeGenerator.java
@@ -0,0 +1,47 @@
+package org.apache.ignite.internal.schema.marshaller.generator;
+
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.TypeSpec;
+
+/**
+ * Marshaller code generator.
+ */
+interface MarshallerCodeGenerator {
+    /**
+     * @return {@code true} if it is simple object marshaller, {@code false} otherwise.
+     */
+    boolean isSimpleType();
+
+    /**
+     * @param tupleExpr Tuple to read from.
+     * @return Unmarshall object code.
+     */
+    CodeBlock unmarshallObjectCode(String tupleExpr);
+
+    /**
+     * @param asm Tuple assembler to write to.
+     * @param objVar Object to serialize.
+     * @return Marshall object code.
+     */
+    CodeBlock marshallObjectCode(String asm, String objVar);
+
+    /**
+     * @param objVar Object var.
+     * @param colIdx Column index.
+     * @return Object field value for given column.
+     */
+    CodeBlock getValueCode(String objVar, int colIdx);
+
+    /**
+     * @param classBuilder Class builder.
+     * @param staticInitBuilder Static initializer builder.
+     */
+    void initStaticHandlers(TypeSpec.Builder classBuilder, CodeBlock.Builder staticInitBuilder);
+
+    /**
+     * @return Marshaller target class.
+     */
+    default Class<?> getClazz() {
+        return null;
+    }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/MarshallerExprGenerator.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/MarshallerExprGenerator.java
deleted file mode 100644
index 433b567..0000000
--- a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/MarshallerExprGenerator.java
+++ /dev/null
@@ -1,94 +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.schema.marshaller.generator;
-
-import org.apache.ignite.internal.schema.marshaller.Serializer;
-
-/**
- * Generate {@link Serializer} method's bodies.
- */
-class MarshallerExprGenerator {
-    /** Object factory regerence expression. */
-    private final String factoryRefExpr;
-
-    /** Object field access expression generators. */
-    protected FieldAccessExprGenerator[] accessors;
-
-    /**
-     * Constructor.
-     *
-     * @param factoryRefExpr Object factory regerence expression.
-     * @param accessors Object field access expression generators.
-     */
-    public MarshallerExprGenerator(String factoryRefExpr, FieldAccessExprGenerator[] accessors) {
-        this.accessors = accessors;
-        this.factoryRefExpr = factoryRefExpr;
-    }
-
-    /**
-     * @return {@code true} if it is simple object marshaller, {@code false} otherwise.
-     */
-    boolean isSimpleTypeMarshaller() {
-        return factoryRefExpr == null;
-    }
-
-    /**
-     * Appends unmashall object code to string builder.
-     *
-     * @param sb String builder.
-     * @param indent Line indentation.
-     */
-    public void appendUnmarshallObjectExpr(StringBuilder sb, String indent) {
-        assert factoryRefExpr != null;
-
-        sb.append(indent).append("Object obj;" + JaninoSerializerGenerator.LF);
-        // Try.
-        sb.append(indent).append("try {" + JaninoSerializerGenerator.LF);
-        sb.append(indent).append(JaninoSerializerGenerator.TAB + "obj = ").append(factoryRefExpr).append(".create();" + JaninoSerializerGenerator.LF);
-
-        // Read column from tuple to object field.
-        for (int i = 0; i < accessors.length; i++)
-            accessors[i].appendPutFieldExpr(sb, accessors[i].readColumnExpr(), indent + JaninoSerializerGenerator.TAB);
-
-        // Catch and rethrow wrapped exeption.
-        sb.append(indent).append("} catch (Exception ex) {" + JaninoSerializerGenerator.LF);
-        sb.append(indent).append(JaninoSerializerGenerator.TAB + "throw new SerializationException(\"Failed to instantiate object: \" + ")
-            .append(factoryRefExpr).append(".getClazz().getName(), ex);").append(JaninoSerializerGenerator.LF);
-        sb.append(indent).append("}" + JaninoSerializerGenerator.LF);
-    }
-
-    /**
-     * Appends mashall object code to string builder.
-     *
-     * @param sb String builder.
-     * @param indent Line indentation.
-     */
-    public void appendMarshallObjectExpr(StringBuilder sb, String indent) {
-        // Try.
-        sb.append(indent).append("try {" + JaninoSerializerGenerator.LF);
-
-        // Write object field to tuple assembler.
-        for (int i = 0; i < accessors.length; i++)
-            accessors[i].appendWriteColumnExpr(sb, accessors[i].getFieldExpr(), indent + JaninoSerializerGenerator.TAB);
-
-        // Catch and rethrow wrapped exeption.
-        sb.append(indent).append("} catch (Exception ex) {" + JaninoSerializerGenerator.LF);
-        sb.append(indent).append(JaninoSerializerGenerator.TAB + "throw new SerializationException(ex);").append(JaninoSerializerGenerator.LF);
-        sb.append(indent).append("}" + JaninoSerializerGenerator.LF);
-    }
-}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/ObjectMarshallerCodeGenerator.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/ObjectMarshallerCodeGenerator.java
new file mode 100644
index 0000000..36d0a27
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/ObjectMarshallerCodeGenerator.java
@@ -0,0 +1,124 @@
+/*
+ * 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.schema.marshaller.generator;
+
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.TypeSpec;
+import java.lang.invoke.VarHandle;
+import java.lang.reflect.Field;
+import javax.lang.model.element.Modifier;
+import org.apache.ignite.internal.schema.Columns;
+import org.apache.ignite.internal.schema.marshaller.MarshallerUtil;
+import org.apache.ignite.internal.schema.marshaller.Serializer;
+
+/**
+ * Generates {@link Serializer} methods code.
+ */
+class ObjectMarshallerCodeGenerator implements MarshallerCodeGenerator {
+    /** Target object factory var. */
+    private final String objectFactoryVar;
+
+    /** Target class. */
+    private final Class<?> tClass;
+
+    /** Mapped columns. */
+    private final Columns columns;
+
+    /** Object field access expression generators. */
+    private final TupleColumnAccessCodeGenerator[] columnAccessessors;
+
+    /**
+     * Constructor.
+     *
+     * @param tClass Target object class.
+     * @param objectFactoryVar Target object factory var.
+     * @param columns Column object is mapped to.
+     * @param firstColIdx Column index offset.
+     */
+    public ObjectMarshallerCodeGenerator(Class<?> tClass, String objectFactoryVar, Columns columns, int firstColIdx) {
+        this.objectFactoryVar = objectFactoryVar;
+        this.tClass = tClass;
+
+        this.columns = columns;
+        columnAccessessors = new TupleColumnAccessCodeGenerator[this.columns.length()];
+        try {
+            for (int i = 0; i < columns.length(); i++) {
+                final Field field = tClass.getDeclaredField(columns.column(i).name());
+
+                columnAccessessors[i] = TupleColumnAccessCodeGenerator.createAccessor(MarshallerUtil.mode(field.getType()), i + firstColIdx);
+            }
+        }
+        catch (NoSuchFieldException ex) {
+            throw new IllegalStateException(ex);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public Class<?> getClazz() {
+        return tClass;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isSimpleType() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public CodeBlock unmarshallObjectCode(String tupleExpr) {
+        final CodeBlock.Builder builder = CodeBlock.builder()
+            .addStatement("$T obj = $L.create()", tClass, objectFactoryVar);
+
+        for (int i = 0; i < columnAccessessors.length; i++)
+            builder.addStatement("FIELD_HANDLE_$L.set(obj, $L)", columnAccessessors[i].columnIdx(), columnAccessessors[i].read(tupleExpr));
+
+        builder.addStatement("return obj");
+        return builder.build();
+    }
+
+    /** {@inheritDoc} */
+    @Override public CodeBlock getValueCode(String objVar, int i) {
+        return CodeBlock.of("FIELD_HANDLE_$L.get($L)", columnAccessessors[i].columnIdx(), objVar);
+    }
+
+    /** {@inheritDoc} */
+    @Override public CodeBlock marshallObjectCode(String asm, String objVar) {
+        final CodeBlock.Builder builder = CodeBlock.builder();
+
+        for (int i = 0; i < columnAccessessors.length; i++)
+            builder.add(columnAccessessors[i].write(asm, getValueCode(objVar, i).toString()));
+
+        return builder.build();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void initStaticHandlers(TypeSpec.Builder builder, CodeBlock.Builder staticBuilder) {
+        for (int i = 0; i < columnAccessessors.length; i++) {
+            builder.addField(FieldSpec.builder(
+                VarHandle.class,
+                CodeBlock.of("FIELD_HANDLE_$L", columnAccessessors[i].columnIdx()).toString(),
+                Modifier.PRIVATE,
+                Modifier.FINAL,
+                Modifier.STATIC)
+                .build());
+
+            staticBuilder.addStatement("FIELD_HANDLE_$L = lookup.unreflectVarHandle($T.class.getDeclaredField($S))",
+                columnAccessessors[i].columnIdx(), tClass, columns.column(i).name());
+        }
+    }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/SerializerGenerator.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/SerializerGenerator.java
new file mode 100644
index 0000000..cea5c62
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/SerializerGenerator.java
@@ -0,0 +1,368 @@
+/*
+ * 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.schema.marshaller.generator;
+
+import com.squareup.javapoet.AnnotationSpec;
+import com.squareup.javapoet.ArrayTypeName;
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import com.squareup.javapoet.TypeSpec;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Objects;
+import javax.annotation.processing.Generated;
+import javax.lang.model.element.Modifier;
+import jdk.jfr.Experimental;
+import org.apache.ignite.internal.schema.Columns;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.Tuple;
+import org.apache.ignite.internal.schema.TupleAssembler;
+import org.apache.ignite.internal.schema.marshaller.AbstractSerializer;
+import org.apache.ignite.internal.schema.marshaller.BinaryMode;
+import org.apache.ignite.internal.schema.marshaller.CompilerUtils;
+import org.apache.ignite.internal.schema.marshaller.MarshallerUtil;
+import org.apache.ignite.internal.schema.marshaller.SerializationException;
+import org.apache.ignite.internal.schema.marshaller.Serializer;
+import org.apache.ignite.internal.schema.marshaller.SerializerFactory;
+import org.apache.ignite.internal.util.ObjectFactory;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * {@link Serializer} code generator.
+ */
+@Experimental
+public class SerializerGenerator implements SerializerFactory {
+    /** Serializer package name. */
+    public static final String SERIALIZER_PACKAGE_NAME = "org.apache.ignite.internal.schema.marshaller";
+
+    /** Serializer package name prefix. */
+    public static final String SERIALIZER_CLASS_NAME_PREFIX = "SerializerForSchema_";
+
+    /** {@inheritDoc} */
+    @Override public Serializer create(
+        SchemaDescriptor schema,
+        Class<?> keyClass,
+        Class<?> valClass
+    ) {
+        final String className = SERIALIZER_CLASS_NAME_PREFIX + schema.version();
+        try {
+            // Generate Serializer code.
+            long generation = System.nanoTime();
+            JavaFile javaFile = generateSerializerClassCode(className, schema, keyClass, valClass);
+            generation = System.nanoTime() - generation;
+
+            //TODO: pass code to logger on trace level.
+//            System.out.println("Serializer code generated in " + TimeUnit.NANOSECONDS.toMicros(generation) + "us");
+            //            System.out.println(javaFile.toString());
+
+            // Compile.
+            long compilation = System.nanoTime();
+            ClassLoader loader = CompilerUtils.compileCode(javaFile);
+            compilation = System.nanoTime() - compilation;
+
+            //            System.out.println("Serializer code compiled in " + TimeUnit.NANOSECONDS.toMicros(compilation) + "us");
+
+            // Instantiate serializer.
+            return (Serializer)loader.loadClass(javaFile.packageName + '.' + className)
+                .getDeclaredConstructor(SchemaDescriptor.class, Class.class, Class.class)
+                .newInstance(schema, keyClass, valClass);
+
+        }
+        catch (InstantiationException | ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+            throw new IllegalStateException("Failed to create serializer for key-value pair: schemaVer=" + schema.version() +
+                ", keyClass=" + keyClass.getSimpleName() + ", valueClass=" + valClass.getSimpleName(), e);
+        }
+    }
+
+    /**
+     * Generates serializer code.
+     *
+     * @param className Serializer class name.
+     * @param schema Schema descriptor.
+     * @param keyClass Key class.
+     * @param valClass Value class.
+     * @return Generated java file representation.
+     */
+    private JavaFile generateSerializerClassCode(String className, SchemaDescriptor schema, Class<?> keyClass,
+        Class<?> valClass) {
+        try {
+            // Build code generators.
+            final MarshallerCodeGenerator keyMarsh = createObjectMarshaller(keyClass, "keyFactory", schema.keyColumns(), 0);
+            final MarshallerCodeGenerator valMarsh = createObjectMarshaller(valClass, "valFactory", schema.valueColumns(), schema.keyColumns().length());
+
+            final TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className)
+                .superclass(AbstractSerializer.class)
+                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+                .addAnnotation(AnnotationSpec.builder(Generated.class).addMember("value", "$S", getClass().getCanonicalName()).build());
+
+            initFieldHandlers(keyMarsh, valMarsh, classBuilder);
+
+            classBuilder
+                .addField(ParameterizedTypeName.get(ObjectFactory.class, keyClass), "keyFactory", Modifier.PRIVATE, Modifier.FINAL)
+                .addField(ParameterizedTypeName.get(ObjectFactory.class, valClass), "valFactory", Modifier.PRIVATE, Modifier.FINAL)
+                .addMethod(
+                    // Constructor.
+                    MethodSpec.constructorBuilder()
+                        .addModifiers(Modifier.PUBLIC)
+                        .addParameter(SchemaDescriptor.class, "schema")
+                        .addParameter(Class.class, "keyClass")
+                        .addParameter(Class.class, "valClass")
+                        .addStatement("super(schema)")
+                        .addStatement("this.keyFactory = $T.factoryForClass(keyClass)", MarshallerUtil.class)
+                        .addStatement("this.valFactory = $T.factoryForClass(valClass)", MarshallerUtil.class)
+                        .build()
+                )
+                .addMethod(generateTupleAsseblerFactoryMethod(schema, keyMarsh, valMarsh))
+                .addMethod(generateSerializeMethod(keyMarsh, valMarsh))
+                .addMethod(generateDeserializeKeyMethod(keyMarsh))
+                .addMethod(generateDeserializeValueMethod(valMarsh));
+
+            return JavaFile
+                .builder(SERIALIZER_PACKAGE_NAME, classBuilder.build())
+                .addStaticImport(MethodHandles.class, "Lookup")
+                .skipJavaLangImports(true)
+                .indent("    ")
+                .build();
+        }
+        catch (Exception ex) {
+            //TODO: fallback to java serializer?
+            throw new IllegalStateException(ex);
+        }
+    }
+
+    /**
+     * @param keyMarsh Key marshaller code generator.
+     * @param valMarsh Value marshaller code generator.
+     * @param classBuilder Serializer class builder.
+     */
+    private void initFieldHandlers(
+        MarshallerCodeGenerator keyMarsh,
+        MarshallerCodeGenerator valMarsh,
+        TypeSpec.Builder classBuilder
+    ) {
+        if (keyMarsh.isSimpleType() && valMarsh.isSimpleType())
+            return; // No field hanlders needed for simple types.
+
+        final CodeBlock.Builder staticInitBuilder = CodeBlock.builder()
+            .addStatement("$T.Lookup lookup", MethodHandles.class)
+            .beginControlFlow("try");
+
+        if (!keyMarsh.isSimpleType()) {
+            staticInitBuilder.addStatement(
+                "lookup = $T.privateLookupIn($T.class, $T.lookup())",
+                MethodHandles.class,
+                Objects.requireNonNull(keyMarsh.getClazz()),
+                MethodHandles.class
+            );
+
+            keyMarsh.initStaticHandlers(classBuilder, staticInitBuilder);
+        }
+
+        if (!valMarsh.isSimpleType()) {
+            staticInitBuilder.addStatement(
+                "lookup = $T.privateLookupIn($T.class, $T.lookup())",
+                MethodHandles.class,
+                Objects.requireNonNull(valMarsh.getClazz()),
+                MethodHandles.class
+            );
+
+            valMarsh.initStaticHandlers(classBuilder, staticInitBuilder);
+        }
+
+        staticInitBuilder
+            .nextControlFlow(
+                "catch ($T | $T ex)",
+                ReflectiveOperationException.class,
+                SecurityException.class
+            )
+            .addStatement("throw new $T(ex)", IllegalStateException.class)
+            .endControlFlow();
+
+        classBuilder.addStaticBlock(staticInitBuilder.build());
+    }
+
+    /**
+     * @param schema Schema descriptor.
+     * @param keyMarsh Key marshaller code generator.
+     * @param valMarsh Value marshaller code generator.
+     * @return Tuple accembler factory method spec.
+     */
+    private MethodSpec generateTupleAsseblerFactoryMethod(SchemaDescriptor schema, MarshallerCodeGenerator keyMarsh,
+        MarshallerCodeGenerator valMarsh) {
+        final MethodSpec.Builder builder = MethodSpec
+            .methodBuilder("createAssembler")
+            .addAnnotation(Override.class)
+            .addModifiers(Modifier.PROTECTED, Modifier.FINAL)
+            .addParameter(Object.class, "key", Modifier.FINAL)
+            .addParameter(Object.class, "val", Modifier.FINAL)
+            .returns(TupleAssembler.class)
+
+            .addStatement("int varlenKeyCols = 0; int varlenValueCols = 0")
+            .addStatement("int varlenKeyColsSize = 0; int varlenValueColsSize = 0")
+            .addStatement("$T keyCols = schema.keyColumns()", Columns.class)
+            .addStatement("$T valCols = schema.valueColumns()", Columns.class);
+
+        Columns keyCols = schema.keyColumns();
+        if (keyCols.firstVarlengthColumn() >= 0) {
+            final CodeBlock.Builder block = CodeBlock.builder().indent()
+                .addStatement("$T fVal", Object.class);// Temporary vars.
+
+            for (int i = keyCols.firstVarlengthColumn(); i < keyCols.length(); i++) {
+                assert !keyCols.column(i).type().spec().fixedLength();
+
+                block.addStatement("fVal = $L", keyMarsh.getValueCode("key", i).toString());
+
+                block.beginControlFlow("if (fVal != null)")
+                    .addStatement("varlenKeyColsSize += $T.getValueSize(fVal, keyCols.column($L).type())", MarshallerUtil.class, i)
+                    .addStatement("varlenKeyCols++")
+                    .endControlFlow();
+            }
+            block.unindent();
+
+            builder
+                .addCode("{\n")
+                .addCode(block.build())
+                .addCode("}\n");
+        }
+
+        Columns valCols = schema.valueColumns();
+        if (valCols.firstVarlengthColumn() >= 0) {
+            final CodeBlock.Builder block = CodeBlock.builder().indent()
+                .addStatement("$T fVal", Object.class);// Temporary vars.
+
+            for (int i = valCols.firstVarlengthColumn(); i < valCols.length(); i++) {
+                assert !valCols.column(i).type().spec().fixedLength();
+
+                block.addStatement("fVal = $L", valMarsh.getValueCode("val", i).toString());
+
+                block.beginControlFlow("if (fVal != null)")
+                    .addStatement("varlenValueColsSize += $T.getValueSize(fVal, valCols.column($L).type())", MarshallerUtil.class, i)
+                    .addStatement("varlenValueCols++")
+                    .endControlFlow();
+            }
+            block.unindent();
+
+            builder
+                .addCode("{\n")
+                .addCode(block.build())
+                .addCode("}\n");
+        }
+
+        builder.addStatement("int size = $T.tupleSize(keyCols, varlenKeyCols, varlenKeyColsSize," +
+            "valCols, varlenValueCols, varlenValueColsSize)", TupleAssembler.class);
+
+        builder.addStatement("return new $T(schema, size, varlenKeyCols, varlenValueCols)", TupleAssembler.class);
+
+        return builder.build();
+    }
+
+    /**
+     * @param valMarsh Value marshaller code generator.
+     * @return Deserialize value method spec.
+     */
+    private MethodSpec generateDeserializeValueMethod(MarshallerCodeGenerator valMarsh) {
+        return MethodSpec
+            .methodBuilder("deserializeValue0")
+            .addAnnotation(Override.class)
+            .addModifiers(Modifier.PROTECTED, Modifier.FINAL)
+            .addParameter(Tuple.class, "tuple", Modifier.FINAL)
+            .addException(SerializationException.class)
+            .returns(TypeName.OBJECT)
+
+            .beginControlFlow("try")
+            .addCode(valMarsh.unmarshallObjectCode("tuple"))
+            .nextControlFlow("catch($T th)", Throwable.class)
+            .addStatement("throw new $T(th)", SerializationException.class)
+            .endControlFlow()
+            .build();
+    }
+
+    /**
+     * @param keyMarsh Key marshaller code generator.
+     * @return Deserialize key method spec.
+     */
+    private MethodSpec generateDeserializeKeyMethod(MarshallerCodeGenerator keyMarsh) {
+        return MethodSpec
+            .methodBuilder("deserializeKey0")
+            .addAnnotation(Override.class)
+            .addModifiers(Modifier.PROTECTED, Modifier.FINAL)
+            .addParameter(Tuple.class, "tuple", Modifier.FINAL)
+            .addException(SerializationException.class)
+            .returns(TypeName.OBJECT)
+
+            .beginControlFlow("try")
+            .addCode(keyMarsh.unmarshallObjectCode("tuple"))
+            .nextControlFlow("catch($T th)", Throwable.class)
+            .addStatement("throw new $T(th)", SerializationException.class)
+            .endControlFlow()
+            .build();
+    }
+
+    /**
+     * @param keyMarsh Key marshaller code generator.
+     * @param valMarsh Value marshaller code generator.
+     * @return Serialize method spec.
+     */
+    private MethodSpec generateSerializeMethod(MarshallerCodeGenerator keyMarsh, MarshallerCodeGenerator valMarsh) {
+        return MethodSpec.
+            methodBuilder("serialize0")
+            .addAnnotation(Override.class)
+            .addModifiers(Modifier.PROTECTED, Modifier.FINAL)
+            .addParameter(TupleAssembler.class, "asm", Modifier.FINAL)
+            .addParameter(TypeName.OBJECT, "key", Modifier.FINAL)
+            .addParameter(TypeName.OBJECT, "val", Modifier.FINAL)
+            .addException(SerializationException.class)
+            .returns(ArrayTypeName.of(TypeName.BYTE))
+
+            .beginControlFlow("try")
+            .addCode(keyMarsh.marshallObjectCode("asm", "key"))
+            .addCode(valMarsh.marshallObjectCode("asm", "val"))
+            .addStatement("return asm.build()")
+
+            .nextControlFlow("catch($T th)", Throwable.class)
+            .addStatement("throw new $T(th)", SerializationException.class)
+            .endControlFlow()
+            .build();
+    }
+
+    /**
+     * Creates marshaller code generator for given class.
+     *
+     * @param tClass Target class.
+     * @param factoryRefVar Object factory variable.
+     * @param columns Columns that tClass mapped to.
+     * @param firstColIdx First column absolute index in schema.
+     * @return Marshaller code generator.
+     */
+    private MarshallerCodeGenerator createObjectMarshaller(
+        Class<?> tClass,
+        @Nullable String factoryRefVar,
+        Columns columns,
+        int firstColIdx
+    ) {
+        BinaryMode mode = MarshallerUtil.mode(tClass);
+
+        if (mode != null) // Simple type.
+            return new IdentityObjectMarshallerExprGenerator(TupleColumnAccessCodeGenerator.createAccessor(mode, firstColIdx));
+        else
+            return new ObjectMarshallerCodeGenerator(tClass, factoryRefVar, columns, firstColIdx);
+    }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/TupleColumnAccessCodeGenerator.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/TupleColumnAccessCodeGenerator.java
new file mode 100644
index 0000000..c6addcf
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/TupleColumnAccessCodeGenerator.java
@@ -0,0 +1,257 @@
+/*
+ * 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.schema.marshaller.generator;
+
+import com.squareup.javapoet.CodeBlock;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.BitSet;
+import java.util.UUID;
+import org.apache.ignite.internal.schema.Tuple;
+import org.apache.ignite.internal.schema.TupleAssembler;
+import org.apache.ignite.internal.schema.marshaller.BinaryMode;
+
+/**
+ * Tuple access code generator.
+ */
+public class TupleColumnAccessCodeGenerator {
+    /** Tuple method handler. */
+    public static final MethodHandle READ_BYTE;
+    /** Tuple method handler. */
+    public static final MethodHandle READ_SHORT;
+    /** Tuple method handler. */
+    public static final MethodHandle READ_INT;
+    /** Tuple method handler. */
+    public static final MethodHandle READ_LONG;
+    /** Tuple method handler. */
+    public static final MethodHandle READ_FLOAT;
+    /** Tuple method handler. */
+    public static final MethodHandle READ_DOUBLE;
+    /** Tuple method handler. */
+    public static final MethodHandle READ_BYTE_BOXED;
+    /** Tuple method handler. */
+    public static final MethodHandle READ_SHORT_BOXED;
+    /** Tuple method handler. */
+    public static final MethodHandle READ_INT_BOXED;
+    /** Tuple method handler. */
+    public static final MethodHandle READ_LONG_BOXED;
+    /** Tuple method handler. */
+    public static final MethodHandle READ_FLOAT_BOXED;
+    /** Tuple method handler. */
+    public static final MethodHandle READ_DOUBLE_BOXED;
+    /** Tuple method handler. */
+    public static final MethodHandle READ_UUID;
+    /** Tuple method handler. */
+    public static final MethodHandle READ_BITSET;
+    /** Tuple method handler. */
+    public static final MethodHandle READ_STRING;
+    /** Tuple method handler. */
+    public static final MethodHandle READ_BYTE_ARR;
+
+    /** Tuple assembler method handler. */
+    public static final MethodHandle WRITE_NULL;
+    /** Tuple assembler method handler. */
+    public static final MethodHandle WRITE_BYTE;
+    /** Tuple assembler method handler. */
+    public static final MethodHandle WRITE_SHORT;
+    /** Tuple assembler method handler. */
+    public static final MethodHandle WRITE_INT;
+    /** Tuple assembler method handler. */
+    public static final MethodHandle WRITE_LONG;
+    /** Tuple assembler method handler. */
+    public static final MethodHandle WRITE_FLOAT;
+    /** Tuple assembler method handler. */
+    public static final MethodHandle WRITE_DOUBLE;
+    /** Tuple assembler method handler. */
+    public static final MethodHandle WRITE_UUID;
+    /** Tuple assembler method handler. */
+    public static final MethodHandle WRITE_BITSET;
+    /** Tuple assembler method handler. */
+    public static final MethodHandle WRITE_STRING;
+    /** Tuple assembler method handler. */
+    public static final MethodHandle WRITE_BYTE_ARR;
+
+    /**
+     * Initializes static handlers.
+     */
+    static {
+        try {
+            MethodHandles.Lookup lookup = MethodHandles.lookup();
+
+            READ_BYTE = lookup.findVirtual(Tuple.class, "byteValue", MethodType.methodType(byte.class, int.class));
+            READ_SHORT = lookup.findVirtual(Tuple.class, "shortValue", MethodType.methodType(short.class, int.class));
+            READ_INT = lookup.findVirtual(Tuple.class, "intValue", MethodType.methodType(int.class, int.class));
+            READ_LONG = lookup.findVirtual(Tuple.class, "longValue", MethodType.methodType(long.class, int.class));
+            READ_FLOAT = lookup.findVirtual(Tuple.class, "floatValue", MethodType.methodType(float.class, int.class));
+            READ_DOUBLE = lookup.findVirtual(Tuple.class, "doubleValue", MethodType.methodType(double.class, int.class));
+            READ_BYTE_BOXED = lookup.findVirtual(Tuple.class, "byteValueBoxed", MethodType.methodType(Byte.class, int.class));
+            READ_SHORT_BOXED = lookup.findVirtual(Tuple.class, "shortValueBoxed", MethodType.methodType(Short.class, int.class));
+            READ_INT_BOXED = lookup.findVirtual(Tuple.class, "intValueBoxed", MethodType.methodType(Integer.class, int.class));
+            READ_LONG_BOXED = lookup.findVirtual(Tuple.class, "longValueBoxed", MethodType.methodType(Long.class, int.class));
+            READ_FLOAT_BOXED = lookup.findVirtual(Tuple.class, "floatValueBoxed", MethodType.methodType(Float.class, int.class));
+            READ_DOUBLE_BOXED = lookup.findVirtual(Tuple.class, "doubleValueBoxed", MethodType.methodType(Double.class, int.class));
+            READ_UUID = lookup.findVirtual(Tuple.class, "uuidValue", MethodType.methodType(UUID.class, int.class));
+            READ_BITSET = lookup.findVirtual(Tuple.class, "bitmaskValue", MethodType.methodType(BitSet.class, int.class));
+            READ_STRING = lookup.findVirtual(Tuple.class, "stringValue", MethodType.methodType(String.class, int.class));
+            READ_BYTE_ARR = lookup.findVirtual(Tuple.class, "bytesValue", MethodType.methodType(byte[].class, int.class));
+
+            WRITE_NULL = lookup.findVirtual(TupleAssembler.class, "appendNull", MethodType.methodType(void.class));
+            WRITE_BYTE = lookup.findVirtual(TupleAssembler.class, "appendByte", MethodType.methodType(void.class, byte.class));
+            WRITE_SHORT = lookup.findVirtual(TupleAssembler.class, "appendShort", MethodType.methodType(void.class, short.class));
+            WRITE_INT = lookup.findVirtual(TupleAssembler.class, "appendInt", MethodType.methodType(void.class, int.class));
+            WRITE_LONG = lookup.findVirtual(TupleAssembler.class, "appendLong", MethodType.methodType(void.class, long.class));
+            WRITE_FLOAT = lookup.findVirtual(TupleAssembler.class, "appendFloat", MethodType.methodType(void.class, float.class));
+            WRITE_DOUBLE = lookup.findVirtual(TupleAssembler.class, "appendDouble", MethodType.methodType(void.class, double.class));
+            WRITE_UUID = lookup.findVirtual(TupleAssembler.class, "appendUuid", MethodType.methodType(void.class, UUID.class));
+            WRITE_BITSET = lookup.findVirtual(TupleAssembler.class, "appendBitmask", MethodType.methodType(void.class, BitSet.class));
+            WRITE_STRING = lookup.findVirtual(TupleAssembler.class, "appendString", MethodType.methodType(void.class, String.class));
+            WRITE_BYTE_ARR = lookup.findVirtual(TupleAssembler.class, "appendBytes", MethodType.methodType(void.class, byte[].class));
+        }
+        catch (NoSuchMethodException | IllegalAccessException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    /**
+     * @param mode Binary mode.
+     * @param colIdx Column index in schema.
+     * @return Tuple column access code generator.
+     */
+    static TupleColumnAccessCodeGenerator createAccessor(BinaryMode mode, int colIdx) {
+        switch (mode) {
+            case P_BYTE:
+                return new TupleColumnAccessCodeGenerator("READ_BYTE", "WRITE_BYTE", byte.class, colIdx);
+            case P_SHORT:
+                return new TupleColumnAccessCodeGenerator("READ_SHORT", "WRITE_SHORT", short.class, colIdx);
+            case P_INT:
+                return new TupleColumnAccessCodeGenerator("READ_INT", "WRITE_INT", int.class, colIdx);
+            case P_LONG:
+                return new TupleColumnAccessCodeGenerator("READ_LONG", "WRITE_LONG", long.class, colIdx);
+            case P_FLOAT:
+                return new TupleColumnAccessCodeGenerator("READ_FLOAT", "WRITE_FLOAT", float.class, colIdx);
+            case P_DOUBLE:
+                return new TupleColumnAccessCodeGenerator("READ_DOUBLE", "WRITE_DOUBLE", double.class, colIdx);
+            case BYTE:
+                return new TupleColumnAccessCodeGenerator("READ_BYTE_BOXED", "WRITE_BYTE", Byte.class, byte.class, colIdx);
+            case SHORT:
+                return new TupleColumnAccessCodeGenerator("READ_SHORT_BOXED", "WRITE_SHORT", Short.class, short.class, colIdx);
+            case INT:
+                return new TupleColumnAccessCodeGenerator("READ_INT_BOXED", "WRITE_INT", Integer.class, int.class, colIdx);
+            case LONG:
+                return new TupleColumnAccessCodeGenerator("READ_LONG_BOXED", "WRITE_LONG", Long.class, long.class, colIdx);
+            case FLOAT:
+                return new TupleColumnAccessCodeGenerator("READ_FLOAT_BOXED", "WRITE_FLOAT", Float.class, float.class, colIdx);
+            case DOUBLE:
+                return new TupleColumnAccessCodeGenerator("READ_DOUBLE_BOXED", "WRITE_DOUBLE", Double.class, double.class, colIdx);
+            case STRING:
+                return new TupleColumnAccessCodeGenerator("READ_STRING", "WRITE_STRING", String.class, colIdx);
+            case UUID:
+                return new TupleColumnAccessCodeGenerator("READ_UUID", "WRITE_UUID", UUID.class, colIdx);
+            case BYTE_ARR:
+                return new TupleColumnAccessCodeGenerator("READ_BYTE_ARR", "WRITE_BYTE_ARR", byte[].class, colIdx);
+            case BITSET:
+                return new TupleColumnAccessCodeGenerator("READ_BITSET", "WRITE_BITSET", BitSet.class, colIdx);
+        }
+
+        throw new IllegalStateException("Unsupported binary mode: " + mode);
+    }
+
+    /** Reader handle name. */
+    private final String readHandleName;
+
+    /** Writer handle name. */
+    private final String writeHandleName;
+
+    /** Mapped value type. */
+    private final Class<?> mappedType;
+
+    /** Write method argument type. */
+    private final Class<?> writeArgType;
+
+    /** Column index in schema. */
+    private final int colIdx;
+
+    /**
+     * Constructor.
+     *
+     * @param readHandleName Reader handle name.
+     * @param writeHandleName Writer handle name.
+     * @param mappedType Mapped value type.
+     * @param colIdx Column index in schema.
+     */
+    TupleColumnAccessCodeGenerator(String readHandleName, String writeHandleName, Class<?> mappedType, int colIdx) {
+        this(readHandleName, writeHandleName, mappedType, mappedType, colIdx);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param readHandleName Reader handle name.
+     * @param writeHandleName Writer handle name.
+     * @param mappedType Mapped value type.
+     * @param writeArgType Write method argument type.
+     * @param colIdx Column index in schema.
+     */
+    TupleColumnAccessCodeGenerator(String readHandleName, String writeHandleName, Class<?> mappedType,
+        Class<?> writeArgType, int colIdx) {
+        this.readHandleName = readHandleName;
+        this.writeHandleName = writeHandleName;
+        this.colIdx = colIdx;
+        this.mappedType = mappedType;
+        this.writeArgType = writeArgType;
+    }
+
+    /**
+     * @return Column index in schema.
+     */
+    public int columnIdx() {
+        return colIdx;
+    }
+
+    /**
+     * @param tuple Tuple.
+     * @return Code that reads column value from tuple.
+     */
+    public CodeBlock read(String tuple) {
+        return CodeBlock.of("($T)$T.$L.invokeExact($L, $L)", mappedType, TupleColumnAccessCodeGenerator.class, readHandleName, tuple, colIdx);
+    }
+
+    /**
+     * @param asmVar Tuple assembler var.
+     * @param valExpr Value expression.
+     * @return Code that writes value to tuple column.
+     */
+    public CodeBlock write(String asmVar, String valExpr) {
+        if (mappedType.isPrimitive())
+            return CodeBlock.builder().addStatement("$T.$L.invokeExact($L, ($T)$L)", TupleColumnAccessCodeGenerator.class, writeHandleName, asmVar, writeArgType, valExpr).build();
+        else {
+            return CodeBlock.builder()
+                .add("{\n").indent()
+                .addStatement("Object fVal")
+                .beginControlFlow("if((fVal = $L) == null)", valExpr)
+                .addStatement("$T.WRITE_NULL.invokeExact($L)", TupleColumnAccessCodeGenerator.class, asmVar)
+                .nextControlFlow("else")
+                .addStatement("$T.$L.invokeExact($L, ($T)fVal)", TupleColumnAccessCodeGenerator.class, writeHandleName, asmVar, writeArgType)
+                .endControlFlow()
+                .unindent()
+                .add("}\n")
+                .build();
+        }
+    }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/UnsafeFieldAccessor.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/FieldAccessor.java
similarity index 50%
rename from modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/UnsafeFieldAccessor.java
rename to modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/FieldAccessor.java
index 72b2491..e8023fb 100644
--- a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/UnsafeFieldAccessor.java
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/FieldAccessor.java
@@ -17,8 +17,12 @@
 
 package org.apache.ignite.internal.schema.marshaller.reflection;
 
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
 import java.lang.reflect.Field;
+import java.util.BitSet;
 import java.util.Objects;
+import java.util.UUID;
 import org.apache.ignite.internal.schema.Column;
 import org.apache.ignite.internal.schema.Columns;
 import org.apache.ignite.internal.schema.Tuple;
@@ -26,32 +30,18 @@ import org.apache.ignite.internal.schema.TupleAssembler;
 import org.apache.ignite.internal.schema.marshaller.BinaryMode;
 import org.apache.ignite.internal.schema.marshaller.MarshallerUtil;
 import org.apache.ignite.internal.schema.marshaller.SerializationException;
-import org.apache.ignite.internal.util.IgniteUnsafeUtils;
 import org.jetbrains.annotations.Nullable;
 
 /**
  * Field accessor to speedup access.
  */
-// TODO: Extract interface, move to java-8 profile and add Java9+ implementation using VarHandles.
-public abstract class UnsafeFieldAccessor {
-    /**
-     * TODO: implement sesitive information filtering.
-     *
-     * @return {@code False} if sensitive information exoising is prohibited, {@code false} otherwise.
-     */
-    private static boolean includeSensitive() {
-        return true;
-    }
-
-    /** Offset. */
-    protected final long offset;
+public abstract class FieldAccessor {
+    /** VarHandle. */
+    protected final VarHandle varHandle;
 
     /** Mode. */
     protected final BinaryMode mode;
 
-    /** Field name */
-    protected final String name;
-
     /**
      * Mapped column position in schema.
      * <p>
@@ -67,7 +57,7 @@ public abstract class UnsafeFieldAccessor {
      * @param colIdx Column index in schema.
      * @return Accessor.
      */
-    static UnsafeFieldAccessor create(Class<?> type, Column col, int colIdx) {
+    static FieldAccessor create(Class<?> type, Column col, int colIdx) {
         try {
             final Field field = type.getDeclaredField(col.name());
 
@@ -75,25 +65,28 @@ public abstract class UnsafeFieldAccessor {
                 throw new IllegalArgumentException("Failed to map non-nullable field to nullable column [name=" + field.getName() + ']');
 
             BinaryMode mode = MarshallerUtil.mode(field.getType());
+            final MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(type, MethodHandles.lookup());
+
+            VarHandle varHandle = lookup.unreflectVarHandle(field);
 
             switch (mode) {
                 case P_BYTE:
-                    return new BytePrimitiveAccessor(field, colIdx);
+                    return new BytePrimitiveAccessor(varHandle, colIdx);
 
                 case P_SHORT:
-                    return new ShortPrimitiveAccessor(field, colIdx);
+                    return new ShortPrimitiveAccessor(varHandle, colIdx);
 
                 case P_INT:
-                    return new IntPrimitiveAccessor(field, colIdx);
+                    return new IntPrimitiveAccessor(varHandle, colIdx);
 
                 case P_LONG:
-                    return new LongPrimitiveAccessor(field, colIdx);
+                    return new LongPrimitiveAccessor(varHandle, colIdx);
 
                 case P_FLOAT:
-                    return new FloatPrimitiveAccessor(field, colIdx);
+                    return new FloatPrimitiveAccessor(varHandle, colIdx);
 
                 case P_DOUBLE:
-                    return new DoublePrimitiveAccessor(field, colIdx);
+                    return new DoublePrimitiveAccessor(varHandle, colIdx);
 
                 case BYTE:
                 case SHORT:
@@ -105,7 +98,7 @@ public abstract class UnsafeFieldAccessor {
                 case UUID:
                 case BYTE_ARR:
                 case BITSET:
-                    return new ReferenceFieldAccessor(field, colIdx, mode);
+                    return new ReferenceFieldAccessor(varHandle, colIdx, mode);
 
                 default:
                     assert false : "Invalid mode " + mode;
@@ -113,7 +106,7 @@ public abstract class UnsafeFieldAccessor {
 
             throw new IllegalArgumentException("Failed to create accessor for field [name=" + field.getName() + ']');
         }
-        catch (NoSuchFieldException | SecurityException ex) {
+        catch (NoSuchFieldException | SecurityException | IllegalAccessException ex) {
             throw new IllegalArgumentException(ex);
         }
     }
@@ -126,7 +119,7 @@ public abstract class UnsafeFieldAccessor {
      * @param mode Binary mode.
      * @return Accessor.
      */
-    static UnsafeFieldAccessor createIdentityAccessor(Column col, int colIdx, BinaryMode mode) {
+    static FieldAccessor createIdentityAccessor(Column col, int colIdx, BinaryMode mode) {
         switch (mode) {
             //  Marshaller read/write object contract methods allowed boxed types only.
             case P_BYTE:
@@ -157,103 +150,229 @@ public abstract class UnsafeFieldAccessor {
     }
 
     /**
-     * Protected constructor.
+     * Reads value object from tuple.
      *
-     * @param field Field.
+     * @param reader Reader.
      * @param colIdx Column index.
-     * @param mode Binary mode;
+     * @param mode Binary read mode.
+     * @return Read value object.
      */
-    protected UnsafeFieldAccessor(Field field, int colIdx, BinaryMode mode) {
-        assert field != null;
+    private static Object readRefValue(Tuple reader, int colIdx, BinaryMode mode) {
+        assert reader != null;
         assert colIdx >= 0;
-        assert mode != null;
 
-        this.colIdx = colIdx;
-        this.mode = mode;
-        offset = IgniteUnsafeUtils.objectFieldOffset(field);
-        name = field.getName();
+        Object val = null;
+
+        switch (mode) {
+            case BYTE:
+                val = reader.byteValueBoxed(colIdx);
+
+                break;
+
+            case SHORT:
+                val = reader.shortValueBoxed(colIdx);
+
+                break;
+
+            case INT:
+                val = reader.intValueBoxed(colIdx);
+
+                break;
+
+            case LONG:
+                val = reader.longValueBoxed(colIdx);
+
+                break;
+
+            case FLOAT:
+                val = reader.floatValueBoxed(colIdx);
+
+                break;
+
+            case DOUBLE:
+                val = reader.doubleValueBoxed(colIdx);
+
+                break;
+
+            case STRING:
+                val = reader.stringValue(colIdx);
+
+                break;
+
+            case UUID:
+                val = reader.uuidValue(colIdx);
+
+                break;
+
+            case BYTE_ARR:
+                val = reader.bytesValue(colIdx);
+
+                break;
+
+            case BITSET:
+                val = reader.bitmaskValue(colIdx);
+
+                break;
+
+            default:
+                assert false : "Invalid mode: " + mode;
+        }
+
+        return val;
+    }
+
+    /**
+     * Writes reference value to tuple.
+     *
+     * @param val Value object.
+     * @param writer Writer.
+     * @param mode Write binary mode.
+     */
+    private static void writeRefObject(Object val, TupleAssembler writer, BinaryMode mode) {
+        assert writer != null;
+
+        if (val == null) {
+            writer.appendNull();
+
+            return;
+        }
+
+        switch (mode) {
+            case BYTE:
+                writer.appendByte((Byte)val);
+
+                break;
+
+            case SHORT:
+                writer.appendShort((Short)val);
+
+                break;
+
+            case INT:
+                writer.appendInt((Integer)val);
+
+                break;
+
+            case LONG:
+                writer.appendLong((Long)val);
+
+                break;
+
+            case FLOAT:
+                writer.appendFloat((Float)val);
+
+                break;
+
+            case DOUBLE:
+                writer.appendDouble((Double)val);
+
+                break;
+
+            case STRING:
+                writer.appendString((String)val);
+
+                break;
+
+            case UUID:
+                writer.appendUuid((UUID)val);
+
+                break;
+
+            case BYTE_ARR:
+                writer.appendBytes((byte[])val);
+
+                break;
+
+            case BITSET:
+                writer.appendBitmask((BitSet)val);
+
+                break;
+
+            default:
+                assert false : "Invalid mode: " + mode;
+        }
     }
 
     /**
      * Protected constructor.
      *
+     * @param varHandle Field.
      * @param colIdx Column index.
      * @param mode Binary mode;
      */
-    private UnsafeFieldAccessor(int colIdx, BinaryMode mode) {
+    protected FieldAccessor(VarHandle varHandle, int colIdx, BinaryMode mode) {
+        assert varHandle != null;
         assert colIdx >= 0;
         assert mode != null;
 
         this.colIdx = colIdx;
         this.mode = mode;
-        offset = 0;
-        name = null;
+        this.varHandle = varHandle;
     }
 
     /**
-     * Get binary read/write mode.
+     * Protected constructor.
      *
-     * @return Binary mode.
+     * @param colIdx Column index.
+     * @param mode Binary mode;
      */
-    public BinaryMode mode() {
-        return mode;
+    private FieldAccessor(int colIdx, BinaryMode mode) {
+        assert colIdx >= 0;
+        assert mode != null;
+
+        this.colIdx = colIdx;
+        this.mode = mode;
+        varHandle = null;
     }
 
     /**
      * Write object field value to tuple.
      *
-     * @param obj Source object.
      * @param writer Tuple writer.
+     * @param obj Source object.
      * @throws SerializationException If failed.
      */
-    public void write(Object obj, TupleAssembler writer) throws SerializationException {
+    public void write(TupleAssembler writer, Object obj) throws SerializationException {
         try {
-            write0(Objects.requireNonNull(obj), writer);
+            write0(writer, obj);
         }
         catch (Exception ex) {
-            if (includeSensitive() && name != null)
-                throw new SerializationException("Failed to read field [id=" + colIdx + ']', ex);
-            else
-                throw new SerializationException("Failed to write field [id=" + colIdx + ']', ex);
+            throw new SerializationException("Failed to write field [id=" + colIdx + ']', ex);
         }
     }
 
     /**
      * Write object field value to tuple.
      *
-     * @param obj Source object.
      * @param writer Tuple writer.
-     * @throws IllegalAccessException If failed.
+     * @param obj Source object.
      */
-    protected abstract void write0(Object obj, TupleAssembler writer) throws IllegalAccessException;
+    protected abstract void write0(TupleAssembler writer, Object obj) throws Exception;
 
     /**
      * Reads value fom tuple to object field.
      *
-     * @param obj Target object.
      * @param reader Tuple reader.
+     * @param obj Target object.
      * @throws SerializationException If failed.
      */
-    public void read(Object obj, Tuple reader) throws SerializationException {
+    public void read(Tuple reader, Object obj) throws SerializationException {
         try {
-            read0(Objects.requireNonNull(obj), reader);
+            read0(reader, Objects.requireNonNull(obj));
         }
         catch (Exception ex) {
-            if (includeSensitive() && name != null)
-                throw new SerializationException("Failed to read field [name=" + name + ']', ex);
-            else
-                throw new SerializationException("Failed to read field [id=" + colIdx + ']', ex);
+            throw new SerializationException("Failed to read field [id=" + colIdx + ']', ex);
         }
     }
 
     /**
      * Reads value fom tuple to object field.
      *
-     * @param obj Target object.
      * @param reader Tuple reader.
-     * @throws IllegalAccessException If failed.
+     * @param obj Target object.
+     * @throws Exception If failed.
      */
-    protected abstract void read0(Object obj, Tuple reader) throws IllegalAccessException;
+    protected abstract void read0(Tuple reader, Object obj) throws Exception;
 
     /**
      * Read value.
@@ -261,7 +380,7 @@ public abstract class UnsafeFieldAccessor {
      * @param reader Tuple reader.
      * @return Object.
      */
-    public Object read(Tuple reader) {
+    public Object read(Tuple reader) throws SerializationException {
         throw new UnsupportedOperationException();
     }
 
@@ -271,14 +390,14 @@ public abstract class UnsafeFieldAccessor {
      * @param obj Object.
      * @return Field value of given object.
      */
-    @Nullable Object value(Object obj) {
-        return IgniteUnsafeUtils.getObjectField(Objects.requireNonNull(obj), offset);
+    Object value(Object obj) {
+        return varHandle.get(Objects.requireNonNull(obj));
     }
 
     /**
      * Accessor for field of primitive {@code byte} type.
      */
-    private static class IdentityAccessor extends UnsafeFieldAccessor {
+    private static class IdentityAccessor extends FieldAccessor {
         /**
          * Constructor.
          *
@@ -290,22 +409,22 @@ public abstract class UnsafeFieldAccessor {
         }
 
         /** {@inheritDoc} */
-        @Override protected void write0(Object obj, TupleAssembler writer) {
-            JavaSerializer.writeRefObject(Objects.requireNonNull(obj, "Null values are not supported."), writer, mode);
+        @Override protected void write0(TupleAssembler writer, Object obj) {
+            writeRefObject(obj, writer, mode);
         }
 
         /** {@inheritDoc} */
-        @Override protected void read0(Object obj, Tuple reader) {
+        @Override protected void read0(Tuple reader, Object obj) {
             throw new UnsupportedOperationException("Called identity accessor for object field.");
         }
 
         /** {@inheritDoc} */
         @Override public Object read(Tuple reader) {
-            return JavaSerializer.readRefValue(reader, colIdx, mode);
+            return readRefValue(reader, colIdx, mode);
         }
 
         /** {@inheritDoc} */
-        @Override @Nullable Object value(Object obj) {
+        @Override Object value(Object obj) {
             return obj;
         }
     }
@@ -313,200 +432,198 @@ public abstract class UnsafeFieldAccessor {
     /**
      * Accessor for field of primitive {@code byte} type.
      */
-    private static class BytePrimitiveAccessor extends UnsafeFieldAccessor {
+    private static class BytePrimitiveAccessor extends FieldAccessor {
         /**
          * Constructor.
          *
-         * @param field Field.
+         * @param varHandle VarHandle.
          * @param colIdx Column index.
          */
-        public BytePrimitiveAccessor(Field field, int colIdx) {
-            super(field, colIdx, BinaryMode.P_BYTE);
+        public BytePrimitiveAccessor(VarHandle varHandle, int colIdx) {
+            super(varHandle, colIdx, BinaryMode.P_BYTE);
         }
 
         /** {@inheritDoc} */
-        @Override protected void write0(Object obj, TupleAssembler writer) {
-            final byte val = IgniteUnsafeUtils.getByteField(obj, offset);
+        @Override protected void write0(TupleAssembler writer, Object obj) {
+            final byte val = (byte)varHandle.get(obj);
 
             writer.appendByte(val);
         }
 
         /** {@inheritDoc} */
-        @Override protected void read0(Object obj, Tuple reader) {
+        @Override protected void read0(Tuple reader, Object obj) {
             final byte val = reader.byteValue(colIdx);
 
-            IgniteUnsafeUtils.putByteField(obj, offset, val);
+            varHandle.set(obj, val);
         }
     }
 
     /**
      * Accessor for field of primitive {@code short} type.
      */
-    private static class ShortPrimitiveAccessor extends UnsafeFieldAccessor {
+    private static class ShortPrimitiveAccessor extends FieldAccessor {
         /**
          * Constructor.
          *
-         * @param field Field.
+         * @param varHandle VarHandle.
          * @param colIdx Column index.
          */
-        public ShortPrimitiveAccessor(Field field, int colIdx) {
-            super(field, colIdx, BinaryMode.P_SHORT);
+        public ShortPrimitiveAccessor(VarHandle varHandle, int colIdx) {
+            super(varHandle, colIdx, BinaryMode.P_SHORT);
         }
 
         /** {@inheritDoc} */
-        @Override protected void write0(Object obj, TupleAssembler writer) {
-            final short val = IgniteUnsafeUtils.getShortField(obj, offset);
+        @Override protected void write0(TupleAssembler writer, Object obj) {
+            final short val = (short)varHandle.get(obj);
 
             writer.appendShort(val);
         }
 
         /** {@inheritDoc} */
-        @Override protected void read0(Object obj, Tuple reader) {
+        @Override protected void read0(Tuple reader, Object obj) {
             final short val = reader.shortValue(colIdx);
 
-            IgniteUnsafeUtils.putShortField(obj, offset, val);
+            varHandle.set(obj, val);
         }
     }
 
     /**
      * Accessor for field of primitive {@code int} type.
      */
-    private static class IntPrimitiveAccessor extends UnsafeFieldAccessor {
+    private static class IntPrimitiveAccessor extends FieldAccessor {
         /**
          * Constructor.
          *
-         * @param field Field.
+         * @param varHandle VarHandle.
          * @param colIdx Column index.
          */
-        public IntPrimitiveAccessor(Field field, int colIdx) {
-            super(field, colIdx, BinaryMode.P_INT);
+        public IntPrimitiveAccessor(VarHandle varHandle, int colIdx) {
+            super(varHandle, colIdx, BinaryMode.P_INT);
         }
 
         /** {@inheritDoc} */
-        @Override protected void write0(Object obj, TupleAssembler writer) {
-            final int val = IgniteUnsafeUtils.getIntField(obj, offset);
+        @Override protected void write0(TupleAssembler writer, Object obj) {
+            final int val = (int)varHandle.get(obj);
 
             writer.appendInt(val);
         }
 
         /** {@inheritDoc} */
-        @Override protected void read0(Object obj, Tuple reader) {
+        @Override protected void read0(Tuple reader, Object obj) {
             final int val = reader.intValue(colIdx);
 
-            IgniteUnsafeUtils.putIntField(obj, offset, val);
+            varHandle.set(obj, val);
         }
     }
 
     /**
      * Accessor for field of primitive {@code long} type.
      */
-    private static class LongPrimitiveAccessor extends UnsafeFieldAccessor {
+    private static class LongPrimitiveAccessor extends FieldAccessor {
         /**
          * Constructor.
          *
-         * @param field Field.
+         * @param varHandle VarHandle.
          * @param colIdx Column index.
          */
-        public LongPrimitiveAccessor(Field field, int colIdx) {
-            super(field, colIdx, BinaryMode.P_LONG);
+        public LongPrimitiveAccessor(VarHandle varHandle, int colIdx) {
+            super(varHandle, colIdx, BinaryMode.P_LONG);
         }
 
         /** {@inheritDoc} */
-        @Override protected void write0(Object obj, TupleAssembler writer) {
-            final long val = IgniteUnsafeUtils.getLongField(obj, offset);
+        @Override protected void write0(TupleAssembler writer, Object obj) {
+            final long val = (long)varHandle.get(obj);
 
             writer.appendLong(val);
         }
 
         /** {@inheritDoc} */
-        @Override protected void read0(Object obj, Tuple reader) {
+        @Override protected void read0(Tuple reader, Object obj) {
             final long val = reader.longValue(colIdx);
 
-            IgniteUnsafeUtils.putLongField(obj, offset, val);
+            varHandle.set(obj, val);
         }
     }
 
     /**
      * Accessor for field of primitive {@code float} type.
      */
-    private static class FloatPrimitiveAccessor extends UnsafeFieldAccessor {
+    private static class FloatPrimitiveAccessor extends FieldAccessor {
         /**
          * Constructor.
          *
-         * @param field Field.
+         * @param varHandle VarHandle.
          * @param colIdx Column index.
          */
-        public FloatPrimitiveAccessor(Field field, int colIdx) {
-            super(field, colIdx, BinaryMode.P_FLOAT);
+        public FloatPrimitiveAccessor(VarHandle varHandle, int colIdx) {
+            super(varHandle, colIdx, BinaryMode.P_FLOAT);
         }
 
         /** {@inheritDoc} */
-        @Override protected void write0(Object obj, TupleAssembler writer) {
-            final float val = IgniteUnsafeUtils.getFloatField(obj, offset);
+        @Override protected void write0(TupleAssembler writer, Object obj) {
+            final float val = (float)varHandle.get(obj);
 
             writer.appendFloat(val);
         }
 
         /** {@inheritDoc} */
-        @Override protected void read0(Object obj, Tuple reader) {
+        @Override protected void read0(Tuple reader, Object obj) {
             final float val = reader.floatValue(colIdx);
 
-            IgniteUnsafeUtils.putFloatField(obj, offset, val);
+            varHandle.set(obj, val);
         }
     }
 
     /**
      * Accessor for field of primitive {@code double} type.
      */
-    private static class DoublePrimitiveAccessor extends UnsafeFieldAccessor {
+    private static class DoublePrimitiveAccessor extends FieldAccessor {
         /**
          * Constructor.
          *
-         * @param field Field.
+         * @param varHandle VarHandle.
          * @param colIdx Column index.
          */
-        public DoublePrimitiveAccessor(Field field, int colIdx) {
-            super(field, colIdx, BinaryMode.P_DOUBLE);
+        public DoublePrimitiveAccessor(VarHandle varHandle, int colIdx) {
+            super(varHandle, colIdx, BinaryMode.P_DOUBLE);
         }
 
         /** {@inheritDoc} */
-        @Override protected void write0(Object obj, TupleAssembler writer) {
-            final double val = IgniteUnsafeUtils.getDoubleField(obj, offset);
+        @Override protected void write0(TupleAssembler writer, Object obj) {
+            final double val = (double)varHandle.get(obj);
 
             writer.appendDouble(val);
         }
 
         /** {@inheritDoc} */
-        @Override protected void read0(Object obj, Tuple reader) {
+        @Override protected void read0(Tuple reader, Object obj) {
             final double val = reader.doubleValue(colIdx);
 
-            IgniteUnsafeUtils.putDoubleField(obj, offset, val);
+            varHandle.set(obj, val);
         }
     }
 
     /**
      * Accessor for field of reference type.
      */
-    private static class ReferenceFieldAccessor extends UnsafeFieldAccessor {
+    private static class ReferenceFieldAccessor extends FieldAccessor {
         /**
          * Constructor.
          *
-         * @param field Field.
+         * @param varHandle VarHandle.
          * @param colIdx Column index.
          * @param mode Binary mode.
          */
-        ReferenceFieldAccessor(Field field, int colIdx, BinaryMode mode) {
-            super(field, colIdx, mode);
+        ReferenceFieldAccessor(VarHandle varHandle, int colIdx, BinaryMode mode) {
+            super(varHandle, colIdx, mode);
         }
 
         /** {@inheritDoc} */
-        @Override protected void write0(Object obj, TupleAssembler writer) {
+        @Override protected void write0(TupleAssembler writer, Object obj) {
             assert obj != null;
             assert writer != null;
 
-            Object val;
-
-            val = IgniteUnsafeUtils.getObjectField(obj, offset);
+            Object val = varHandle.get(obj);
 
             if (val == null) {
                 writer.appendNull();
@@ -514,14 +631,14 @@ public abstract class UnsafeFieldAccessor {
                 return;
             }
 
-            JavaSerializer.writeRefObject(val, writer, mode);
+            writeRefObject(val, writer, mode);
         }
 
         /** {@inheritDoc} */
-        @Override public void read0(Object obj, Tuple reader) {
-            Object val = JavaSerializer.readRefValue(reader, colIdx, mode);
+        @Override public void read0(Tuple reader, Object obj) {
+            Object val = readRefValue(reader, colIdx, mode);
 
-            IgniteUnsafeUtils.putObjectField(obj, offset, val);
+            varHandle.set(obj, val);
         }
     }
 }
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/JavaSerializer.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/JavaSerializer.java
index 4bbb630..d0f6532 100644
--- a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/JavaSerializer.java
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/JavaSerializer.java
@@ -17,171 +17,20 @@
 
 package org.apache.ignite.internal.schema.marshaller.reflection;
 
-import java.util.BitSet;
-import java.util.UUID;
-import org.apache.ignite.internal.schema.ByteBufferTuple;
 import org.apache.ignite.internal.schema.Columns;
 import org.apache.ignite.internal.schema.SchemaDescriptor;
 import org.apache.ignite.internal.schema.Tuple;
 import org.apache.ignite.internal.schema.TupleAssembler;
-import org.apache.ignite.internal.schema.marshaller.BinaryMode;
+import org.apache.ignite.internal.schema.marshaller.AbstractSerializer;
 import org.apache.ignite.internal.schema.marshaller.SerializationException;
-import org.apache.ignite.internal.schema.marshaller.Serializer;
+import org.jetbrains.annotations.Nullable;
 
 import static org.apache.ignite.internal.schema.marshaller.MarshallerUtil.getValueSize;
 
 /**
  * Reflection based (de)serializer.
  */
-public class JavaSerializer implements Serializer {
-
-    /**
-     * Reads value object from tuple.
-     *
-     * @param reader Reader.
-     * @param colIdx Column index.
-     * @param mode Binary read mode.
-     * @return Read value object.
-     */
-    static Object readRefValue(Tuple reader, int colIdx, BinaryMode mode) {
-        assert reader != null;
-        assert colIdx >= 0;
-
-        Object val = null;
-
-        switch (mode) {
-            case BYTE:
-                val = reader.byteValueBoxed(colIdx);
-
-                break;
-
-            case SHORT:
-                val = reader.shortValueBoxed(colIdx);
-
-                break;
-
-            case INT:
-                val = reader.intValueBoxed(colIdx);
-
-                break;
-
-            case LONG:
-                val = reader.longValueBoxed(colIdx);
-
-                break;
-
-            case FLOAT:
-                val = reader.floatValueBoxed(colIdx);
-
-                break;
-
-            case DOUBLE:
-                val = reader.doubleValueBoxed(colIdx);
-
-                break;
-
-            case STRING:
-                val = reader.stringValue(colIdx);
-
-                break;
-
-            case UUID:
-                val = reader.uuidValue(colIdx);
-
-                break;
-
-            case BYTE_ARR:
-                val = reader.bytesValue(colIdx);
-
-                break;
-
-            case BITSET:
-                val = reader.bitmaskValue(colIdx);
-
-                break;
-
-            default:
-                assert false : "Invalid mode: " + mode;
-        }
-
-        return val;
-    }
-
-    /**
-     * Writes reference value to tuple.
-     *
-     * @param val Value object.
-     * @param writer Writer.
-     * @param mode Write binary mode.
-     */
-    static void writeRefObject(Object val, TupleAssembler writer, BinaryMode mode) {
-        assert writer != null;
-
-        if (val == null) {
-            writer.appendNull();
-
-            return;
-        }
-
-        switch (mode) {
-            case BYTE:
-                writer.appendByte((Byte)val);
-
-                break;
-
-            case SHORT:
-                writer.appendShort((Short)val);
-
-                break;
-
-            case INT:
-                writer.appendInt((Integer)val);
-
-                break;
-
-            case LONG:
-                writer.appendLong((Long)val);
-
-                break;
-
-            case FLOAT:
-                writer.appendFloat((Float)val);
-
-                break;
-
-            case DOUBLE:
-                writer.appendDouble((Double)val);
-
-                break;
-
-            case STRING:
-                writer.appendString((String)val);
-
-                break;
-
-            case UUID:
-                writer.appendUuid((UUID)val);
-
-                break;
-
-            case BYTE_ARR:
-                writer.appendBytes((byte[])val);
-
-                break;
-
-            case BITSET:
-                writer.appendBitmask((BitSet)val);
-
-                break;
-
-            default:
-                assert false : "Invalid mode: " + mode;
-        }
-    }
-
-    /** Schema. */
-    private final SchemaDescriptor schema;
-
+public class JavaSerializer extends AbstractSerializer {
     /** Key class. */
     private final Class<?> keyClass;
 
@@ -202,7 +51,7 @@ public class JavaSerializer implements Serializer {
      * @param valClass Value type.
      */
     public JavaSerializer(SchemaDescriptor schema, Class<?> keyClass, Class<?> valClass) {
-        this.schema = schema;
+        super(schema);
         this.keyClass = keyClass;
         this.valClass = valClass;
 
@@ -211,18 +60,13 @@ public class JavaSerializer implements Serializer {
     }
 
     /** {@inheritDoc} */
-    @Override public byte[] serialize(Object key, Object val) throws SerializationException {
+    @Override protected  byte[] serialize0(TupleAssembler asm, Object key, @Nullable Object val)
+        throws SerializationException {
         assert keyClass.isInstance(key);
         assert val == null || valClass.isInstance(val);
 
-        final TupleAssembler asm = createAssembler(key, val);
-
         keyMarsh.writeObject(key, asm);
-
-        if (val != null)
-            valMarsh.writeObject(val, asm);
-        else
-            assert false; // TODO: add tomstone support and remove assertion.
+        valMarsh.writeObject(val, asm);
 
         return asm.build();
     }
@@ -234,7 +78,7 @@ public class JavaSerializer implements Serializer {
      * @param val Value object.
      * @return Tuple assembler.
      */
-    private TupleAssembler createAssembler(Object key, Object val) {
+    @Override protected TupleAssembler createAssembler(Object key, Object val) {
         ObjectStatistic keyStat = collectObjectStats(schema.keyColumns(), keyMarsh, key);
         ObjectStatistic valStat = collectObjectStats(schema.valueColumns(), valMarsh, val);
 
@@ -274,9 +118,7 @@ public class JavaSerializer implements Serializer {
     }
 
     /** {@inheritDoc} */
-    @Override public Object deserializeKey(byte[] data) throws SerializationException {
-        final Tuple tuple = new ByteBufferTuple(schema, data);
-
+    @Override protected Object deserializeKey0(Tuple tuple) throws SerializationException {
         final Object o = keyMarsh.readObject(tuple);
 
         assert keyClass.isInstance(o);
@@ -285,14 +127,10 @@ public class JavaSerializer implements Serializer {
     }
 
     /** {@inheritDoc} */
-    @Override public Object deserializeValue(byte[] data) throws SerializationException {
-        final Tuple tuple = new ByteBufferTuple(schema, data);
-
-        // TODO: add tomstone support.
-
+    @Override protected Object deserializeValue0(Tuple tuple) throws SerializationException {
         final Object o = valMarsh.readObject(tuple);
 
-        assert valClass.isInstance(o);
+        assert o == null || valClass.isInstance(o);
 
         return o;
     }
@@ -308,7 +146,7 @@ public class JavaSerializer implements Serializer {
         int nonNullFieldsSize;
 
         /** Constructor. */
-        public ObjectStatistic(int nonNullFields, int nonNullFieldsSize) {
+        ObjectStatistic(int nonNullFields, int nonNullFieldsSize) {
             this.nonNullFields = nonNullFields;
             this.nonNullFieldsSize = nonNullFieldsSize;
         }
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/Marshaller.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/Marshaller.java
index 506c3a3..49d3ee4 100644
--- a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/Marshaller.java
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/Marshaller.java
@@ -32,7 +32,7 @@ import org.jetbrains.annotations.Nullable;
 /**
  * Marshaller.
  */
-public class Marshaller {
+class Marshaller {
     /**
      * Creates marshaller for class.
      *
@@ -41,7 +41,7 @@ public class Marshaller {
      * @param aClass Type.
      * @return Marshaller.
      */
-    public static Marshaller createMarshaller(Columns cols, int firstColId, Class<? extends Object> aClass) {
+    static Marshaller createMarshaller(Columns cols, int firstColId, Class<? extends Object> aClass) {
         final BinaryMode mode = MarshallerUtil.mode(aClass);
 
         if (mode != null) {
@@ -51,17 +51,17 @@ public class Marshaller {
             assert mode.typeSpec() == col.type().spec() : "Target type is not compatible.";
             assert !aClass.isPrimitive() : "Non-nullable types are not allowed.";
 
-            return new Marshaller(UnsafeFieldAccessor.createIdentityAccessor(col, firstColId, mode));
+            return new Marshaller(FieldAccessor.createIdentityAccessor(col, firstColId, mode));
         }
 
-        UnsafeFieldAccessor[] fieldAccessors = new UnsafeFieldAccessor[cols.length()];
+        FieldAccessor[] fieldAccessors = new FieldAccessor[cols.length()];
 
         // Build accessors
         for (int i = 0; i < cols.length(); i++) {
             final Column col = cols.column(i);
 
             final int colIdx = firstColId + i; /* Absolute column idx in schema. */
-            fieldAccessors[i] = UnsafeFieldAccessor.create(aClass, col, colIdx);
+            fieldAccessors[i] = FieldAccessor.create(aClass, col, colIdx);
         }
 
         return new Marshaller(new ObjectFactory<>(aClass), fieldAccessors);
@@ -71,7 +71,7 @@ public class Marshaller {
      * Field accessors for mapped columns.
      * Array has same size and order as columns.
      */
-    private final UnsafeFieldAccessor[] fieldAccessors;
+    private final FieldAccessor[] fieldAccessors;
 
     /**
      * Object factory for complex types or {@code null} for basic type.
@@ -86,7 +86,7 @@ public class Marshaller {
      * @param fieldAccessors Object field accessors for mapped columns.
      */
     @SuppressWarnings("AssignmentOrReturnOfFieldWithMutableType")
-    public Marshaller(Factory<?> factory, UnsafeFieldAccessor[] fieldAccessors) {
+    Marshaller(Factory<?> factory, FieldAccessor[] fieldAccessors) {
         this.fieldAccessors = fieldAccessors;
         this.factory = Objects.requireNonNull(factory);
     }
@@ -97,8 +97,8 @@ public class Marshaller {
      *
      * @param fieldAccessor Identity field accessor for object of basic type.
      */
-    public Marshaller(UnsafeFieldAccessor fieldAccessor) {
-        fieldAccessors = new UnsafeFieldAccessor[] {fieldAccessor};
+    public Marshaller(FieldAccessor fieldAccessor) {
+        fieldAccessors = new FieldAccessor[] {fieldAccessor};
         factory = null;
     }
 
@@ -121,13 +121,13 @@ public class Marshaller {
      * @throws SerializationException If failed.
      */
     public Object readObject(Tuple reader) throws SerializationException {
-        if (isBasicTypeMarshaller())
+        if (isSimpleTypeMarshaller())
             return fieldAccessors[0].read(reader);
 
         final Object obj = factory.create();
 
         for (int fldIdx = 0; fldIdx < fieldAccessors.length; fldIdx++)
-            fieldAccessors[fldIdx].read(obj, reader);
+            fieldAccessors[fldIdx].read(reader, obj);
 
         return obj;
     }
@@ -141,13 +141,13 @@ public class Marshaller {
      */
     public void writeObject(Object obj, TupleAssembler writer) throws SerializationException {
         for (int fldIdx = 0; fldIdx < fieldAccessors.length; fldIdx++)
-            fieldAccessors[fldIdx].write(obj, writer);
+            fieldAccessors[fldIdx].write(writer, obj);
     }
 
     /**
-     * @return {@code true} if it is marshaller for basic type, {@code false} otherwise.
+     * @return {@code true} if it is marshaller for simple type, {@code false} otherwise.
      */
-    private boolean isBasicTypeMarshaller() {
+    private boolean isSimpleTypeMarshaller() {
         return factory == null;
     }
 }
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/util/IgniteUnsafeUtils.java b/modules/commons/src/main/java/org/apache/ignite/internal/util/IgniteUnsafeUtils.java
deleted file mode 100644
index 74ea3a3..0000000
--- a/modules/commons/src/main/java/org/apache/ignite/internal/util/IgniteUnsafeUtils.java
+++ /dev/null
@@ -1,273 +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.util;
-
-import java.lang.reflect.Field;
-import java.security.AccessController;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
-import sun.misc.Unsafe;
-
-/**
- * Unsafe utility.
- */
-//TODO Move class to 'java-8' profile. Java9+ should use varhandles instead.
-public final class IgniteUnsafeUtils {
-    /** Unsafe. */
-    private static final Unsafe UNSAFE = unsafe();
-
-    /**
-     * @return Instance of Unsafe class.
-     */
-    private static Unsafe unsafe() {
-        try {
-            return Unsafe.getUnsafe();
-        }
-        catch (SecurityException ignored) {
-            try {
-                return AccessController.doPrivileged(
-                    new PrivilegedExceptionAction<Unsafe>() {
-                        @Override public Unsafe run() throws Exception {
-                            Field f = Unsafe.class.getDeclaredField("theUnsafe");
-
-                            f.setAccessible(true);
-
-                            return (Unsafe)f.get(null);
-                        }
-                    });
-            }
-            catch (PrivilegedActionException e) {
-                throw new RuntimeException("Could not initialize intrinsics.", e.getCause());
-            }
-        }
-    }
-
-    /**
-     * Returns object field offset.
-     *
-     * @param field Field.
-     * @return Object field offset.
-     */
-    public static long objectFieldOffset(Field field) {
-        return UNSAFE.objectFieldOffset(field);
-    }
-
-    /**
-     * Gets boolean value from object field.
-     *
-     * @param obj Object.
-     * @param fieldOff Field offset.
-     * @return Boolean value from object field.
-     */
-    public static boolean getBooleanField(Object obj, long fieldOff) {
-        return UNSAFE.getBoolean(obj, fieldOff);
-    }
-
-    /**
-     * Stores boolean value into object field.
-     *
-     * @param obj Object.
-     * @param fieldOff Field offset.
-     * @param val Value.
-     */
-    public static void putBooleanField(Object obj, long fieldOff, boolean val) {
-        UNSAFE.putBoolean(obj, fieldOff, val);
-    }
-
-    /**
-     * Gets byte value from object field.
-     *
-     * @param obj Object.
-     * @param fieldOff Field offset.
-     * @return Byte value from object field.
-     */
-    public static byte getByteField(Object obj, long fieldOff) {
-        return UNSAFE.getByte(obj, fieldOff);
-    }
-
-    /**
-     * Stores byte value into object field.
-     *
-     * @param obj Object.
-     * @param fieldOff Field offset.
-     * @param val Value.
-     */
-    public static void putByteField(Object obj, long fieldOff, byte val) {
-        UNSAFE.putByte(obj, fieldOff, val);
-    }
-
-    /**
-     * Gets short value from object field.
-     *
-     * @param obj Object.
-     * @param fieldOff Field offset.
-     * @return Short value from object field.
-     */
-    public static short getShortField(Object obj, long fieldOff) {
-        return UNSAFE.getShort(obj, fieldOff);
-    }
-
-    /**
-     * Stores short value into object field.
-     *
-     * @param obj Object.
-     * @param fieldOff Field offset.
-     * @param val Value.
-     */
-    public static void putShortField(Object obj, long fieldOff, short val) {
-        UNSAFE.putShort(obj, fieldOff, val);
-    }
-
-    /**
-     * Gets char value from object field.
-     *
-     * @param obj Object.
-     * @param fieldOff Field offset.
-     * @return Char value from object field.
-     */
-    public static char getCharField(Object obj, long fieldOff) {
-        return UNSAFE.getChar(obj, fieldOff);
-    }
-
-    /**
-     * Stores char value into object field.
-     *
-     * @param obj Object.
-     * @param fieldOff Field offset.
-     * @param val Value.
-     */
-    public static void putCharField(Object obj, long fieldOff, char val) {
-        UNSAFE.putChar(obj, fieldOff, val);
-    }
-
-    /**
-     * Gets integer value from object field.
-     *
-     * @param obj Object.
-     * @param fieldOff Field offset.
-     * @return Integer value from object field.
-     */
-    public static int getIntField(Object obj, long fieldOff) {
-        return UNSAFE.getInt(obj, fieldOff);
-    }
-
-    /**
-     * Stores integer value into object field.
-     *
-     * @param obj Object.
-     * @param fieldOff Field offset.
-     * @param val Value.
-     */
-    public static void putIntField(Object obj, long fieldOff, int val) {
-        UNSAFE.putInt(obj, fieldOff, val);
-    }
-
-    /**
-     * Gets long value from object field.
-     *
-     * @param obj Object.
-     * @param fieldOff Field offset.
-     * @return Long value from object field.
-     */
-    public static long getLongField(Object obj, long fieldOff) {
-        return UNSAFE.getLong(obj, fieldOff);
-    }
-
-    /**
-     * Stores long value into object field.
-     *
-     * @param obj Object.
-     * @param fieldOff Field offset.
-     * @param val Value.
-     */
-    public static void putLongField(Object obj, long fieldOff, long val) {
-        UNSAFE.putLong(obj, fieldOff, val);
-    }
-
-    /**
-     * Gets float value from object field.
-     *
-     * @param obj Object.
-     * @param fieldOff Field offset.
-     * @return Float value from object field.
-     */
-    public static float getFloatField(Object obj, long fieldOff) {
-        return UNSAFE.getFloat(obj, fieldOff);
-    }
-
-    /**
-     * Stores float value into object field.
-     *
-     * @param obj Object.
-     * @param fieldOff Field offset.
-     * @param val Value.
-     */
-    public static void putFloatField(Object obj, long fieldOff, float val) {
-        UNSAFE.putFloat(obj, fieldOff, val);
-    }
-
-    /**
-     * Gets double value from object field.
-     *
-     * @param obj Object.
-     * @param fieldOff Field offset.
-     * @return Double value from object field.
-     */
-    public static double getDoubleField(Object obj, long fieldOff) {
-        return UNSAFE.getDouble(obj, fieldOff);
-    }
-
-    /**
-     * Stores double value into object field.
-     *
-     * @param obj Object.
-     * @param fieldOff Field offset.
-     * @param val Value.
-     */
-    public static void putDoubleField(Object obj, long fieldOff, double val) {
-        UNSAFE.putDouble(obj, fieldOff, val);
-    }
-
-    /**
-     * Gets reference from object field.
-     *
-     * @param obj Object.
-     * @param fieldOff Field offset.
-     * @return Reference from object field.
-     */
-    public static Object getObjectField(Object obj, long fieldOff) {
-        return UNSAFE.getObject(obj, fieldOff);
-    }
-
-    /**
-     * Stores reference value into object field.
-     *
-     * @param obj Object.
-     * @param fieldOff Field offset.
-     * @param val Value.
-     */
-    public static void putObjectField(Object obj, long fieldOff, Object val) {
-        UNSAFE.putObject(obj, fieldOff, val);
-    }
-
-    /**
-     * Stub.
-     */
-    private IgniteUnsafeUtils() {
-    }
-}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/util/ObjectFactory.java b/modules/commons/src/main/java/org/apache/ignite/internal/util/ObjectFactory.java
index f31965f..30f9365 100644
--- a/modules/commons/src/main/java/org/apache/ignite/internal/util/ObjectFactory.java
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/util/ObjectFactory.java
@@ -35,7 +35,6 @@ public class ObjectFactory<T> implements Factory<T> {
     public ObjectFactory(Class<T> tClass) {
         try {
             cnstr = tClass.getDeclaredConstructor();
-
             cnstr.setAccessible(true);
         }
         catch (NoSuchMethodException e) {
@@ -52,11 +51,4 @@ public class ObjectFactory<T> implements Factory<T> {
             throw new IllegalStateException("Failed to instantiate class: " + cnstr.getDeclaringClass().getName(), e);
         }
     }
-
-    /**
-     * @return Class of object created by the factory.
-     */
-    public Class<T> getClazz() {
-        return cnstr.getDeclaringClass();
-    }
 }
diff --git a/modules/commons/src/main/java/org/apache/ignite/lang/IgniteExperimental.java b/modules/commons/src/main/java/org/apache/ignite/lang/IgniteExperimental.java
new file mode 100644
index 0000000..c68fe14
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/lang/IgniteExperimental.java
@@ -0,0 +1,38 @@
+/*
+ * 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.lang;
+
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.TYPE;
+
+/**
+ * This annotation marks API elements (such as interfaces, methods, annotations and whole API packages) as experimental
+ * meaning that the API is not finalized yet and may be changed or replaced in future Ignite releases.
+ * <p>
+ * Such APIs are exposed so that users can make use of a feature before the API has been stabilized. The expectation is
+ * that an API element should be "eventually" stabilized. Incompatible changes are allowed for such APIs: API may be
+ * removed, changed or stabilized in future Ignite releases (both minor and maintenance).
+ */
+@Target(value = {TYPE, METHOD, ANNOTATION_TYPE, PACKAGE, FIELD})
+public @interface IgniteExperimental {
+}
diff --git a/modules/commons/src/test/java/org/apache/ignite/internal/benchmarks/SerializerBenchmarkTest.java b/modules/commons/src/test/java/org/apache/ignite/internal/benchmarks/SerializerBenchmarkTest.java
index 23193ba..b030a4f 100644
--- a/modules/commons/src/test/java/org/apache/ignite/internal/benchmarks/SerializerBenchmarkTest.java
+++ b/modules/commons/src/test/java/org/apache/ignite/internal/benchmarks/SerializerBenchmarkTest.java
@@ -17,18 +17,21 @@
 
 package org.apache.ignite.internal.benchmarks;
 
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.TypeSpec;
 import java.lang.reflect.Field;
 import java.util.Random;
 import java.util.concurrent.TimeUnit;
+import javax.lang.model.element.Modifier;
 import org.apache.ignite.internal.schema.Column;
 import org.apache.ignite.internal.schema.Columns;
 import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.marshaller.CompilerUtils;
 import org.apache.ignite.internal.schema.marshaller.Serializer;
 import org.apache.ignite.internal.schema.marshaller.SerializerFactory;
 import org.apache.ignite.internal.util.Factory;
 import org.apache.ignite.internal.util.ObjectFactory;
-import org.codehaus.commons.compiler.CompilerFactoryFactory;
-import org.codehaus.commons.compiler.IClassBodyEvaluator;
 import org.openjdk.jmh.annotations.Benchmark;
 import org.openjdk.jmh.annotations.BenchmarkMode;
 import org.openjdk.jmh.annotations.Fork;
@@ -56,7 +59,7 @@ import static org.apache.ignite.internal.schema.NativeType.LONG;
 @Measurement(time = 10, iterations = 5, timeUnit = TimeUnit.SECONDS)
 @BenchmarkMode({Mode.Throughput, Mode.AverageTime})
 @OutputTimeUnit(TimeUnit.MICROSECONDS)
-@Fork(1)
+@Fork(jvmArgs = "-Djava.lang.invoke.stringConcat=BC_SB" /* Workaround for Java 9+ */, value = 1)
 public class SerializerBenchmarkTest {
     /** Random. */
     private Random rnd = new Random();
@@ -72,7 +75,7 @@ public class SerializerBenchmarkTest {
     public int fieldsCount;
 
     /** Serializer. */
-    @Param({"Janino", "Java"})
+    @Param({"Generated"/*, "Java"*/})
     public String serializerName;
 
     /**
@@ -91,6 +94,8 @@ public class SerializerBenchmarkTest {
      */
     @Setup
     public void init() throws Exception {
+        Thread.currentThread().setContextClassLoader(CompilerUtils.dynamicClassLoader());
+
         long seed = System.currentTimeMillis();
 
         rnd = new Random(seed);
@@ -105,7 +110,7 @@ public class SerializerBenchmarkTest {
         if ("Java".equals(serializerName))
             serializer = SerializerFactory.createJavaSerializerFactory().create(schema, Long.class, valClass);
         else
-            serializer = SerializerFactory.createJaninoSerializerFactory().create(schema, Long.class, valClass);
+            serializer = SerializerFactory.createGeneratedSerializerFactory().create(schema, Long.class, valClass);
     }
 
     /**
@@ -155,28 +160,32 @@ public class SerializerBenchmarkTest {
      * @return Generated test object class.
      * @throws Exception If failed.
      */
-    private Class<?> createGeneratedObjectClass(int maxFields, Class<?> fieldType) throws Exception {
-        final IClassBodyEvaluator ce = CompilerFactoryFactory.getDefaultCompilerFactory().newClassBodyEvaluator();
+    private Class<?> createGeneratedObjectClass(int maxFields, Class<?> fieldType) {
+        final String packageName = "org.apache.ignite.internal.benchmarks";
+        final String className = "TestObject";
 
-        ce.setClassName("TestObject");
-        ce.setDefaultImports("java.util.Random");
+        final TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC);
 
-        final StringBuilder sb = new StringBuilder();
         for (int i = 0; i < maxFields; i++)
-            sb.append(fieldType.getName()).append(" col").append(i).append(";\n");
+            classBuilder.addField(fieldType, "col" + i, Modifier.PRIVATE);
 
-        // Constructor.
-        sb.append("public TestObject() {\n");
-        sb.append("    Random rnd = new Random();\n");
-        for (int i = 0; i < maxFields; i++)
-            sb.append("    col").append(i).append(" = rnd.nextLong();\n");
-        sb.append("}\n");
+        { // Build constructor.
+            final MethodSpec.Builder builder = MethodSpec.constructorBuilder()
+                .addModifiers(Modifier.PUBLIC)
+                .addStatement("$T rnd = new $T()", Random.class, Random.class);
 
-        try {
-            ce.setParentClassLoader(getClass().getClassLoader());
-            ce.cook(sb.toString());
+            for (int i = 0; i < maxFields; i++)
+                builder.addStatement("col$L = rnd.nextLong()", i);
+
+            classBuilder.addMethod(builder.build());
+        }
+
+        final JavaFile javaFile = JavaFile.builder(packageName, classBuilder.build()).build();
 
-            return ce.getClazz();
+        final ClassLoader loader = CompilerUtils.compileCode(javaFile);
+
+        try {
+            return loader.loadClass(packageName + '.' + className);
         }
         catch (Exception ex) {
             throw new IllegalStateException("Failed to compile/instantiate generated Serializer.", ex);
diff --git a/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/JavaSerializerTest.java b/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/JavaSerializerTest.java
index 49ef554..7b41412 100644
--- a/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/JavaSerializerTest.java
+++ b/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/JavaSerializerTest.java
@@ -17,6 +17,9 @@
 
 package org.apache.ignite.internal.schema.marshaller;
 
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.TypeSpec;
 import java.util.Arrays;
 import java.util.BitSet;
 import java.util.List;
@@ -24,6 +27,7 @@ import java.util.Objects;
 import java.util.Random;
 import java.util.UUID;
 import java.util.stream.Stream;
+import javax.lang.model.element.Modifier;
 import org.apache.ignite.internal.schema.Bitmask;
 import org.apache.ignite.internal.schema.Column;
 import org.apache.ignite.internal.schema.Columns;
@@ -31,8 +35,9 @@ import org.apache.ignite.internal.schema.NativeType;
 import org.apache.ignite.internal.schema.NativeTypeSpec;
 import org.apache.ignite.internal.schema.SchemaDescriptor;
 import org.apache.ignite.internal.schema.TestUtils;
-import org.apache.ignite.internal.schema.marshaller.generator.JaninoSerializerGenerator;
+import org.apache.ignite.internal.schema.marshaller.generator.SerializerGenerator;
 import org.apache.ignite.internal.schema.marshaller.reflection.JavaSerializerFactory;
+import org.apache.ignite.internal.util.ObjectFactory;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DynamicNode;
 import org.junit.jupiter.api.TestFactory;
@@ -64,7 +69,7 @@ public class JavaSerializerTest {
      */
     private static List<SerializerFactory> serializerFactoryProvider() {
         return Arrays.asList(
-            new JaninoSerializerGenerator(),
+            new SerializerGenerator(),
             new JavaSerializerFactory()
         );
     }
@@ -245,7 +250,7 @@ public class JavaSerializerTest {
      */
     @ParameterizedTest
     @MethodSource("serializerFactoryProvider")
-    public void testClassWithNoDefaultConstructor(SerializerFactory factory) throws SerializationException {
+    public void testClassWithNoDefaultConstructor(SerializerFactory factory) {
         Column[] cols = new Column[] {
             new Column("pLongCol", LONG, false),
         };
@@ -258,7 +263,49 @@ public class JavaSerializerTest {
         assertThrows(IllegalStateException.class,
             () -> factory.create(schema, key.getClass(), val.getClass()),
             "Class has no default constructor: class=org.apache.ignite.internal.schema.marshaller.JavaSerializerTest$WrongTestObject"
-            );
+        );
+    }
+
+    /**
+     *
+     */
+    @ParameterizedTest
+    @MethodSource("serializerFactoryProvider")
+    public void testClassLoader(SerializerFactory factory) throws SerializationException {
+        final ClassLoader prevClassLoader = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(CompilerUtils.dynamicClassLoader());
+
+            Column[] keyCols = new Column[] {
+                new Column("key", LONG, false)
+            };
+
+            Column[] valCols = new Column[] {
+                new Column("col0", LONG, false),
+                new Column("col1", LONG, false),
+                new Column("col2", LONG, false),
+            };
+
+            SchemaDescriptor schema = new SchemaDescriptor(1, new Columns(keyCols), new Columns(valCols));
+
+            final Class<?> valClass = createGeneratedObjectClass(Long.class);
+            final ObjectFactory<?> objFactory = new ObjectFactory<>(valClass);
+
+            final Long key = rnd.nextLong();
+
+            Serializer serializer = factory.create(schema, key.getClass(), valClass);
+
+            byte[] bytes = serializer.serialize(key, objFactory.create());
+
+            Object key1 = serializer.deserializeKey(bytes);
+            Object val1 = serializer.deserializeValue(bytes);
+
+            assertTrue(key.getClass().isInstance(key1));
+            assertTrue(valClass.isInstance(val1));
+        }
+        finally {
+            Thread.currentThread().setContextClassLoader(prevClassLoader);
+        }
     }
 
     /**
@@ -318,6 +365,42 @@ public class JavaSerializerTest {
     }
 
     /**
+     * Generate class for test objects.
+     *
+     * @param fieldType Field type.
+     * @return Generated test object class.
+     */
+    private Class<?> createGeneratedObjectClass(Class<?> fieldType) {
+        final String packageName = getClass().getPackageName();
+        final String className = "GeneratedTestObject";
+
+        final TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC);
+
+        for (int i = 0; i < 3; i++)
+            classBuilder.addField(fieldType, "col" + i, Modifier.PRIVATE);
+
+        { // Build constructor.
+            final MethodSpec.Builder builder = MethodSpec.constructorBuilder()
+                .addModifiers(Modifier.PUBLIC)
+                .addStatement("$T rnd = new $T()", Random.class, Random.class);
+
+            for (int i = 0; i < 3; i++)
+                builder.addStatement("col$L = rnd.nextLong()", i);
+
+            classBuilder.addMethod(builder.build());
+        }
+
+        final JavaFile javaFile = JavaFile.builder(packageName, classBuilder.build()).build();
+
+        try {
+            return CompilerUtils.compileCode(javaFile).loadClass(packageName + '.' + className);
+        }
+        catch (Exception ex) {
+            throw new IllegalStateException("Failed to compile/instantiate generated Serializer.", ex);
+        }
+    }
+
+    /**
      * Test object.
      */
     public static class TestObject {
@@ -340,7 +423,9 @@ public class JavaSerializerTest {
             obj.longCol = rnd.nextLong();
             obj.floatCol = rnd.nextFloat();
             obj.doubleCol = rnd.nextDouble();
+            obj.nullLongCol = null;
 
+            obj.nullBytesCol = null;
             obj.uuidCol = new UUID(rnd.nextLong(), rnd.nextLong());
             obj.bitmaskCol = TestUtils.randomBitSet(rnd, 42);
             obj.stringCol = TestUtils.randomString(rnd, rnd.nextInt(255));
@@ -411,7 +496,7 @@ public class JavaSerializerTest {
     /**
      * Test object with private constructor.
      */
-    private static class PrivateTestObject {
+    public static class PrivateTestObject {
         /**
          * @return Random TestObject.
          */
@@ -429,7 +514,6 @@ public class JavaSerializerTest {
         /**
          * Private constructor.
          */
-        @SuppressWarnings("RedundantNoArgConstructor")
         private PrivateTestObject() {
         }
 
diff --git a/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/reflection/FieldAccessorTest.java b/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/reflection/FieldAccessorTest.java
index 8e864d6..00a05f5 100644
--- a/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/reflection/FieldAccessorTest.java
+++ b/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/reflection/FieldAccessorTest.java
@@ -103,17 +103,17 @@ public class FieldAccessorTest {
         final TestObject obj = TestObject.randomObject(rnd);
 
         for (int i = 0; i < cols.length; i++) {
-            UnsafeFieldAccessor accessor = UnsafeFieldAccessor.create(TestObject.class, cols[i], i);
+            FieldAccessor accessor = FieldAccessor.create(TestObject.class, cols[i], i);
 
-            accessor.write(obj, tupleAssembler);
+            accessor.write(tupleAssembler, obj);
         }
 
         final TestObject restoredObj = new TestObject();
 
         for (int i = 0; i < cols.length; i++) {
-            UnsafeFieldAccessor accessor = UnsafeFieldAccessor.create(TestObject.class, cols[i], i);
+            FieldAccessor accessor = FieldAccessor.create(TestObject.class, cols[i], i);
 
-            accessor.read(restoredObj, tuple);
+            accessor.read(tuple, restoredObj);
         }
 
         assertEquals(obj.pByteCol, restoredObj.pByteCol);
@@ -159,17 +159,17 @@ public class FieldAccessorTest {
         obj.stringCol = TestUtils.randomString(rnd, 255);
 
         for (int i = 0; i < cols.length; i++) {
-            UnsafeFieldAccessor accessor = UnsafeFieldAccessor.create(TestSimpleObject.class, cols[i], i);
+            FieldAccessor accessor = FieldAccessor.create(TestSimpleObject.class, cols[i], i);
 
-            accessor.write(obj, tupleAssembler);
+            accessor.write(tupleAssembler, obj);
         }
 
         final TestSimpleObject restoredObj = new TestSimpleObject();
 
         for (int i = 0; i < cols.length; i++) {
-            UnsafeFieldAccessor accessor = UnsafeFieldAccessor.create(TestSimpleObject.class, cols[i], i);
+            FieldAccessor accessor = FieldAccessor.create(TestSimpleObject.class, cols[i], i);
 
-            accessor.read(restoredObj, tuple);
+            accessor.read(tuple, restoredObj);
         }
 
         assertEquals(obj.intCol, restoredObj.intCol);
@@ -184,7 +184,7 @@ public class FieldAccessorTest {
      */
     @Test
     public void testIdentityAccessor() throws Exception {
-        final UnsafeFieldAccessor accessor = UnsafeFieldAccessor.createIdentityAccessor(
+        final FieldAccessor accessor = FieldAccessor.createIdentityAccessor(
             new Column("col0", STRING, true),
             0,
             BinaryMode.STRING);
@@ -193,7 +193,7 @@ public class FieldAccessorTest {
 
         final Pair<TupleAssembler, Tuple> mocks = createMocks();
 
-        accessor.write("Other string", mocks.getFirst());
+        accessor.write(mocks.getFirst(), "Other string");
         assertEquals("Other string", accessor.read(mocks.getSecond()));
     }
 
@@ -202,7 +202,7 @@ public class FieldAccessorTest {
      */
     @Test
     public void testWrongIdentityAccessor() throws Exception {
-        final UnsafeFieldAccessor accessor = UnsafeFieldAccessor.createIdentityAccessor(
+        final FieldAccessor accessor = FieldAccessor.createIdentityAccessor(
             new Column("col0", STRING, true),
             42,
             BinaryMode.UUID);
@@ -213,7 +213,7 @@ public class FieldAccessorTest {
 
         assertThrows(
             SerializationException.class,
-            () -> accessor.write("Other string", mocks.getFirst()),
+            () -> accessor.write(mocks.getFirst(), "Other string"),
             "Failed to write field [id=42]"
         );
     }
@@ -242,7 +242,7 @@ public class FieldAccessorTest {
 
         final Answer<Object> tupleAnswer = new Answer<Object>() {
             @Override public Object answer(InvocationOnMock invocation) {
-                final int idx = invocation.getArgumentAt(0, Integer.class);
+                final int idx = invocation.getArgument(0, Integer.class);
 
                 return vals.get(idx);
             }
diff --git a/pom.xml b/pom.xml
index 2d460d4..73e6a1c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -49,14 +49,16 @@
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 
         <!-- Dependencies versions. -->
-        <janino.version>3.0.11</janino.version>
+        <javapoet.version>1.13.0</javapoet.version>
         <javax.annotation.api.version>1.3.2</javax.annotation.api.version>
         <jetbrains.annotations.version>20.1.0</jetbrains.annotations.version>
         <jmh.framework.verion>1.9.3</jmh.framework.verion>
         <junit.jupiter.version>5.7.0</junit.jupiter.version>
-        <mockito.version>1.10.19</mockito.version>
+        <mockito.core.version>3.6.28</mockito.core.version>
 
         <!-- Maven plugins -->
+        <maven.compiler.release>11</maven.compiler.release>
+
         <maven.compiler.plugin.version>3.8.1</maven.compiler.plugin.version>
         <maven.surefire.plugin.version>3.0.0-M4</maven.surefire.plugin.version>
         <apache.rat.plugin.version>0.13</apache.rat.plugin.version>
@@ -141,9 +143,6 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <version>${maven.compiler.plugin.version}</version>
-                <configuration>
-                    <release>11</release>
-                </configuration>
             </plugin>
 
             <plugin>