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:16 UTC

[ignite-3] branch ignite-13618 updated (db6342b -> 0a56da1)

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

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


 discard db6342b  Rewrite to VarHandles. Improve serializer API.
     new 0a56da1  Rewrite to VarHandles. Improve serializer API.

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (db6342b)
            \
             N -- N -- N   refs/heads/ignite-13618 (0a56da1)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

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


Summary of changes:
 .../marshaller/generator/ObjectMarshallerCodeGenerator.java      | 2 +-
 .../schema/marshaller/generator/SerializerGenerator.java         | 9 ++++++++-
 .../marshaller/generator/TupleColumnAccessCodeGenerator.java     | 2 +-
 3 files changed, 10 insertions(+), 3 deletions(-)


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

Posted by am...@apache.org.
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>