You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@beam.apache.org by re...@apache.org on 2020/02/24 23:09:53 UTC

[beam] branch master updated: Support null fields in rows with ByteBuddy generated code.

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

reuvenlax pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git


The following commit(s) were added to refs/heads/master by this push:
     new b63bd59  Support null fields in rows with ByteBuddy generated code.
     new 16fa12c  Merge pull request #10926 from reuvenlax/fix_bytebuddy_nullable
b63bd59 is described below

commit b63bd5928864f37d88746f48566e6ed56b8b6660
Author: Reuven Lax <re...@relax-macbookpro3.roam.corp.google.com>
AuthorDate: Thu Feb 20 21:46:34 2020 -0800

    Support null fields in rows with ByteBuddy generated code.
---
 .../beam/sdk/schemas/utils/AutoValueUtils.java     |   3 +
 .../beam/sdk/schemas/utils/AvroByteBuddyUtils.java |   3 +
 .../beam/sdk/schemas/utils/ByteBuddyUtils.java     | 290 +++++++++++++--------
 .../beam/sdk/schemas/utils/ConvertHelpers.java     |   3 +
 .../beam/sdk/schemas/utils/JavaBeanUtils.java      |   6 +
 .../apache/beam/sdk/schemas/utils/POJOUtils.java   |   9 +
 .../beam/sdk/schemas/JavaBeanSchemaTest.java       |  44 ++++
 .../beam/sdk/schemas/JavaFieldSchemaTest.java      |  54 ++++
 .../beam/sdk/schemas/utils/TestJavaBeans.java      | 199 ++++++++++++++
 .../apache/beam/sdk/schemas/utils/TestPOJOs.java   | 105 ++++++++
 .../extensions/protobuf/ProtoByteBuddyUtils.java   |   3 +
 11 files changed, 612 insertions(+), 107 deletions(-)

diff --git a/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/AutoValueUtils.java b/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/AutoValueUtils.java
index 775fe65..0435cb8 100644
--- a/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/AutoValueUtils.java
+++ b/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/AutoValueUtils.java
@@ -44,6 +44,7 @@ import org.apache.beam.sdk.schemas.utils.ByteBuddyUtils.TypeConversionsFactory;
 import org.apache.beam.sdk.util.common.ReflectHelpers;
 import org.apache.beam.sdk.values.TypeDescriptor;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.ByteBuddy;
+import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.asm.AsmVisitorWrapper;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.description.method.MethodDescription.ForLoadedMethod;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.description.type.TypeDescription.ForLoadedType;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.dynamic.DynamicType;
@@ -62,6 +63,7 @@ import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.byte
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.member.MethodInvocation;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.member.MethodReturn;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.jar.asm.ClassWriter;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.matcher.ElementMatchers;
 import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.Lists;
 
@@ -207,6 +209,7 @@ public class AutoValueUtils {
               .intercept(
                   new BuilderCreateInstruction(types, setterMethods, builderClass, buildMethod));
       return builder
+          .visit(new AsmVisitorWrapper.ForDeclaredMethods().writerFlags(ClassWriter.COMPUTE_FRAMES))
           .make()
           .load(ReflectHelpers.findClassLoader(), ClassLoadingStrategy.Default.INJECTION)
           .getLoaded()
diff --git a/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/AvroByteBuddyUtils.java b/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/AvroByteBuddyUtils.java
index 3bb7e8d..8957cf0 100644
--- a/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/AvroByteBuddyUtils.java
+++ b/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/AvroByteBuddyUtils.java
@@ -34,6 +34,7 @@ import org.apache.beam.sdk.schemas.utils.ReflectUtils.ClassWithSchema;
 import org.apache.beam.sdk.util.common.ReflectHelpers;
 import org.apache.beam.sdk.values.TypeDescriptor;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.ByteBuddy;
+import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.asm.AsmVisitorWrapper;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.description.type.TypeDescription.ForLoadedType;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.dynamic.DynamicType;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
@@ -43,6 +44,7 @@ import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.byte
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.collection.ArrayAccess;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.jar.asm.ClassWriter;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.matcher.ElementMatchers;
 import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.Maps;
 
@@ -89,6 +91,7 @@ class AvroByteBuddyUtils {
               .intercept(construct);
 
       return builder
+          .visit(new AsmVisitorWrapper.ForDeclaredMethods().writerFlags(ClassWriter.COMPUTE_FRAMES))
           .make()
           .load(
               ReflectHelpers.findClassLoader(clazz.getClassLoader()),
diff --git a/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/ByteBuddyUtils.java b/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/ByteBuddyUtils.java
index f1850e9..8a5ddbe 100644
--- a/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/ByteBuddyUtils.java
+++ b/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/ByteBuddyUtils.java
@@ -44,6 +44,7 @@ import org.apache.beam.sdk.values.TypeParameter;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.ByteBuddy;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.NamingStrategy;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.NamingStrategy.SuffixingRandom.BaseNameResolver;
+import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.asm.AsmVisitorWrapper;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.description.method.MethodDescription.ForLoadedConstructor;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.description.method.MethodDescription.ForLoadedMethod;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.description.type.TypeDescription;
@@ -52,6 +53,7 @@ import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.dynamic.DynamicType
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.dynamic.scaffold.InstrumentedType;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.Implementation;
+import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.Implementation.Context;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.ByteCodeAppender;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.ByteCodeAppender.Size;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.Duplication;
@@ -68,6 +70,10 @@ import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.byte
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.member.MethodInvocation;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.member.MethodReturn;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.jar.asm.ClassWriter;
+import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.jar.asm.Label;
+import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.jar.asm.MethodVisitor;
+import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.jar.asm.Opcodes;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.matcher.ElementMatchers;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.utility.RandomString;
 import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.Function;
@@ -143,6 +149,42 @@ public class ByteBuddyUtils {
     }
   };
 
+  // This StackManipulation returns onNotNull if the result of readValue is not null. Otherwise it
+  // returns null.
+  static class ShortCircuitReturnNull implements StackManipulation {
+    private final StackManipulation readValue;
+    private final StackManipulation onNotNull;
+
+    ShortCircuitReturnNull(StackManipulation readValue, StackManipulation onNotNull) {
+      this.readValue = readValue;
+      this.onNotNull = onNotNull;
+    }
+
+    @Override
+    public boolean isValid() {
+      return true;
+    }
+
+    @Override
+    public Size apply(MethodVisitor methodVisitor, Context context) {
+      Size size = new Size(0, 0);
+      size = size.aggregate(readValue.apply(methodVisitor, context));
+      Label label = new Label();
+      Label skipLabel = new Label();
+      methodVisitor.visitJumpInsn(Opcodes.IFNONNULL, label);
+      size = size.aggregate(new Size(-1, 0));
+      methodVisitor.visitInsn(Opcodes.ACONST_NULL);
+      methodVisitor.visitJumpInsn(Opcodes.GOTO, skipLabel);
+      size = size.aggregate(new Size(0, 1));
+      methodVisitor.visitLabel(label);
+      // We set COMPUTE_FRAMES on our builders, which causes ASM to calculate the correct frame
+      // information to insert here.
+      size = size.aggregate(onNotNull.apply(methodVisitor, context));
+      methodVisitor.visitLabel(skipLabel);
+      return size;
+    }
+  }
+
   // Create a new FieldValueGetter subclass.
   @SuppressWarnings("unchecked")
   public static DynamicType.Builder<FieldValueGetter> subclassGetterInterface(
@@ -401,6 +443,7 @@ public class ByteBuddyUtils {
                     });
 
     return builder
+        .visit(new AsmVisitorWrapper.ForDeclaredMethods().writerFlags(ClassWriter.COMPUTE_FRAMES))
         .make()
         .load(
             ReflectHelpers.findClassLoader(((Class) fromType).getClassLoader()),
@@ -584,6 +627,7 @@ public class ByteBuddyUtils {
                       .getOnly()));
 
       // Generate a SerializableFunction to convert the element-type objects.
+      StackManipulation stackManipulation;
       final TypeDescriptor finalComponentType = ReflectUtils.boxIfPrimitive(componentType);
       if (!finalComponentType.hasUnresolvedParameters()) {
         Type convertedComponentType =
@@ -594,16 +638,18 @@ public class ByteBuddyUtils {
                     componentType.getRawType(),
                     convertedComponentType,
                     (s) -> getFactory().createGetterConversions(s).convert(finalComponentType)));
-        return createTransformingContainer(functionType, readListValue);
+        stackManipulation = createTransformingContainer(functionType, readListValue);
       } else {
-        return readListValue;
+        stackManipulation = readListValue;
       }
+      return new ShortCircuitReturnNull(readValue, stackManipulation);
     }
 
     @Override
     protected StackManipulation convertIterable(TypeDescriptor<?> type) {
       TypeDescriptor componentType = ReflectUtils.getIterableComponentType(type);
       Type convertedComponentType = getFactory().createTypeConversion(true).convert(componentType);
+
       final TypeDescriptor finalComponentType = ReflectUtils.boxIfPrimitive(componentType);
       if (!finalComponentType.hasUnresolvedParameters()) {
         ForLoadedType functionType =
@@ -612,7 +658,8 @@ public class ByteBuddyUtils {
                     componentType.getRawType(),
                     convertedComponentType,
                     (s) -> getFactory().createGetterConversions(s).convert(finalComponentType)));
-        return createTransformingContainer(functionType, readValue);
+        StackManipulation stackManipulation = createTransformingContainer(functionType, readValue);
+        return new ShortCircuitReturnNull(readValue, stackManipulation);
       } else {
         return readValue;
       }
@@ -630,7 +677,8 @@ public class ByteBuddyUtils {
                     componentType.getRawType(),
                     convertedComponentType,
                     (s) -> getFactory().createGetterConversions(s).convert(finalComponentType)));
-        return createTransformingContainer(functionType, readValue);
+        StackManipulation stackManipulation = createTransformingContainer(functionType, readValue);
+        return new ShortCircuitReturnNull(readValue, stackManipulation);
       } else {
         return readValue;
       }
@@ -648,7 +696,8 @@ public class ByteBuddyUtils {
                     componentType.getRawType(),
                     convertedComponentType,
                     (s) -> getFactory().createGetterConversions(s).convert(finalComponentType)));
-        return createTransformingContainer(functionType, readValue);
+        StackManipulation stackManipulation = createTransformingContainer(functionType, readValue);
+        return new ShortCircuitReturnNull(readValue, stackManipulation);
       } else {
         return readValue;
       }
@@ -675,27 +724,31 @@ public class ByteBuddyUtils {
                     valueType.getRawType(),
                     convertedValueType,
                     (s) -> getFactory().createGetterConversions(s).convert(valueType)));
-        return new Compound(
-            readValue,
-            TypeCreation.of(keyFunctionType),
-            Duplication.SINGLE,
-            MethodInvocation.invoke(
-                keyFunctionType
-                    .getDeclaredMethods()
-                    .filter(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(0)))
-                    .getOnly()),
-            TypeCreation.of(valueFunctionType),
-            Duplication.SINGLE,
-            MethodInvocation.invoke(
-                valueFunctionType
-                    .getDeclaredMethods()
-                    .filter(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(0)))
-                    .getOnly()),
-            MethodInvocation.invoke(
-                BYTE_BUDDY_UTILS_TYPE
-                    .getDeclaredMethods()
-                    .filter(ElementMatchers.named("getTransformingMap"))
-                    .getOnly()));
+        StackManipulation stackManipulation =
+            new Compound(
+                readValue,
+                TypeCreation.of(keyFunctionType),
+                Duplication.SINGLE,
+                MethodInvocation.invoke(
+                    keyFunctionType
+                        .getDeclaredMethods()
+                        .filter(
+                            ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(0)))
+                        .getOnly()),
+                TypeCreation.of(valueFunctionType),
+                Duplication.SINGLE,
+                MethodInvocation.invoke(
+                    valueFunctionType
+                        .getDeclaredMethods()
+                        .filter(
+                            ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(0)))
+                        .getOnly()),
+                MethodInvocation.invoke(
+                    BYTE_BUDDY_UTILS_TYPE
+                        .getDeclaredMethods()
+                        .filter(ElementMatchers.named("getTransformingMap"))
+                        .getOnly()));
+        return new ShortCircuitReturnNull(readValue, stackManipulation);
       } else {
         return readValue;
       }
@@ -773,7 +826,8 @@ public class ByteBuddyUtils {
                           .and(ElementMatchers.takesArguments(ForLoadedType.of(long.class))))
                   .getOnly()));
 
-      return new StackManipulation.Compound(stackManipulations);
+      StackManipulation stackManipulation = new StackManipulation.Compound(stackManipulations);
+      return new ShortCircuitReturnNull(readValue, stackManipulation);
     }
 
     @Override
@@ -784,14 +838,17 @@ public class ByteBuddyUtils {
       // We must extract the array from the ByteBuffer before returning.
       // NOTE: we only support array-backed byte buffers in these POJOs. Others (e.g. mmaped
       // files) are not supported.
-      return new Compound(
-          readValue,
-          MethodInvocation.invoke(
-              BYTE_BUFFER_TYPE
-                  .getDeclaredMethods()
-                  .filter(
-                      ElementMatchers.named("array").and(ElementMatchers.returns(BYTE_ARRAY_TYPE)))
-                  .getOnly()));
+      StackManipulation stackManipulation =
+          new Compound(
+              readValue,
+              MethodInvocation.invoke(
+                  BYTE_BUFFER_TYPE
+                      .getDeclaredMethods()
+                      .filter(
+                          ElementMatchers.named("array")
+                              .and(ElementMatchers.returns(BYTE_ARRAY_TYPE)))
+                      .getOnly()));
+      return new ShortCircuitReturnNull(readValue, stackManipulation);
     }
 
     @Override
@@ -803,13 +860,16 @@ public class ByteBuddyUtils {
 
       // Otherwise, generate the following code:
       // return value.toString();
-      return new Compound(
-          readValue,
-          MethodInvocation.invoke(
-              CHAR_SEQUENCE_TYPE
-                  .getDeclaredMethods()
-                  .filter(ElementMatchers.named("toString").and(ElementMatchers.takesArguments(0)))
-                  .getOnly()));
+      StackManipulation stackManipulation =
+          new Compound(
+              readValue,
+              MethodInvocation.invoke(
+                  CHAR_SEQUENCE_TYPE
+                      .getDeclaredMethods()
+                      .filter(
+                          ElementMatchers.named("toString").and(ElementMatchers.takesArguments(0)))
+                      .getOnly()));
+      return new ShortCircuitReturnNull(readValue, stackManipulation);
     }
 
     @Override
@@ -824,17 +884,20 @@ public class ByteBuddyUtils {
 
     @Override
     protected StackManipulation convertEnum(TypeDescriptor<?> type) {
-      return new Compound(
-          readValue,
-          MethodInvocation.invoke(
-              ENUM_TYPE
-                  .getDeclaredMethods()
-                  .filter(ElementMatchers.named("ordinal").and(ElementMatchers.takesArguments(0)))
-                  .getOnly()),
-          Assigner.DEFAULT.assign(
-              INTEGER_TYPE.asUnboxed().asGenericType(),
-              INTEGER_TYPE.asGenericType(),
-              Typing.STATIC));
+      StackManipulation stackManipulation =
+          new Compound(
+              readValue,
+              MethodInvocation.invoke(
+                  ENUM_TYPE
+                      .getDeclaredMethods()
+                      .filter(
+                          ElementMatchers.named("ordinal").and(ElementMatchers.takesArguments(0)))
+                      .getOnly()),
+              Assigner.DEFAULT.assign(
+                  INTEGER_TYPE.asUnboxed().asGenericType(),
+                  INTEGER_TYPE.asGenericType(),
+                  Typing.STATIC));
+      return new ShortCircuitReturnNull(readValue, stackManipulation);
     }
 
     @Override
@@ -877,6 +940,7 @@ public class ByteBuddyUtils {
       Type rowElementType =
           getFactory().createTypeConversion(false).convert(type.getComponentType());
       final TypeDescriptor arrayElementType = ReflectUtils.boxIfPrimitive(type.getComponentType());
+      StackManipulation readTransformedValue = readValue;
       if (!arrayElementType.hasUnresolvedParameters()) {
         ForLoadedType conversionFunction =
             new ForLoadedType(
@@ -884,13 +948,13 @@ public class ByteBuddyUtils {
                     TypeDescriptor.of(rowElementType).getRawType(),
                     Primitives.wrap(arrayElementType.getRawType()),
                     (s) -> getFactory().createSetterConversions(s).convert(arrayElementType)));
-        readValue = createTransformingContainer(conversionFunction, readValue);
+        readTransformedValue = createTransformingContainer(conversionFunction, readValue);
       }
 
       // Extract an array from the collection.
       StackManipulation stackManipulation =
           new Compound(
-              readValue,
+              readTransformedValue,
               TypeCasting.to(COLLECTION_TYPE),
               // Call Collection.toArray(T[[]) to extract the array. Push new T[0] on the stack
               // before
@@ -920,7 +984,7 @@ public class ByteBuddyUtils {
                                 .and(ElementMatchers.takesArguments(arrayType)))
                         .getOnly()));
       }
-      return stackManipulation;
+      return new ShortCircuitReturnNull(readValue, stackManipulation);
     }
 
     @Override
@@ -939,7 +1003,7 @@ public class ByteBuddyUtils {
                     (s) -> getFactory().createSetterConversions(s).convert(iterableElementType)));
         StackManipulation transformedContainer =
             createTransformingContainer(conversionFunction, readValue);
-        return transformedContainer;
+        return new ShortCircuitReturnNull(readValue, transformedContainer);
       } else {
         return readValue;
       }
@@ -962,7 +1026,7 @@ public class ByteBuddyUtils {
                     (s) -> getFactory().createSetterConversions(s).convert(collectionElementType)));
         StackManipulation transformedContainer =
             createTransformingContainer(conversionFunction, readValue);
-        return transformedContainer;
+        return new ShortCircuitReturnNull(readValue, transformedContainer);
       } else {
         return readValue;
       }
@@ -976,6 +1040,7 @@ public class ByteBuddyUtils {
               .convert(ReflectUtils.getIterableComponentType(type));
       final TypeDescriptor collectionElementType = ReflectUtils.getIterableComponentType(type);
 
+      StackManipulation readTrasformedValue = readValue;
       if (!collectionElementType.hasUnresolvedParameters()) {
         ForLoadedType conversionFunction =
             new ForLoadedType(
@@ -983,12 +1048,12 @@ public class ByteBuddyUtils {
                     TypeDescriptor.of(rowElementType).getRawType(),
                     collectionElementType.getRawType(),
                     (s) -> getFactory().createSetterConversions(s).convert(collectionElementType)));
-        readValue = createTransformingContainer(conversionFunction, readValue);
+        readTrasformedValue = createTransformingContainer(conversionFunction, readValue);
       }
       // TODO: Don't copy if already a list!
       StackManipulation transformedList =
           new Compound(
-              readValue,
+              readTrasformedValue,
               MethodInvocation.invoke(
                   new ForLoadedType(Lists.class)
                       .getDeclaredMethods()
@@ -996,7 +1061,7 @@ public class ByteBuddyUtils {
                           ElementMatchers.named("newArrayList")
                               .and(ElementMatchers.takesArguments(Iterable.class)))
                       .getOnly()));
-      return transformedList;
+      return new ShortCircuitReturnNull(readValue, transformedList);
     }
 
     @Override
@@ -1008,6 +1073,7 @@ public class ByteBuddyUtils {
           getFactory().createTypeConversion(false).convert(ReflectUtils.getMapType(type, 1));
       final TypeDescriptor valueElementType = ReflectUtils.getMapType(type, 1);
 
+      StackManipulation readTrasformedValue = readValue;
       if (!keyElementType.hasUnresolvedParameters()
           && !valueElementType.hasUnresolvedParameters()) {
         ForLoadedType keyConversionFunction =
@@ -1022,7 +1088,7 @@ public class ByteBuddyUtils {
                     TypeDescriptor.of(rowValueType).getRawType(),
                     valueElementType.getRawType(),
                     (s) -> getFactory().createSetterConversions(s).convert(valueElementType)));
-        readValue =
+        readTrasformedValue =
             new Compound(
                 readValue,
                 TypeCreation.of(keyConversionFunction),
@@ -1047,7 +1113,7 @@ public class ByteBuddyUtils {
                         .filter(ElementMatchers.named("getTransformingMap"))
                         .getOnly()));
       }
-      return readValue;
+      return new ShortCircuitReturnNull(readValue, readTrasformedValue);
     }
 
     @Override
@@ -1077,6 +1143,7 @@ public class ByteBuddyUtils {
                   .getDeclaredMethods()
                   .filter(ElementMatchers.named("getMillis"))
                   .getOnly()));
+
       if (type.isSubtypeOf(TypeDescriptor.of(BaseLocal.class))) {
         // Access DateTimeZone.UTC
         stackManipulations.add(
@@ -1112,7 +1179,8 @@ public class ByteBuddyUtils {
                     .getOnly()));
       }
 
-      return new Compound(stackManipulations);
+      StackManipulation stackManipulation = new Compound(stackManipulations);
+      return new ShortCircuitReturnNull(readValue, stackManipulation);
     }
 
     @Override
@@ -1121,17 +1189,19 @@ public class ByteBuddyUtils {
       // return ByteBuffer.wrap((byte[]) value);
 
       // We currently assume that a byte[] setter will always accept a parameter of type byte[].
-      return new Compound(
-          readValue,
-          TypeCasting.to(BYTE_ARRAY_TYPE),
-          // Create a new ByteBuffer that wraps this byte[].
-          MethodInvocation.invoke(
-              BYTE_BUFFER_TYPE
-                  .getDeclaredMethods()
-                  .filter(
-                      ElementMatchers.named("wrap")
-                          .and(ElementMatchers.takesArguments(BYTE_ARRAY_TYPE)))
-                  .getOnly()));
+      StackManipulation stackManipulation =
+          new Compound(
+              readValue,
+              TypeCasting.to(BYTE_ARRAY_TYPE),
+              // Create a new ByteBuffer that wraps this byte[].
+              MethodInvocation.invoke(
+                  BYTE_BUFFER_TYPE
+                      .getDeclaredMethods()
+                      .filter(
+                          ElementMatchers.named("wrap")
+                              .and(ElementMatchers.takesArguments(BYTE_ARRAY_TYPE)))
+                      .getOnly()));
+      return new ShortCircuitReturnNull(readValue, stackManipulation);
     }
 
     @Override
@@ -1145,20 +1215,22 @@ public class ByteBuddyUtils {
       // return new T((CharacterSequence) value).
 
       ForLoadedType loadedType = new ForLoadedType(type.getRawType());
-      return new StackManipulation.Compound(
-          TypeCreation.of(loadedType),
-          Duplication.SINGLE,
-          // Load the parameter and cast it to a CharSequence.
-          readValue,
-          TypeCasting.to(CHAR_SEQUENCE_TYPE),
-          // Create an element of the field type that wraps this one.
-          MethodInvocation.invoke(
-              loadedType
-                  .getDeclaredMethods()
-                  .filter(
-                      ElementMatchers.isConstructor()
-                          .and(ElementMatchers.takesArguments(CHAR_SEQUENCE_TYPE)))
-                  .getOnly()));
+      StackManipulation stackManipulation =
+          new StackManipulation.Compound(
+              TypeCreation.of(loadedType),
+              Duplication.SINGLE,
+              // Load the parameter and cast it to a CharSequence.
+              readValue,
+              TypeCasting.to(CHAR_SEQUENCE_TYPE),
+              // Create an element of the field type that wraps this one.
+              MethodInvocation.invoke(
+                  loadedType
+                      .getDeclaredMethods()
+                      .filter(
+                          ElementMatchers.isConstructor()
+                              .and(ElementMatchers.takesArguments(CHAR_SEQUENCE_TYPE)))
+                      .getOnly()));
+      return new ShortCircuitReturnNull(readValue, stackManipulation);
     }
 
     @Override
@@ -1178,24 +1250,28 @@ public class ByteBuddyUtils {
       ForLoadedType loadedType = new ForLoadedType(type.getRawType());
 
       // Convert the stored ordinal back to the Java enum constant.
-      return new Compound(
-          // Call EnumType::values() to get an array of all enum constants.
-          MethodInvocation.invoke(
-              loadedType
-                  .getDeclaredMethods()
-                  .filter(
-                      ElementMatchers.named("values")
-                          .and(ElementMatchers.isStatic().and(ElementMatchers.takesArguments(0))))
-                  .getOnly()),
-          // Read the integer enum value.
-          readValue,
-          // Unbox Integer -> int before accessing the array.
-          Assigner.DEFAULT.assign(
-              INTEGER_TYPE.asBoxed().asGenericType(),
-              INTEGER_TYPE.asUnboxed().asGenericType(),
-              Typing.STATIC),
-          // Access the array to return the Java enum type.
-          ArrayAccess.REFERENCE.load());
+      StackManipulation stackManipulation =
+          new Compound(
+              // Call EnumType::values() to get an array of all enum constants.
+              MethodInvocation.invoke(
+                  loadedType
+                      .getDeclaredMethods()
+                      .filter(
+                          ElementMatchers.named("values")
+                              .and(
+                                  ElementMatchers.isStatic()
+                                      .and(ElementMatchers.takesArguments(0))))
+                      .getOnly()),
+              // Read the integer enum value.
+              readValue,
+              // Unbox Integer -> int before accessing the array.
+              Assigner.DEFAULT.assign(
+                  INTEGER_TYPE.asBoxed().asGenericType(),
+                  INTEGER_TYPE.asUnboxed().asGenericType(),
+                  Typing.STATIC),
+              // Access the array to return the Java enum type.
+              ArrayAccess.REFERENCE.load());
+      return new ShortCircuitReturnNull(readValue, stackManipulation);
     }
 
     @Override
diff --git a/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/ConvertHelpers.java b/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/ConvertHelpers.java
index 5c435b5..1e786d1 100644
--- a/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/ConvertHelpers.java
+++ b/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/ConvertHelpers.java
@@ -35,6 +35,7 @@ import org.apache.beam.sdk.util.common.ReflectHelpers;
 import org.apache.beam.sdk.values.Row;
 import org.apache.beam.sdk.values.TypeDescriptor;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.ByteBuddy;
+import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.asm.AsmVisitorWrapper;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.description.type.TypeDescription;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.dynamic.DynamicType;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
@@ -45,6 +46,7 @@ import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.byte
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.StackManipulation;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.member.MethodReturn;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.jar.asm.ClassWriter;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.matcher.ElementMatchers;
 import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.primitives.Primitives;
 
@@ -160,6 +162,7 @@ public class ConvertHelpers {
         (DynamicType.Builder<SerializableFunction>) new ByteBuddy().subclass(genericType);
     try {
       return builder
+          .visit(new AsmVisitorWrapper.ForDeclaredMethods().writerFlags(ClassWriter.COMPUTE_FRAMES))
           .method(ElementMatchers.named("apply"))
           .intercept(new ConvertPrimitiveInstruction(outputType, typeConversionsFactory))
           .make()
diff --git a/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/JavaBeanUtils.java b/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/JavaBeanUtils.java
index e25342b..0b4fe79 100644
--- a/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/JavaBeanUtils.java
+++ b/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/JavaBeanUtils.java
@@ -38,6 +38,7 @@ import org.apache.beam.sdk.schemas.utils.ByteBuddyUtils.TypeConversionsFactory;
 import org.apache.beam.sdk.schemas.utils.ReflectUtils.ClassWithSchema;
 import org.apache.beam.sdk.util.common.ReflectHelpers;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.ByteBuddy;
+import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.asm.AsmVisitorWrapper;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.description.method.MethodDescription.ForLoadedMethod;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.dynamic.DynamicType;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
@@ -51,6 +52,7 @@ import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.byte
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.member.MethodInvocation;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.member.MethodReturn;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.jar.asm.ClassWriter;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.matcher.ElementMatchers;
 import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.Maps;
 
@@ -140,6 +142,7 @@ public class JavaBeanUtils {
     builder = implementGetterMethods(builder, typeInformation, typeConversionsFactory);
     try {
       return builder
+          .visit(new AsmVisitorWrapper.ForDeclaredMethods().writerFlags(ClassWriter.COMPUTE_FRAMES))
           .make()
           .load(
               ReflectHelpers.findClassLoader(
@@ -203,6 +206,7 @@ public class JavaBeanUtils {
     builder = implementSetterMethods(builder, typeInformation, typeConversionsFactory);
     try {
       return builder
+          .visit(new AsmVisitorWrapper.ForDeclaredMethods().writerFlags(ClassWriter.COMPUTE_FRAMES))
           .make()
           .load(
               ReflectHelpers.findClassLoader(
@@ -267,6 +271,7 @@ public class JavaBeanUtils {
                   new ConstructorCreateInstruction(
                       types, clazz, constructor, typeConversionsFactory));
       return builder
+          .visit(new AsmVisitorWrapper.ForDeclaredMethods().writerFlags(ClassWriter.COMPUTE_FRAMES))
           .make()
           .load(
               ReflectHelpers.findClassLoader(clazz.getClassLoader()),
@@ -314,6 +319,7 @@ public class JavaBeanUtils {
                       types, clazz, creator, typeConversionsFactory));
 
       return builder
+          .visit(new AsmVisitorWrapper.ForDeclaredMethods().writerFlags(ClassWriter.COMPUTE_FRAMES))
           .make()
           .load(
               ReflectHelpers.findClassLoader(clazz.getClassLoader()),
diff --git a/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/POJOUtils.java b/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/POJOUtils.java
index aa968b4..7b69e09 100644
--- a/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/POJOUtils.java
+++ b/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/POJOUtils.java
@@ -42,6 +42,7 @@ import org.apache.beam.sdk.schemas.utils.ReflectUtils.ClassWithSchema;
 import org.apache.beam.sdk.util.common.ReflectHelpers;
 import org.apache.beam.sdk.values.TypeDescriptor;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.ByteBuddy;
+import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.asm.AsmVisitorWrapper;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.description.field.FieldDescription.ForLoadedField;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.description.type.TypeDescription.ForLoadedType;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.dynamic.DynamicType;
@@ -61,6 +62,7 @@ import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.byte
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.member.MethodInvocation;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.member.MethodReturn;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.jar.asm.ClassWriter;
 import org.apache.beam.vendor.bytebuddy.v1_9_3.net.bytebuddy.matcher.ElementMatchers;
 import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.Maps;
 
@@ -146,6 +148,7 @@ public class POJOUtils {
               .intercept(new SetFieldCreateInstruction(fields, clazz, typeConversionsFactory));
 
       return builder
+          .visit(new AsmVisitorWrapper.ForDeclaredMethods().writerFlags(ClassWriter.COMPUTE_FRAMES))
           .make()
           .load(
               ReflectHelpers.findClassLoader(clazz.getClassLoader()),
@@ -194,6 +197,7 @@ public class POJOUtils {
                       types, clazz, constructor, typeConversionsFactory));
 
       return builder
+          .visit(new AsmVisitorWrapper.ForDeclaredMethods().writerFlags(ClassWriter.COMPUTE_FRAMES))
           .make()
           .load(
               ReflectHelpers.findClassLoader(clazz.getClassLoader()),
@@ -241,6 +245,7 @@ public class POJOUtils {
                       types, clazz, creator, typeConversionsFactory));
 
       return builder
+          .visit(new AsmVisitorWrapper.ForDeclaredMethods().writerFlags(ClassWriter.COMPUTE_FRAMES))
           .make()
           .load(ReflectHelpers.findClassLoader(), ClassLoadingStrategy.Default.INJECTION)
           .getLoaded()
@@ -284,6 +289,7 @@ public class POJOUtils {
         implementGetterMethods(builder, field, typeInformation.getName(), typeConversionsFactory);
     try {
       return builder
+          .visit(new AsmVisitorWrapper.ForDeclaredMethods().writerFlags(ClassWriter.COMPUTE_FRAMES))
           .make()
           .load(
               ReflectHelpers.findClassLoader(field.getDeclaringClass().getClassLoader()),
@@ -305,6 +311,7 @@ public class POJOUtils {
       String name,
       TypeConversionsFactory typeConversionsFactory) {
     return builder
+        .visit(new AsmVisitorWrapper.ForDeclaredMethods().writerFlags(ClassWriter.COMPUTE_FRAMES))
         .method(ElementMatchers.named("name"))
         .intercept(FixedValue.reference(name))
         .method(ElementMatchers.named("get"))
@@ -362,6 +369,7 @@ public class POJOUtils {
     builder = implementSetterMethods(builder, field, typeConversionsFactory);
     try {
       return builder
+          .visit(new AsmVisitorWrapper.ForDeclaredMethods().writerFlags(ClassWriter.COMPUTE_FRAMES))
           .make()
           .load(
               ReflectHelpers.findClassLoader(field.getDeclaringClass().getClassLoader()),
@@ -382,6 +390,7 @@ public class POJOUtils {
       Field field,
       TypeConversionsFactory typeConversionsFactory) {
     return builder
+        .visit(new AsmVisitorWrapper.ForDeclaredMethods().writerFlags(ClassWriter.COMPUTE_FRAMES))
         .method(ElementMatchers.named("name"))
         .intercept(FixedValue.reference(field.getName()))
         .method(ElementMatchers.named("set"))
diff --git a/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/JavaBeanSchemaTest.java b/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/JavaBeanSchemaTest.java
index feb51db..5ec69e5 100644
--- a/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/JavaBeanSchemaTest.java
+++ b/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/JavaBeanSchemaTest.java
@@ -17,6 +17,7 @@
  */
 package org.apache.beam.sdk.schemas;
 
+import static org.apache.beam.sdk.schemas.utils.TestJavaBeans.ALL_NULLABLE_BEAN_SCHEMA;
 import static org.apache.beam.sdk.schemas.utils.TestJavaBeans.ARRAY_OF_BYTE_ARRAY_BEAM_SCHEMA;
 import static org.apache.beam.sdk.schemas.utils.TestJavaBeans.ITERABLE_BEAM_SCHEMA;
 import static org.apache.beam.sdk.schemas.utils.TestJavaBeans.NESTED_ARRAYS_BEAM_SCHEMA;
@@ -27,6 +28,7 @@ import static org.apache.beam.sdk.schemas.utils.TestJavaBeans.PRIMITIVE_ARRAY_BE
 import static org.apache.beam.sdk.schemas.utils.TestJavaBeans.SIMPLE_BEAN_SCHEMA;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
@@ -37,6 +39,7 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import org.apache.beam.sdk.schemas.utils.SchemaTestUtils;
+import org.apache.beam.sdk.schemas.utils.TestJavaBeans.AllNullableBean;
 import org.apache.beam.sdk.schemas.utils.TestJavaBeans.ArrayOfByteArray;
 import org.apache.beam.sdk.schemas.utils.TestJavaBeans.IterableBean;
 import org.apache.beam.sdk.schemas.utils.TestJavaBeans.MismatchingNullableBean;
@@ -160,6 +163,47 @@ public class JavaBeanSchemaTest {
   }
 
   @Test
+  public void testNullableToRow() throws NoSuchSchemaException {
+    SchemaRegistry registry = SchemaRegistry.createDefault();
+    AllNullableBean bean = new AllNullableBean();
+    Row row = registry.getToRowFunction(AllNullableBean.class).apply(bean);
+
+    assertEquals(12, row.getFieldCount());
+    assertNull(row.getString("str"));
+    assertNull(row.getByte("aByte"));
+    assertNull(row.getInt16("aShort"));
+    assertNull(row.getInt32("anInt"));
+    assertNull(row.getInt64("aLong"));
+    assertNull(row.getBoolean("aBoolean"));
+    assertNull(row.getDateTime("dateTime"));
+    assertNull(row.getDateTime("instant"));
+    assertNull(row.getBytes("bytes"));
+    assertNull(row.getBytes("byteBuffer"));
+    assertNull(row.getDecimal("bigDecimal"));
+    assertNull(row.getString("stringBuilder"));
+  }
+
+  @Test
+  public void testNullableFromRow() throws NoSuchSchemaException {
+    SchemaRegistry registry = SchemaRegistry.createDefault();
+    Row row = Row.nullRow(ALL_NULLABLE_BEAN_SCHEMA);
+
+    AllNullableBean bean = registry.getFromRowFunction(AllNullableBean.class).apply(row);
+    assertNull(bean.getStr());
+    assertNull(bean.getaByte());
+    assertNull(bean.getaShort());
+    assertNull(bean.getAnInt());
+    assertNull(bean.getaLong());
+    assertNull(bean.isaBoolean());
+    assertNull(bean.getDateTime());
+    assertNull(bean.getInstant());
+    assertNull(bean.getBytes());
+    assertNull(bean.getByteBuffer());
+    assertNull(bean.getBigDecimal());
+    assertNull(bean.getStringBuilder());
+  }
+
+  @Test
   public void testToRowSerializable() throws NoSuchSchemaException {
     SchemaRegistry registry = SchemaRegistry.createDefault();
     SerializableUtils.ensureSerializableRoundTrip(registry.getToRowFunction(SimpleBean.class));
diff --git a/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/JavaFieldSchemaTest.java b/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/JavaFieldSchemaTest.java
index 4134a57..9dcdcd4 100644
--- a/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/JavaFieldSchemaTest.java
+++ b/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/JavaFieldSchemaTest.java
@@ -23,6 +23,7 @@ import static org.apache.beam.sdk.schemas.utils.TestPOJOs.NESTED_MAP_POJO_SCHEMA
 import static org.apache.beam.sdk.schemas.utils.TestPOJOs.NESTED_NULLABLE_SCHEMA;
 import static org.apache.beam.sdk.schemas.utils.TestPOJOs.NESTED_POJO_SCHEMA;
 import static org.apache.beam.sdk.schemas.utils.TestPOJOs.NULLABLES_SCHEMA;
+import static org.apache.beam.sdk.schemas.utils.TestPOJOs.NULLABLE_POJO_SCHEMA;
 import static org.apache.beam.sdk.schemas.utils.TestPOJOs.POJO_WITH_ENUM_SCHEMA;
 import static org.apache.beam.sdk.schemas.utils.TestPOJOs.POJO_WITH_ITERABLE;
 import static org.apache.beam.sdk.schemas.utils.TestPOJOs.POJO_WITH_NESTED_ARRAY_SCHEMA;
@@ -47,6 +48,7 @@ import org.apache.beam.sdk.schemas.utils.TestPOJOs.NestedArrayPOJO;
 import org.apache.beam.sdk.schemas.utils.TestPOJOs.NestedArraysPOJO;
 import org.apache.beam.sdk.schemas.utils.TestPOJOs.NestedMapPOJO;
 import org.apache.beam.sdk.schemas.utils.TestPOJOs.NestedPOJO;
+import org.apache.beam.sdk.schemas.utils.TestPOJOs.NullablePOJO;
 import org.apache.beam.sdk.schemas.utils.TestPOJOs.POJOWithNestedNullable;
 import org.apache.beam.sdk.schemas.utils.TestPOJOs.POJOWithNullables;
 import org.apache.beam.sdk.schemas.utils.TestPOJOs.PojoWithEnum;
@@ -91,6 +93,10 @@ public class JavaFieldSchemaTest {
         new StringBuilder(name).append("builder"));
   }
 
+  private NullablePOJO createNullable() {
+    return new NullablePOJO(null, null, null, null, null, null, null, null, null, null, null, null);
+  }
+
   private AnnotatedSimplePojo createAnnotated(String name) {
     return new AnnotatedSimplePojo(
         name,
@@ -190,6 +196,54 @@ public class JavaFieldSchemaTest {
   }
 
   @Test
+  public void testNullableSchema() throws NoSuchSchemaException {
+    SchemaRegistry registry = SchemaRegistry.createDefault();
+    Schema schema = registry.getSchema(NullablePOJO.class);
+    SchemaTestUtils.assertSchemaEquivalent(NULLABLE_POJO_SCHEMA, schema);
+  }
+
+  @Test
+  public void testNullableToRow() throws NoSuchSchemaException {
+    SchemaRegistry registry = SchemaRegistry.createDefault();
+    NullablePOJO pojo = createNullable();
+    Row row = registry.getToRowFunction(NullablePOJO.class).apply(pojo);
+
+    assertEquals(12, row.getFieldCount());
+    assertNull(row.getString("str"));
+    assertNull(row.getByte("aByte"));
+    assertNull(row.getInt16("aShort"));
+    assertNull(row.getInt32("anInt"));
+    assertNull(row.getInt64("aLong"));
+    assertNull(row.getBoolean("aBoolean"));
+    assertNull(row.getDateTime("dateTime"));
+    assertNull(row.getDateTime("instant"));
+    assertNull(row.getBytes("bytes"));
+    assertNull(row.getBytes("byteBuffer"));
+    assertNull(row.getDecimal("bigDecimal"));
+    assertNull(row.getString("stringBuilder"));
+  }
+
+  @Test
+  public void testNullableFromRow() throws NoSuchSchemaException {
+    SchemaRegistry registry = SchemaRegistry.createDefault();
+    Row row = Row.nullRow(NULLABLE_POJO_SCHEMA);
+
+    NullablePOJO pojo = registry.getFromRowFunction(NullablePOJO.class).apply(row);
+    assertNull(pojo.str);
+    assertNull(pojo.aByte);
+    assertNull(pojo.aShort);
+    assertNull(pojo.anInt);
+    assertNull(pojo.aLong);
+    assertNull(pojo.aBoolean);
+    assertNull(pojo.dateTime);
+    assertNull(pojo.instant);
+    assertNull(pojo.bytes);
+    assertNull(pojo.byteBuffer);
+    assertNull(pojo.bigDecimal);
+    assertNull(pojo.stringBuilder);
+  }
+
+  @Test
   public void testToRowSerializable() throws NoSuchSchemaException {
     SchemaRegistry registry = SchemaRegistry.createDefault();
     SerializableUtils.ensureSerializableRoundTrip(registry.getToRowFunction(SimplePOJO.class));
diff --git a/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/utils/TestJavaBeans.java b/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/utils/TestJavaBeans.java
index 32cf264..6783945 100644
--- a/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/utils/TestJavaBeans.java
+++ b/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/utils/TestJavaBeans.java
@@ -313,6 +313,205 @@ public class TestJavaBeans {
           .addStringField("stringBuilder")
           .build();
 
+  /** A simple Bean containing basic nullable types. * */
+  @DefaultSchema(JavaBeanSchema.class)
+  public static class AllNullableBean {
+    @Nullable private String str;
+    @Nullable private Byte aByte;
+    @Nullable private Short aShort;
+    @Nullable private Integer anInt;
+    @Nullable private Long aLong;
+    @Nullable private Boolean aBoolean;
+    @Nullable private DateTime dateTime;
+    @Nullable private Instant instant;
+    @Nullable private byte[] bytes;
+    @Nullable private ByteBuffer byteBuffer;
+    @Nullable private BigDecimal bigDecimal;
+    @Nullable private StringBuilder stringBuilder;
+
+    public AllNullableBean() {
+      this.str = null;
+      this.aByte = null;
+      this.aShort = null;
+      this.anInt = null;
+      this.aLong = null;
+      this.aBoolean = null;
+      this.dateTime = null;
+      this.instant = null;
+      this.bytes = null;
+      this.byteBuffer = null;
+      this.bigDecimal = null;
+      this.stringBuilder = null;
+    }
+
+    @Nullable
+    public String getStr() {
+      return str;
+    }
+
+    public void setStr(@Nullable String str) {
+      this.str = str;
+    }
+
+    @Nullable
+    public Byte getaByte() {
+      return aByte;
+    }
+
+    public void setaByte(@Nullable Byte aByte) {
+      this.aByte = aByte;
+    }
+
+    @Nullable
+    public Short getaShort() {
+      return aShort;
+    }
+
+    public void setaShort(@Nullable Short aShort) {
+      this.aShort = aShort;
+    }
+
+    @Nullable
+    public Integer getAnInt() {
+      return anInt;
+    }
+
+    public void setAnInt(@Nullable Integer anInt) {
+      this.anInt = anInt;
+    }
+
+    @Nullable
+    public Long getaLong() {
+      return aLong;
+    }
+
+    public void setaLong(@Nullable Long aLong) {
+      this.aLong = aLong;
+    }
+
+    @Nullable
+    public Boolean isaBoolean() {
+      return aBoolean;
+    }
+
+    public void setaBoolean(@Nullable Boolean aBoolean) {
+      this.aBoolean = aBoolean;
+    }
+
+    @Nullable
+    public DateTime getDateTime() {
+      return dateTime;
+    }
+
+    public void setDateTime(@Nullable DateTime dateTime) {
+      this.dateTime = dateTime;
+    }
+
+    @Nullable
+    public byte[] getBytes() {
+      return bytes;
+    }
+
+    public void setBytes(@Nullable byte[] bytes) {
+      this.bytes = bytes;
+    }
+
+    @Nullable
+    public ByteBuffer getByteBuffer() {
+      return byteBuffer;
+    }
+
+    public void setByteBuffer(@Nullable ByteBuffer byteBuffer) {
+      this.byteBuffer = byteBuffer;
+    }
+
+    @Nullable
+    public Instant getInstant() {
+      return instant;
+    }
+
+    public void setInstant(@Nullable Instant instant) {
+      this.instant = instant;
+    }
+
+    @Nullable
+    public BigDecimal getBigDecimal() {
+      return bigDecimal;
+    }
+
+    public void setBigDecimal(@Nullable BigDecimal bigDecimal) {
+      this.bigDecimal = bigDecimal;
+    }
+
+    @Nullable
+    public StringBuilder getStringBuilder() {
+      return stringBuilder;
+    }
+
+    public void setStringBuilder(@Nullable StringBuilder stringBuilder) {
+      this.stringBuilder = stringBuilder;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      SimpleBean that = (SimpleBean) o;
+      return Objects.equals(aByte, that.aByte)
+          && Objects.equals(aShort, that.aShort)
+          && Objects.equals(anInt, that.anInt)
+          && Objects.equals(aLong, that.aLong)
+          && Objects.equals(aBoolean, that.aBoolean)
+          && Objects.equals(str, that.str)
+          && Objects.equals(dateTime, that.dateTime)
+          && Objects.equals(instant, that.instant)
+          && Arrays.equals(bytes, that.bytes)
+          && Objects.equals(byteBuffer, that.byteBuffer)
+          && Objects.equals(bigDecimal, that.bigDecimal)
+          && Objects.equals(stringBuilder, that.stringBuilder);
+    }
+
+    @Override
+    public int hashCode() {
+      int result =
+          Objects.hash(
+              str,
+              aByte,
+              aShort,
+              anInt,
+              aLong,
+              aBoolean,
+              dateTime,
+              instant,
+              byteBuffer,
+              bigDecimal,
+              stringBuilder);
+      result = 31 * result + Arrays.hashCode(bytes);
+      return result;
+    }
+  }
+
+  /** The schema for {@link AllNullableBean}. * */
+  public static final Schema ALL_NULLABLE_BEAN_SCHEMA =
+      Schema.builder()
+          .addNullableField("str", FieldType.STRING)
+          .addNullableField("aByte", FieldType.BYTE)
+          .addNullableField("aShort", FieldType.INT16)
+          .addNullableField("anInt", FieldType.INT32)
+          .addNullableField("aLong", FieldType.INT64)
+          .addNullableField("aBoolean", FieldType.BOOLEAN)
+          .addNullableField("dateTime", FieldType.DATETIME)
+          .addNullableField("instant", FieldType.DATETIME)
+          .addNullableField("bytes", FieldType.BYTES)
+          .addNullableField("byteBuffer", FieldType.BYTES)
+          .addNullableField("bigDecimal", FieldType.DECIMAL)
+          .addNullableField("stringBuilder", FieldType.STRING)
+          .build();
+
   /** A simple Bean containing basic types. * */
   @DefaultSchema(JavaBeanSchema.class)
   public static class SimpleBeanWithAnnotations {
diff --git a/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/utils/TestPOJOs.java b/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/utils/TestPOJOs.java
index 0952d17..be779c0 100644
--- a/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/utils/TestPOJOs.java
+++ b/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/utils/TestPOJOs.java
@@ -901,4 +901,109 @@ public class TestPOJOs {
       Schema.builder()
           .addLogicalTypeField("color", EnumerationType.create("RED", "GREEN", "BLUE"))
           .build();
+
+  /** A simple POJO containing nullable basic types. * */
+  @DefaultSchema(JavaFieldSchema.class)
+  public static class NullablePOJO {
+    @Nullable public String str;
+    @Nullable public Byte aByte;
+    @Nullable public Short aShort;
+    @Nullable public Integer anInt;
+    @Nullable public Long aLong;
+    @Nullable public Boolean aBoolean;
+    @Nullable public DateTime dateTime;
+    @Nullable public Instant instant;
+    @Nullable public byte[] bytes;
+    @Nullable public ByteBuffer byteBuffer;
+    @Nullable public BigDecimal bigDecimal;
+    @Nullable public StringBuilder stringBuilder;
+
+    public NullablePOJO() {}
+
+    public NullablePOJO(
+        String str,
+        Byte aByte,
+        Short aShort,
+        Integer anInt,
+        Long aLong,
+        Boolean aBoolean,
+        DateTime dateTime,
+        Instant instant,
+        byte[] bytes,
+        ByteBuffer byteBuffer,
+        BigDecimal bigDecimal,
+        StringBuilder stringBuilder) {
+      this.str = str;
+      this.aByte = aByte;
+      this.aShort = aShort;
+      this.anInt = anInt;
+      this.aLong = aLong;
+      this.aBoolean = aBoolean;
+      this.dateTime = dateTime;
+      this.instant = instant;
+      this.bytes = bytes;
+      this.byteBuffer = byteBuffer;
+      this.bigDecimal = bigDecimal;
+      this.stringBuilder = stringBuilder;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      NullablePOJO that = (NullablePOJO) o;
+      return Objects.equals(aByte, that.aByte)
+          && Objects.equals(aShort, that.aShort)
+          && Objects.equals(anInt, that.anInt)
+          && Objects.equals(aLong, that.aLong)
+          && Objects.equals(aBoolean, that.aBoolean)
+          && Objects.equals(str, that.str)
+          && Objects.equals(dateTime, that.dateTime)
+          && Objects.equals(instant, that.instant)
+          && Arrays.equals(bytes, that.bytes)
+          && Objects.equals(byteBuffer, that.byteBuffer)
+          && Objects.equals(bigDecimal, that.bigDecimal)
+          && Objects.equals(stringBuilder.toString(), that.stringBuilder.toString());
+    }
+
+    @Override
+    public int hashCode() {
+      int result =
+          Objects.hash(
+              str,
+              aByte,
+              aShort,
+              anInt,
+              aLong,
+              aBoolean,
+              dateTime,
+              instant,
+              byteBuffer,
+              bigDecimal,
+              stringBuilder);
+      result = 31 * result + Arrays.hashCode(bytes);
+      return result;
+    }
+  }
+
+  /** The schema for {@link NullablePOJO}. * */
+  public static final Schema NULLABLE_POJO_SCHEMA =
+      Schema.builder()
+          .addNullableField("str", FieldType.STRING)
+          .addNullableField("aByte", FieldType.BYTE)
+          .addNullableField("aShort", FieldType.INT16)
+          .addNullableField("anInt", FieldType.INT32)
+          .addNullableField("aLong", FieldType.INT64)
+          .addNullableField("aBoolean", FieldType.BOOLEAN)
+          .addNullableField("dateTime", FieldType.DATETIME)
+          .addNullableField("instant", FieldType.DATETIME)
+          .addNullableField("bytes", FieldType.BYTES)
+          .addNullableField("byteBuffer", FieldType.BYTES)
+          .addNullableField("bigDecimal", FieldType.DECIMAL)
+          .addNullableField("stringBuilder", FieldType.STRING)
+          .build();
 }
diff --git a/sdks/java/extensions/protobuf/src/main/java/org/apache/beam/sdk/extensions/protobuf/ProtoByteBuddyUtils.java b/sdks/java/extensions/protobuf/src/main/java/org/apache/beam/sdk/extensions/protobuf/ProtoByteBuddyUtils.java
index bafa38f..2b0af35 100644
--- a/sdks/java/extensions/protobuf/src/main/java/org/apache/beam/sdk/extensions/protobuf/ProtoByteBuddyUtils.java
+++ b/sdks/java/extensions/protobuf/src/main/java/org/apache/beam/sdk/extensions/protobuf/ProtoByteBuddyUtils.java
@@ -1019,6 +1019,9 @@ public class ProtoByteBuddyUtils {
               .intercept(new BuilderSupplier(protoClass));
       Supplier supplier =
           builder
+              .visit(
+                  new AsmVisitorWrapper.ForDeclaredMethods()
+                      .writerFlags(ClassWriter.COMPUTE_FRAMES))
               .make()
               .load(ReflectHelpers.findClassLoader(), ClassLoadingStrategy.Default.INJECTION)
               .getLoaded()