Update JRuby to leverage pure-Ruby DSL.

JRuby unit and conformance test fixes.
diff --git a/ruby/Rakefile b/ruby/Rakefile
index c7187a6..8aae2ee 100644
--- a/ruby/Rakefile
+++ b/ruby/Rakefile
@@ -67,16 +67,18 @@
 end
 
 if RUBY_PLATFORM == "java"
-  if `which mvn` == ''
-    raise ArgumentError, "maven needs to be installed"
-  end
-  task :clean do
+  task :clean => :require_mvn do
     system("mvn --batch-mode clean")
   end
 
-  task :compile do
+  task :compile => :require_mvn do
     system("mvn --batch-mode package")
   end
+
+  task :require_mvn do
+    raise ArgumentError, "maven needs to be installed" if `which mvn` == ''
+  end
+
 else
   Rake::ExtensionTask.new("protobuf_c", spec) do |ext|
     unless RUBY_PLATFORM =~ /darwin/
@@ -95,7 +97,22 @@
     ]
   end
 
+  task 'gem:java' do
+    sh "rm Gemfile.lock"
+    require 'rake_compiler_dock'
+    # Specify the repo root as the working and mount directory to provide access
+    # to the java directory
+    repo_root = File.realdirpath File.join(Dir.pwd, '..')
+    RakeCompilerDock.sh <<-"EOT", platform: 'jruby', rubyvm: :jruby, mountdir: repo_root, workdir: repo_root
+      sudo apt-get install maven -y && \
+      cd java && mvn install -Dmaven.test.skip=true && cd ../ruby && \
+      bundle && \
+      IN_DOCKER=true rake compile gem
+    EOT
+  end
+
   task 'gem:windows' do
+    sh "rm Gemfile.lock"
     require 'rake_compiler_dock'
     ['x86-mingw32', 'x64-mingw32', 'x86_64-linux', 'x86-linux'].each do |plat|
       RakeCompilerDock.sh <<-"EOT", platform: plat
@@ -111,7 +128,7 @@
       system "rake cross native gem RUBY_CC_VERSION=3.0.0:2.7.0:2.6.0:2.5.1:2.4.0:2.3.0"
     end
   else
-    task 'gem:native' => [:genproto, 'gem:windows']
+    task 'gem:native' => [:genproto, 'gem:windows', 'gem:java']
   end
 end
 
diff --git a/ruby/lib/google/protobuf.rb b/ruby/lib/google/protobuf.rb
index 9368768..a68154d 100644
--- a/ruby/lib/google/protobuf.rb
+++ b/ruby/lib/google/protobuf.rb
@@ -51,9 +51,10 @@
     require 'google/protobuf_c'
   end
 
-  require 'google/protobuf/descriptor_dsl'
 end
 
+require 'google/protobuf/descriptor_dsl'
+
 require 'google/protobuf/repeated_field'
 
 module Google
diff --git a/ruby/pom.xml b/ruby/pom.xml
index 6c96bf4..0049858 100644
--- a/ruby/pom.xml
+++ b/ruby/pom.xml
@@ -88,7 +88,7 @@
         <dependency>
             <groupId>com.google.protobuf</groupId>
             <artifactId>protobuf-java-util</artifactId>
-            <version>3.13.0</version>
+            <version>3.18.0</version>
         </dependency>
     </dependencies>
 </project>
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyBuilder.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyBuilder.java
deleted file mode 100644
index b19ea64..0000000
--- a/ruby/src/main/java/com/google/protobuf/jruby/RubyBuilder.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Protocol Buffers - Google's data interchange format
- * Copyright 2014 Google Inc.  All rights reserved.
- * https://developers.google.com/protocol-buffers/
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- *     * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package com.google.protobuf.jruby;
-
-import org.jruby.*;
-import org.jruby.anno.JRubyClass;
-import org.jruby.anno.JRubyMethod;
-import org.jruby.runtime.*;
-import org.jruby.runtime.builtin.IRubyObject;
-
-@JRubyClass(name = "Builder")
-public class RubyBuilder extends RubyObject {
-    public static void createRubyBuilder(Ruby runtime) {
-        RubyModule internal = runtime.getClassFromPath("Google::Protobuf::Internal");
-        RubyClass cBuilder = internal.defineClassUnder("Builder", runtime.getObject(), new ObjectAllocator() {
-            @Override
-            public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
-                return new RubyBuilder(runtime, klazz);
-            }
-        });
-        cBuilder.defineAnnotatedMethods(RubyBuilder.class);
-    }
-
-    public RubyBuilder(Ruby runtime, RubyClass metaClass) {
-        super(runtime, metaClass);
-        this.cFileBuilderContext = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Internal::FileBuilderContext");
-    }
-
-    /*
-     * call-seq:
-     *     Builder.new => builder
-     *
-     * Creates a new Builder. A Builder can accumulate a set of new message and enum
-     * descriptors and atomically register them into a pool in a way that allows for
-     * (co)recursive type references.
-     */
-    @JRubyMethod
-    public IRubyObject initialize(ThreadContext context, IRubyObject descriptorPool) {
-        this.descriptorPool = (RubyDescriptorPool) descriptorPool;
-        return this;
-    }
-
-    /*
-     * call-seq:
-     *     Builder.add_message(name, &block)
-     *
-     * Old and deprecated way to create a new descriptor.
-     * See FileBuilderContext.add_message for the recommended way.
-     *
-     * Exists for backwards compatibility to allow building descriptor pool for
-     * files generated by protoc which don't add messages within "add_file" block.
-     * Descriptors created this way get assigned to a default empty FileDescriptor.
-     */
-    @JRubyMethod(name = "add_message")
-    public IRubyObject addMessage(ThreadContext context, IRubyObject name, Block block) {
-        ensureDefaultFileBuilder(context);
-        defaultFileBuilder.addMessage(context, name, block);
-        return context.nil;
-    }
-
-    /*
-     * call-seq:
-     *     Builder.add_enum(name, &block)
-     *
-     * Old and deprecated way to create a new enum descriptor.
-     * See FileBuilderContext.add_enum for the recommended way.
-     *
-     * Exists for backwards compatibility to allow building descriptor pool for
-     * files generated by protoc which don't add enums within "add_file" block.
-     * Enum descriptors created this way get assigned to a default empty
-     * FileDescriptor.
-     */
-    @JRubyMethod(name = "add_enum")
-    public IRubyObject addEnum(ThreadContext context, IRubyObject name, Block block) {
-        ensureDefaultFileBuilder(context);
-        defaultFileBuilder.addEnum(context, name, block);
-        return context.nil;
-    }
-
-    /*
-     * call-seq:
-     *     Builder.add_file(name, options = nil, &block)
-     *
-     * Creates a new, file descriptor with the given name and options and invokes
-     * the block in the context of a FileBuilderContext on that descriptor. The
-     * block can then call FileBuilderContext#add_message or
-     * FileBuilderContext#add_enum to define new messages or enums, respectively.
-     *
-     * This is the recommended, idiomatic way to build file descriptors.
-     */
-    @JRubyMethod(name = "add_file")
-    public IRubyObject addFile(ThreadContext context, IRubyObject name, IRubyObject options, Block block) {
-        RubyFileBuilderContext ctx = (RubyFileBuilderContext) cFileBuilderContext.newInstance(context, descriptorPool, name, options, Block.NULL_BLOCK);
-        ctx.instance_eval(context, block);
-        ctx.build(context);
-        return context.nil;
-    }
-
-    /*
-     * Used to trigger the build when using the deprecated syntax
-     */
-    protected void build(ThreadContext context) {
-        if (defaultFileBuilder != null) {
-            defaultFileBuilder.build(context);
-        }
-    }
-
-    private void ensureDefaultFileBuilder(ThreadContext context) {
-        if (defaultFileBuilder == null) {
-            this.defaultFileBuilder = (RubyFileBuilderContext) cFileBuilderContext.newInstance(context, descriptorPool, context.runtime.newString("ruby_default_file.proto"), Block.NULL_BLOCK);
-        }
-    }
-
-    private RubyClass cFileBuilderContext;
-    private RubyDescriptorPool descriptorPool;
-    private RubyFileBuilderContext defaultFileBuilder;
-}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptorPool.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptorPool.java
index 99a7f02..6cdb341 100644
--- a/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptorPool.java
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptorPool.java
@@ -37,9 +37,11 @@
 import com.google.protobuf.Descriptors.DescriptorValidationException;
 import com.google.protobuf.Descriptors.EnumDescriptor;
 import com.google.protobuf.Descriptors.FileDescriptor;
+import com.google.protobuf.InvalidProtocolBufferException;
 import org.jruby.*;
 import org.jruby.anno.JRubyClass;
 import org.jruby.anno.JRubyMethod;
+import org.jruby.exceptions.RaiseException;
 import org.jruby.runtime.*;
 import org.jruby.runtime.builtin.IRubyObject;
 
@@ -61,7 +63,6 @@
 
         cDescriptorPool.defineAnnotatedMethods(RubyDescriptorPool.class);
         descriptorPool = (RubyDescriptorPool) cDescriptorPool.newInstance(runtime.getCurrentContext(), Block.NULL_BLOCK);
-        cBuilder = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Internal::Builder");
         cDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Descriptor");
         cEnumDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::EnumDescriptor");
     }
@@ -74,9 +75,10 @@
 
     @JRubyMethod
     public IRubyObject build(ThreadContext context, Block block) {
-        RubyBuilder ctx = (RubyBuilder) cBuilder.newInstance(context, this, Block.NULL_BLOCK);
+        RubyClass cBuilder = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::Internal::Builder");
+        RubyBasicObject ctx = (RubyBasicObject) cBuilder.newInstance(context, this, Block.NULL_BLOCK);
         ctx.instance_eval(context, block);
-        ctx.build(context); // Needs to be called to support the deprecated syntax
+        ctx.callMethod(context, "build"); // Needs to be called to support the deprecated syntax
         return context.nil;
     }
 
@@ -109,6 +111,18 @@
         return descriptorPool;
     }
 
+    @JRubyMethod(required = 1)
+    public IRubyObject add_serialized_file (ThreadContext context, IRubyObject data ) {
+        byte[] bin = data.convertToString().getBytes();
+        try {
+            FileDescriptorProto.Builder builder = FileDescriptorProto.newBuilder().mergeFrom(bin);
+            registerFileDescriptor(context, builder);
+        } catch (InvalidProtocolBufferException e) {
+            throw RaiseException.from(context.runtime, (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::ParseError"), e.getMessage());
+        }
+        return context.nil;
+    }
+
     protected void registerFileDescriptor(ThreadContext context, FileDescriptorProto.Builder builder) {
         final FileDescriptor fd;
         try {
@@ -157,7 +171,6 @@
         return fileDescriptors.toArray(new FileDescriptor[fileDescriptors.size()]);
     }
 
-    private static RubyClass cBuilder;
     private static RubyClass cDescriptor;
     private static RubyClass cEnumDescriptor;
     private static RubyDescriptorPool descriptorPool;
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumBuilderContext.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumBuilderContext.java
deleted file mode 100644
index 38d31ad..0000000
--- a/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumBuilderContext.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Protocol Buffers - Google's data interchange format
- * Copyright 2014 Google Inc.  All rights reserved.
- * https://developers.google.com/protocol-buffers/
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- *     * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package com.google.protobuf.jruby;
-
-import com.google.protobuf.DescriptorProtos.EnumDescriptorProto;
-import org.jruby.Ruby;
-import org.jruby.RubyClass;
-import org.jruby.RubyModule;
-import org.jruby.RubyNumeric;
-import org.jruby.RubyObject;
-import org.jruby.anno.JRubyClass;
-import org.jruby.anno.JRubyMethod;
-import org.jruby.runtime.ObjectAllocator;
-import org.jruby.runtime.ThreadContext;
-import org.jruby.runtime.builtin.IRubyObject;
-
-@JRubyClass(name = "EnumBuilderContext")
-public class RubyEnumBuilderContext extends RubyObject {
-    public static void createRubyEnumBuilderContext(Ruby runtime) {
-        RubyModule internal = runtime.getClassFromPath("Google::Protobuf::Internal");
-        RubyClass cEnumBuilderContext = internal.defineClassUnder("EnumBuilderContext", runtime.getObject(), new ObjectAllocator() {
-            @Override
-            public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
-                return new RubyEnumBuilderContext(runtime, klazz);
-            }
-        });
-        cEnumBuilderContext.defineAnnotatedMethods(RubyEnumBuilderContext.class);
-    }
-
-    public RubyEnumBuilderContext(Ruby ruby, RubyClass klazz) {
-        super(ruby, klazz);
-    }
-
-    @JRubyMethod
-    public IRubyObject initialize(ThreadContext context, IRubyObject fileBuilderContext, IRubyObject name) {
-        this.fileBuilderContext = (RubyFileBuilderContext) fileBuilderContext;
-        this.builder = this.fileBuilderContext.getNewEnumBuilder();
-        this.builder.setName(name.asJavaString());
-        this.builder.getOptionsBuilder().setAllowAlias(true);
-
-        return this;
-    }
-
-    /*
-     * call-seq:
-     *     EnumBuilder.add_value(name, number)
-     *
-     * Adds the given name => number mapping to the enum type. Name must be a Ruby
-     * symbol.
-     */
-    @JRubyMethod
-    public IRubyObject value(ThreadContext context, IRubyObject name, IRubyObject number) {
-        this.builder.addValueBuilder()
-            .setName(name.asJavaString())
-            .setNumber(RubyNumeric.num2int(number));
-        return context.nil;
-    }
-
-    private EnumDescriptorProto.Builder builder;
-    private RubyFileBuilderContext fileBuilderContext;
-}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java
index e9c1f10..26f00db 100644
--- a/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java
@@ -35,6 +35,7 @@
 import com.google.protobuf.DescriptorProtos.EnumDescriptorProto;
 import com.google.protobuf.Descriptors.EnumDescriptor;
 import com.google.protobuf.Descriptors.EnumValueDescriptor;
+import com.google.protobuf.Descriptors.FileDescriptor;
 import org.jruby.Ruby;
 import org.jruby.RubyClass;
 import org.jruby.RubyModule;
@@ -151,6 +152,7 @@
         Ruby runtime = context.runtime;
 
         RubyModule enumModule = RubyModule.newModule(runtime);
+        boolean defaultValueRequiredButNotFound = descriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3;
         for (EnumValueDescriptor value : descriptor.getValues()) {
             String name = value.getName();
             // Make sure its a valid constant name before trying to create it
@@ -159,8 +161,14 @@
             } else {
                 runtime.getWarnings().warn("Enum value " + name + " does not start with an uppercase letter as is required for Ruby constants.");
             }
+            if (value.getNumber() == 0) {
+                defaultValueRequiredButNotFound = false;
+            }
         }
 
+        if (defaultValueRequiredButNotFound) {
+            throw Utils.createTypeError(context, "Enum definition " + name + " does not contain a value for '0'");
+        }
         enumModule.instance_variable_set(runtime.newString(Utils.DESCRIPTOR_INSTANCE_VAR), this);
         enumModule.defineAnnotatedMethods(RubyEnum.class);
         return enumModule;
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java
index 8ac5e4c..e9594d8 100644
--- a/ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java
@@ -32,8 +32,8 @@
 
 package com.google.protobuf.jruby;
 
-import com.google.protobuf.Descriptors.Descriptor;
 import com.google.protobuf.Descriptors.FieldDescriptor;
+import com.google.protobuf.Descriptors.FileDescriptor;
 import org.jruby.*;
 import org.jruby.anno.JRubyClass;
 import org.jruby.anno.JRubyMethod;
@@ -228,6 +228,9 @@
     }
 
     protected void setDescriptor(ThreadContext context, FieldDescriptor descriptor, RubyDescriptorPool pool) {
+        if (descriptor.isRequired() && descriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3) {
+            throw Utils.createTypeError(context, descriptor.getName() + " is labeled required but required fields are unsupported in proto3");
+        }
         this.descriptor = descriptor;
         this.name = context.runtime.newString(descriptor.getName());
         this.pool = pool;
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyFileBuilderContext.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyFileBuilderContext.java
deleted file mode 100644
index 00ce3f2..0000000
--- a/ruby/src/main/java/com/google/protobuf/jruby/RubyFileBuilderContext.java
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * Protocol Buffers - Google's data interchange format
- * Copyright 2014 Google Inc.  All rights reserved.
- * https://developers.google.com/protocol-buffers/
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- *     * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package com.google.protobuf.jruby;
-
-import com.google.protobuf.DescriptorProtos.DescriptorProto;
-import com.google.protobuf.DescriptorProtos.EnumDescriptorProto;
-import com.google.protobuf.DescriptorProtos.EnumValueDescriptorProtoOrBuilder;
-import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
-import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
-import com.google.protobuf.Descriptors.FieldDescriptor;
-import org.jruby.Ruby;
-import org.jruby.RubyClass;
-import org.jruby.RubyHash;
-import org.jruby.RubyModule;
-import org.jruby.RubyObject;
-import org.jruby.anno.JRubyClass;
-import org.jruby.anno.JRubyMethod;
-import org.jruby.runtime.Block;
-import org.jruby.runtime.ObjectAllocator;
-import org.jruby.runtime.ThreadContext;
-import org.jruby.runtime.builtin.IRubyObject;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.TreeMap;
-
-@JRubyClass(name = "FileBuilderContext")
-public class RubyFileBuilderContext extends RubyObject {
-    public static void createRubyFileBuilderContext(Ruby runtime) {
-        RubyModule internal = runtime.getClassFromPath("Google::Protobuf::Internal");
-        RubyClass cFileBuilderContext = internal.defineClassUnder("FileBuilderContext", runtime.getObject(), new ObjectAllocator() {
-            @Override
-            public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
-                return new RubyFileBuilderContext(runtime, klazz);
-            }
-        });
-        cFileBuilderContext.defineAnnotatedMethods(RubyFileBuilderContext.class);
-
-        cDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Descriptor");
-        cEnumBuilderContext = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Internal::EnumBuilderContext");
-        cEnumDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::EnumDescriptor");
-        cMessageBuilderContext = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Internal::MessageBuilderContext");
-    }
-
-    public RubyFileBuilderContext(Ruby runtime, RubyClass klazz) {
-        super(runtime, klazz);
-    }
-
-    /*
-     * call-seq:
-     *     FileBuilderContext.new(descriptor_pool, name, options = nil) => context
-     *
-     * Create a new file builder context for the given file descriptor and
-     * builder context. This class is intended to serve as a DSL context to be used
-     * with #instance_eval.
-     */
-    @JRubyMethod(required = 2, optional = 1)
-    public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
-        this.descriptorPool = (RubyDescriptorPool) args[0];
-        this.builder = FileDescriptorProto.newBuilder();
-        this.builder.setName(args[1].asJavaString());
-        this.builder.setSyntax("proto3");
-
-        if (args.length > 2) {
-            RubyHash options = (RubyHash) args[2];
-            IRubyObject syntax = options.fastARef(context.runtime.newSymbol("syntax"));
-
-            if (syntax != null) {
-                String syntaxStr = syntax.asJavaString();
-                this.builder.setSyntax(syntaxStr);
-                this.proto3 = syntaxStr.equals("proto3");
-            }
-        }
-
-        return this;
-    }
-
-    /*
-     * call-seq:
-     *     FileBuilderContext.add_enum(name, &block)
-     *
-     * Creates a new, empty enum descriptor with the given name, and invokes the
-     * block in the context of an EnumBuilderContext on that descriptor. The block
-     * can then call EnumBuilderContext#add_value to define the enum values.
-     *
-     * This is the recommended, idiomatic way to build enum definitions.
-     */
-    @JRubyMethod(name = "add_enum")
-    public IRubyObject addEnum(ThreadContext context, IRubyObject name, Block block) {
-        RubyObject ctx = (RubyObject) cEnumBuilderContext.newInstance(context, this, name, Block.NULL_BLOCK);
-        ctx.instance_eval(context, block);
-
-        return context.nil;
-    }
-
-    /*
-     * call-seq:
-     *     FileBuilderContext.add_message(name, &block)
-     *
-     * Creates a new, empty descriptor with the given name, and invokes the block in
-     * the context of a MessageBuilderContext on that descriptor. The block can then
-     * call, e.g., MessageBuilderContext#optional and MessageBuilderContext#repeated
-     * methods to define the message fields.
-     *
-     * This is the recommended, idiomatic way to build message definitions.
-     */
-    @JRubyMethod(name = "add_message")
-    public IRubyObject addMessage(ThreadContext context, IRubyObject name, Block block) {
-        RubyObject ctx = (RubyObject) cMessageBuilderContext.newInstance(context, this, name, Block.NULL_BLOCK);
-        ctx.instance_eval(context, block);
-
-        return context.nil;
-    }
-
-    protected void build(ThreadContext context) {
-        Ruby runtime = context.runtime;
-        List<DescriptorProto.Builder> messageBuilderList = builder.getMessageTypeBuilderList();
-        List<EnumDescriptorProto.Builder> enumBuilderList = builder.getEnumTypeBuilderList();
-
-        // Get the package name from defined names
-        String packageName = getPackageName(messageBuilderList, enumBuilderList);
-
-        if (!packageName.isEmpty()) {
-            builder.setPackage(packageName);
-        }
-
-        // Make an index of the message builders so we can easily nest them
-        TreeMap<String, DescriptorProto.Builder> messageBuilderMap = new TreeMap();
-        for (DescriptorProto.Builder messageBuilder : messageBuilderList) {
-            messageBuilderMap.put(messageBuilder.getName(), messageBuilder);
-        }
-
-        // Make an index of the enum builders so we can easily nest them
-        HashMap<String, EnumDescriptorProto.Builder> enumBuilderMap = new HashMap();
-        for (EnumDescriptorProto.Builder enumBuilder : enumBuilderList) {
-            enumBuilderMap.put("." + enumBuilder.getName(), enumBuilder);
-        }
-
-        // Rename and properly nest messages and create associated ruby objects
-        int packageNameLength = packageName.length();
-        int currentMessageIndex = 0;
-        int currentEnumIndex = 0;
-
-        // Need to get a static list because we are potentially deleting some of them from the collection
-        DescriptorProto.Builder[] messageBuilders = new DescriptorProto.Builder[messageBuilderList.size()];
-        messageBuilderList.toArray(messageBuilders);
-        EnumDescriptorProto.Builder[] enumBuilders = new EnumDescriptorProto.Builder[enumBuilderList.size()];
-        enumBuilderList.toArray(enumBuilders);
-
-        for (EnumDescriptorProto.Builder enumBuilder : enumBuilders) {
-            String name = enumBuilder.getName();
-            int lastDot = name.lastIndexOf('.');
-
-            if (lastDot > packageNameLength) {
-                String parentName = name.substring(0, lastDot);
-                String shortName = name.substring(lastDot + 1);
-
-                enumBuilder.setName(shortName);
-                messageBuilderMap.get(parentName).addEnumType(enumBuilder);
-
-                builder.removeEnumType(currentEnumIndex);
-
-            } else {
-                if (packageNameLength > 0) {
-                    // Remove the package name
-                    String shortName = name.substring(packageNameLength + 1);
-                    enumBuilder.setName(shortName);
-                }
-
-                currentEnumIndex++;
-            }
-
-            // Ensure we have a default value if using proto3 syntax
-            if (proto3) {
-                boolean foundDefault = false;
-                for (EnumValueDescriptorProtoOrBuilder enumValue : enumBuilder.getValueOrBuilderList()) {
-                    if (enumValue.getNumber() == 0) {
-                        foundDefault = true;
-                        break;
-                    }
-                }
-
-                if (!foundDefault) {
-                    throw Utils.createTypeError(context, "Enum definition " + enumBuilder.getName() + " does not contain a value for '0'");
-                }
-            }
-        }
-
-        // Wipe out top level message builders so we can insert only the ones that should be there
-        builder.clearMessageType();
-
-        /*
-         * This block is done in this order because calling
-         * `addNestedType` and `addMessageType` makes a copy of the builder
-         * so the objects that our maps point to are no longer the objects
-         * that are being used to build the descriptions.
-         */
-        for (HashMap.Entry<String, DescriptorProto.Builder> entry : messageBuilderMap.descendingMap().entrySet()) {
-            DescriptorProto.Builder messageBuilder = entry.getValue();
-
-            // Rewrite any enum defaults needed
-            for(FieldDescriptorProto.Builder field : messageBuilder.getFieldBuilderList()) {
-                String typeName = field.getTypeName();
-
-                if (typeName == null || !field.hasDefaultValue()) continue;
-
-                EnumDescriptorProto.Builder enumBuilder = enumBuilderMap.get(typeName);
-
-                if (enumBuilder == null) continue;
-
-                int defaultValue = Integer.parseInt(field.getDefaultValue());
-
-                for (EnumValueDescriptorProtoOrBuilder enumValue : enumBuilder.getValueOrBuilderList()) {
-                    if (enumValue.getNumber() == defaultValue) {
-                        field.setDefaultValue(enumValue.getName());
-                        break;
-                    }
-                }
-            }
-
-            // Turn Foo.Bar.Baz into a correctly nested structure with the correct name
-            String name = messageBuilder.getName();
-            int lastDot = name.lastIndexOf('.');
-
-            if (lastDot > packageNameLength) {
-                String parentName = name.substring(0, lastDot);
-                String shortName = name.substring(lastDot + 1);
-                messageBuilder.setName(shortName);
-                messageBuilderMap.get(parentName).addNestedType(messageBuilder);
-
-            } else {
-                if (packageNameLength > 0) {
-                    // Remove the package name
-                    messageBuilder.setName(name.substring(packageNameLength + 1));
-                }
-
-                // Add back in top level message definitions
-                builder.addMessageType(messageBuilder);
-
-                currentMessageIndex++;
-            }
-        }
-
-        descriptorPool.registerFileDescriptor(context, builder);
-    }
-
-    protected EnumDescriptorProto.Builder getNewEnumBuilder() {
-        return builder.addEnumTypeBuilder();
-    }
-
-    protected DescriptorProto.Builder getNewMessageBuilder() {
-        return builder.addMessageTypeBuilder();
-    }
-
-    protected boolean isProto3() {
-        return proto3;
-    }
-
-    private String getPackageName(List<DescriptorProto.Builder> messages, List<EnumDescriptorProto.Builder> enums) {
-        String shortest = null;
-        String longest = null;
-
-        /*
-         * The >= in the longest string comparisons below makes it so we replace
-         * the name in case all the names are the same length. This makes it so
-         * that the shortest and longest aren't the same name to prevent
-         * finding a "package" that isn't correct
-         */
-
-        for (DescriptorProto.Builder message : messages) {
-            String name = message.getName();
-            int nameLength = name.length();
-            if (shortest == null) {
-                shortest = name;
-                longest = name;
-            } else if (nameLength < shortest.length()) {
-                shortest = name;
-            } else if (nameLength >= longest.length()) {
-                longest = name;
-            }
-        }
-
-        for (EnumDescriptorProto.Builder item : enums) {
-            String name = item.getName();
-            int nameLength = name.length();
-            if (shortest == null) {
-                shortest = name;
-                longest = name;
-            } else if (nameLength < shortest.length()) {
-                shortest = name;
-            } else if (nameLength >= longest.length()) {
-                longest = name;
-            }
-        }
-
-        if (shortest == null) {
-            return "";
-        }
-
-        int lastCommonDot = 0;
-        for (int i = 0; i < shortest.length(); i++) {
-            char nextChar = shortest.charAt(i);
-            if (nextChar != longest.charAt(i)) break;
-            if (nextChar == '.') lastCommonDot = i;
-        }
-
-        return shortest.substring(0, lastCommonDot);
-    }
-
-    private static RubyClass cDescriptor;
-    private static RubyClass cEnumBuilderContext;
-    private static RubyClass cEnumDescriptor;
-    private static RubyClass cMessageBuilderContext;
-
-    private FileDescriptorProto.Builder builder;
-    private RubyDescriptorPool descriptorPool;
-    private boolean proto3 = true;
-}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java
index 087f1cb..8140ec5 100644
--- a/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java
@@ -37,7 +37,6 @@
 import org.jruby.*;
 import org.jruby.anno.JRubyClass;
 import org.jruby.anno.JRubyMethod;
-import org.jruby.internal.runtime.methods.DynamicMethod;
 import org.jruby.runtime.Block;
 import org.jruby.runtime.Helpers;
 import org.jruby.runtime.ObjectAllocator;
@@ -80,11 +79,12 @@
      * on the same values as field-type symbols in message descriptors) that
      * indicate the type of the map key and value fields.
      *
-     * The supported key types are: :int32, :int64, :uint32, :uint64, :bool,
-     * :string, :bytes.
+     * The supported key types are: :int32, :int64, :uint32, :uint64, :fixed32,
+     * :fixed64, :sfixed32, :sfixed64, :sint32, :sint64, :bool, :string, :bytes.
      *
-     * The supported value types are: :int32, :int64, :uint32, :uint64, :bool,
-     * :string, :bytes, :enum, :message.
+     * The supported value types are: :int32, :int64, :uint32, :uint64, :fixed32,
+     * :fixed64, :sfixed32, :sfixed64, :sint32, :sint64, :bool, :string, :bytes,
+     * :enum, :message.
      *
      * The third argument, value_typeclass, must be present if value_type is :enum
      * or :message. As in RepeatedField#new, this argument must be a message class
@@ -113,8 +113,14 @@
                 break;
             case INT32:
             case INT64:
+            case SINT32:
+            case SINT64:
             case UINT32:
             case UINT64:
+            case FIXED32:
+            case FIXED64:
+            case SFIXED32:
+            case SFIXED64:
             case BOOL:
                 // These are OK.
                 break;
@@ -154,7 +160,7 @@
          * other types for keys, so deal with them specifically first
          */
         if (keyTypeIsString && !(key instanceof RubySymbol || key instanceof RubyString)) {
-            throw context.runtime.newTypeError("Expected string for map key");
+            throw Utils.createTypeError(context, "Expected string for map key");
         }
         key = Utils.checkType(context, keyType, "key", key, (RubyModule) valueTypeClass);
         value = Utils.checkType(context, valueType, "value", value, (RubyModule) valueTypeClass);
@@ -399,7 +405,7 @@
 
     protected RubyMap mergeIntoSelf(final ThreadContext context, IRubyObject hashmap) {
         if (hashmap instanceof RubyHash) {
-            ((RubyHash) hashmap).visitAll(new RubyHash.Visitor() {
+            ((RubyHash) hashmap).visitAll(context, new RubyHash.Visitor() {
                 @Override
                 public void visit(IRubyObject key, IRubyObject val) {
                     if (val instanceof RubyHash && !valueTypeClass.isNil()) {
@@ -407,14 +413,14 @@
                     }
                     indexSet(context, key, val);
                 }
-            });
+            }, null);
         } else if (hashmap instanceof RubyMap) {
             RubyMap other = (RubyMap) hashmap;
             if (!typeCompatible(other)) {
                 throw Utils.createTypeError(context, "Attempt to merge Map with mismatching types");
             }
         } else {
-            throw context.runtime.newTypeError("Unknown type merging into Map");
+            throw Utils.createTypeError(context, "Unknown type merging into Map");
         }
         return this;
     }
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java
index a905c9a..2737182 100644
--- a/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java
@@ -93,16 +93,18 @@
                 throw runtime.newArgumentError("expected Hash arguments.");
             }
             RubyHash hash = args[0].convertToHash();
-            hash.visitAll(new RubyHash.Visitor() {
+            hash.visitAll(context, new RubyHash.Visitor() {
                 @Override
                 public void visit(IRubyObject key, IRubyObject value) {
-                    if (!(key instanceof RubySymbol) && !(key instanceof RubyString))
-                        throw runtime.newTypeError("Expected string or symbols as hash keys in initialization map.");
+                    if (!(key instanceof RubySymbol) && !(key instanceof RubyString)) {
+                        throw Utils.createTypeError(context,
+                            "Expected string or symbols as hash keys in initialization map.");
+                    }
                     final FieldDescriptor fieldDescriptor = findField(context, key, ignoreUnknownFieldsOnInit);
 
                     if (value == null || value.isNil()) return;
 
-                    if (Utils.isMapEntry(fieldDescriptor)) {
+                    if (fieldDescriptor.isMapField()) {
                         if (!(value instanceof RubyHash))
                             throw runtime.newArgumentError("Expected Hash object as initializer value for map field '" +  key.asJavaString() + "' (given " + value.getMetaClass() + ").");
 
@@ -130,7 +132,7 @@
 
                     }
                 }
-            });
+            }, null);
         }
         return this;
     }
@@ -181,6 +183,9 @@
         sb.append(cname).append(colon);
 
         for (FieldDescriptor fd : descriptor.getFields()) {
+            if (fd.hasPresence() && !fields.containsKey(fd)) {
+                continue;
+            }
             if (addComma) {
                 sb.append(comma);
             } else {
@@ -347,7 +352,7 @@
 
                 if (fieldDescriptor != null &&
                         (!proto3 || fieldDescriptor.getContainingOneof() == null) && // This seems like a bug but its needed to pass the tests...
-                        fieldHasPresence(fieldDescriptor)) {
+                    fieldDescriptor.hasPresence()) {
                     return fields.containsKey(fieldDescriptor) ? runtime.getTrue() : runtime.getFalse();
                 }
 
@@ -428,12 +433,11 @@
     @JRubyMethod
     public IRubyObject dup(ThreadContext context) {
         RubyMessage dup = (RubyMessage) metaClass.newInstance(context, Block.NULL_BLOCK);
-        IRubyObject value;
         for (FieldDescriptor fieldDescriptor : this.descriptor.getFields()) {
             if (fieldDescriptor.isRepeated()) {
                 dup.fields.put(fieldDescriptor, this.getRepeatedField(context, fieldDescriptor));
             } else if (fields.containsKey(fieldDescriptor)) {
-                dup.fields.put(fieldDescriptor, fields.get(fieldDescriptor));
+                dup.setFieldInternal(context, fieldDescriptor, fields.get(fieldDescriptor));
             } else if (this.builder.hasField(fieldDescriptor)) {
                 dup.fields.put(fieldDescriptor, wrapField(context, fieldDescriptor, this.builder.getField(fieldDescriptor)));
             }
@@ -474,7 +478,7 @@
      *     MessageClass.decode(data) => message
      *
      * Decodes the given data (as a string containing bytes in protocol buffers wire
-     * format) under the interpretration given by this message class's definition
+     * format) under the interpretation given by this message class's definition
      * and returns a message object with the corresponding field values.
      */
     @JRubyMethod(meta = true)
@@ -532,11 +536,14 @@
                 printer = printer.preservingProtoFieldNames();
             }
         }
+        printer = printer.usingTypeRegistry(JsonFormat.TypeRegistry.newBuilder().add(message.descriptor).build());
 
         try {
             result = printer.print(message.build(context));
-        } catch(InvalidProtocolBufferException e) {
+        } catch (InvalidProtocolBufferException e) {
             throw runtime.newRuntimeError(e.getMessage());
+        } catch (IllegalArgumentException e) {
+            throw createParseError(context, e.getMessage());
         }
 
         return runtime.newString(result);
@@ -547,7 +554,7 @@
      *     MessageClass.decode_json(data, options = {}) => message
      *
      * Decodes the given data (as a string containing bytes in protocol buffers wire
-     * format) under the interpretration given by this message class's definition
+     * format) under the interpretation given by this message class's definition
      * and returns a message object with the corresponding field values.
      *
      *  @param options [Hash] options for the decoder
@@ -577,6 +584,7 @@
         }
 
         RubyMessage ret = (RubyMessage) ((RubyClass) recv).newInstance(context, Block.NULL_BLOCK);
+        parser = parser.usingTypeRegistry(JsonFormat.TypeRegistry.newBuilder().add(ret.descriptor).build());
 
         try {
             parser.merge(data.asJavaString(), ret.builder);
@@ -633,6 +641,8 @@
         if (depth > SINK_MAXIMUM_NESTING) {
             throw context.runtime.newRuntimeError("Maximum recursion depth exceeded during encoding.");
         }
+
+        // Handle the typical case where the fields.keySet contain the fieldDescriptors
         for (FieldDescriptor fieldDescriptor : fields.keySet()) {
             IRubyObject value = fields.get(fieldDescriptor);
 
@@ -648,15 +658,53 @@
 
                 builder.clearField(fieldDescriptor);
                 for (int i = 0; i < repeatedField.size(); i++) {
-                    Object item = convert(context, fieldDescriptor, repeatedField.get(i), depth);
+                    Object item = convert(context, fieldDescriptor, repeatedField.get(i), depth,
+                        /*isDefaultValueForBytes*/ false);
                     builder.addRepeatedField(fieldDescriptor, item);
                 }
 
-            } else {
-                builder.setField(fieldDescriptor, convert(context, fieldDescriptor, value, depth));
+            } else if (!value.isNil()) {
+                /**
+                 * Detect the special case where default_value strings are provided for byte fields.
+                 * If so, disable normal string encoding behavior within convert.
+                 * For a more detailed explanation of other possible workarounds, see the comments
+                 * above {@code com.google.protobuf.Internal#stringDefaultValue()
+                 * stringDefaultValue}.
+                 */
+                boolean isDefaultStringForBytes = false;
+                FieldDescriptor enumFieldDescriptorForType =
+                    this.builder.getDescriptorForType().findFieldByName("type");
+                EnumValueDescriptor type = enumFieldDescriptorForType == null ?
+                    null : ((EnumValueDescriptor)this.builder.getField(enumFieldDescriptorForType));
+                if (type != null && type.getName().equals("TYPE_BYTES") &&
+                    fieldDescriptor.getFullName().equals("google.protobuf.FieldDescriptorProto.default_value")) {
+                    isDefaultStringForBytes = true;
+                }
+                builder.setField(fieldDescriptor, convert(context, fieldDescriptor, value, depth, isDefaultStringForBytes));
             }
         }
 
+        // Handle cases where {@code fields} doesn't contain the value until after getFieldInternal
+        // is called - typical of a deserialized message. Skip non-maps and descriptors that already
+        // have an entry in {@code fields}.
+        for (FieldDescriptor fieldDescriptor : descriptor.getFields()) {
+            if (!fieldDescriptor.isMapField()) {
+                continue;
+            }
+            IRubyObject value = fields.get(fieldDescriptor);
+            if (value!=null) {
+                continue;
+            }
+            value = getFieldInternal(context, fieldDescriptor);
+            if (value instanceof RubyMap) {
+                builder.clearField(fieldDescriptor);
+                RubyDescriptor mapDescriptor = (RubyDescriptor) getDescriptorForField(context,
+                    fieldDescriptor);
+                for (DynamicMessage kv : ((RubyMap) value).build(context, mapDescriptor, depth)) {
+                    builder.addRepeatedField(fieldDescriptor, kv);
+                }
+            }
+        }
         return builder.build();
     }
 
@@ -667,7 +715,7 @@
             if (fdef.isRepeated()) {
                 copy.fields.put(fdef, this.getRepeatedField(context, fdef).deepCopy(context));
             } else if (fields.containsKey(fdef)) {
-                copy.fields.put(fdef, fields.get(fdef));
+                copy.setFieldInternal(context, fdef, fields.get(fdef));
             } else if (builder.hasField(fdef)) {
                 copy.fields.put(fdef, wrapField(context, fdef, builder.getField(fdef)));
             }
@@ -691,7 +739,9 @@
 
     protected IRubyObject hasField(ThreadContext context, FieldDescriptor fieldDescriptor) {
         validateMessageType(context, fieldDescriptor, "has?");
-        if (!fieldHasPresence(fieldDescriptor)) throw context.runtime.newArgumentError("does not track presence");
+        if (!fieldDescriptor.hasPresence()) {
+            throw context.runtime.newArgumentError("does not track presence");
+        }
         return fields.containsKey(fieldDescriptor) ? context.runtime.getTrue() : context.runtime.getFalse();
     }
 
@@ -762,19 +812,24 @@
     // convert a ruby object to protobuf type, skip type check since it is checked on the way in
     private Object convert(ThreadContext context,
                            FieldDescriptor fieldDescriptor,
-                           IRubyObject value, int depth) {
-        Ruby runtime = context.runtime;
+                           IRubyObject value, int depth, boolean isDefaultStringForBytes) {
         Object val = null;
         switch (fieldDescriptor.getType()) {
             case INT32:
+            case SFIXED32:
+            case SINT32:
                 val = RubyNumeric.num2int(value);
                 break;
             case INT64:
+            case SFIXED64:
+            case SINT64:
                 val = RubyNumeric.num2long(value);
                 break;
+            case FIXED32:
             case UINT32:
                 val = Utils.num2uint(value);
                 break;
+            case FIXED64:
             case UINT64:
                 val = Utils.num2ulong(context.runtime, value);
                 break;
@@ -791,7 +846,11 @@
                 val = ByteString.copyFrom(((RubyString) value).getBytes());
                 break;
             case STRING:
-                val = ((RubyString) value).asJavaString();
+                if (isDefaultStringForBytes) {
+                    val = ((RubyString) value).getByteList().toString();
+                } else {
+                    val = value.asJavaString();
+                }
                 break;
             case MESSAGE:
                 val = ((RubyMessage) value).build(context, depth + 1);
@@ -819,6 +878,10 @@
     }
 
     private IRubyObject wrapField(ThreadContext context, FieldDescriptor fieldDescriptor, Object value) {
+        return wrapField(context, fieldDescriptor, value, false);
+    }
+
+    private IRubyObject wrapField(ThreadContext context, FieldDescriptor fieldDescriptor, Object value, boolean encodeBytes) {
         if (value == null) {
             return context.runtime.getNil();
         }
@@ -827,6 +890,12 @@
         switch (fieldDescriptor.getType()) {
             case INT32:
             case INT64:
+            case FIXED32:
+            case SINT32:
+            case FIXED64:
+            case SINT64:
+            case SFIXED64:
+            case SFIXED32:
             case UINT32:
             case UINT64:
             case FLOAT:
@@ -834,7 +903,7 @@
             case BOOL:
             case BYTES:
             case STRING:
-                return Utils.wrapPrimaryValue(context, fieldDescriptor.getType(), value);
+                return Utils.wrapPrimaryValue(context, fieldDescriptor.getType(), value, encodeBytes);
             case MESSAGE:
                 RubyClass typeClass = (RubyClass) ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context);
                 RubyMessage msg = (RubyMessage) typeClass.newInstance(context, Block.NULL_BLOCK);
@@ -872,22 +941,44 @@
         return getFieldInternal(context, fieldDescriptor, true);
     }
 
-    private IRubyObject getFieldInternal(ThreadContext context, FieldDescriptor fieldDescriptor, boolean returnDefaults) {
+    private IRubyObject getFieldInternal(ThreadContext context, FieldDescriptor fieldDescriptor,
+        boolean returnDefaults) {
         OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof();
         if (oneofDescriptor != null) {
             if (oneofCases.get(oneofDescriptor) == fieldDescriptor) {
-                return fields.get(fieldDescriptor);
+                IRubyObject value = fields.get(fieldDescriptor);
+                if (value == null) {
+                    FieldDescriptor oneofCase = builder.getOneofFieldDescriptor(oneofDescriptor);
+                    if (oneofCase != null) {
+                        Object builderValue = builder.getField(oneofCase);
+                        if (builderValue != null) {
+                            boolean encodeBytes = oneofCase.hasDefaultValue() && builderValue.equals(oneofCase.getDefaultValue());
+                            value = wrapField(context, oneofCase, builderValue, encodeBytes);
+                        }
+                    }
+                    if (value == null) {
+                        return context.nil;
+                    } else {
+                        return value;
+                    }
+                } else {
+                    return value;
+                }
             } else {
                 FieldDescriptor oneofCase = builder.getOneofFieldDescriptor(oneofDescriptor);
                 if (oneofCase != fieldDescriptor) {
-                  if (fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE || !returnDefaults) {
-                    return context.nil;
-                  } else {
-                    return wrapField(context, fieldDescriptor, fieldDescriptor.getDefaultValue());
-                  }
+                    if (fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE
+                        || !returnDefaults) {
+                        return context.nil;
+                    } else {
+                        return wrapField(context, fieldDescriptor,
+                            fieldDescriptor.getDefaultValue(), true);
+                    }
                 }
                 if (returnDefaults || builder.hasField(fieldDescriptor)) {
-                    IRubyObject value = wrapField(context, oneofCase, builder.getField(oneofCase));
+                    Object rawValue = builder.getField(oneofCase);
+                    boolean encodeBytes = oneofCase.hasDefaultValue() && rawValue.equals(oneofCase.getDefaultValue());
+                    IRubyObject value = wrapField(context, oneofCase, rawValue, encodeBytes);
                     fields.put(fieldDescriptor, value);
                     return value;
                 } else {
@@ -896,7 +987,7 @@
             }
         }
 
-        if (Utils.isMapEntry(fieldDescriptor)) {
+        if (fieldDescriptor.isMapField()) {
             RubyMap map = (RubyMap) fields.get(fieldDescriptor);
             if (map == null) {
                 map = newMapForField(context, fieldDescriptor);
@@ -925,7 +1016,9 @@
             if (fields.containsKey(fieldDescriptor)) {
                 return fields.get(fieldDescriptor);
             } else if (returnDefaults || builder.hasField(fieldDescriptor)) {
-                IRubyObject value = wrapField(context, fieldDescriptor, builder.getField(fieldDescriptor));
+                Object rawValue = builder.getField(fieldDescriptor);
+                boolean encodeBytes = fieldDescriptor.hasDefaultValue() && rawValue.equals(fieldDescriptor.getDefaultValue());
+                IRubyObject value = wrapField(context, fieldDescriptor, rawValue, encodeBytes);
                 if (builder.hasField(fieldDescriptor)) {
                     fields.put(fieldDescriptor, value);
                 }
@@ -938,7 +1031,7 @@
     private IRubyObject setFieldInternal(ThreadContext context, FieldDescriptor fieldDescriptor, IRubyObject value) {
         testFrozen("can't modify frozen " + getMetaClass());
 
-        if (Utils.isMapEntry(fieldDescriptor)) {
+        if (fieldDescriptor.isMapField()) {
             if (!(value instanceof RubyMap)) {
                 throw Utils.createTypeError(context, "Expected Map instance");
             }
@@ -980,7 +1073,9 @@
                 // Keep track of what Oneofs are set
                 if (value.isNil()) {
                     oneofCases.remove(oneofDescriptor);
-                    addValue = false;
+                    if (!oneofDescriptor.isSynthetic()) {
+                        addValue = false;
+                    }
                 } else {
                     oneofCases.put(oneofDescriptor, fieldDescriptor);
                 }
@@ -1019,20 +1114,12 @@
         return context.runtime.newSymbol("UNKNOWN");
     }
 
-    private boolean fieldHasPresence(FieldDescriptor fieldDescriptor) {
-      return !fieldDescriptor.isRepeated() &&
-              (fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE ||
-                fieldDescriptor.getContainingOneof() != null ||
-                !proto3);
-    }
-
     private RubyRepeatedField rubyToRepeatedField(ThreadContext context,
                                                   FieldDescriptor fieldDescriptor, IRubyObject value) {
         RubyArray arr = value.convertToArray();
         RubyRepeatedField repeatedField = repeatedFieldForFieldDescriptor(context, fieldDescriptor);
         IRubyObject[] values = new IRubyObject[arr.size()];
         FieldDescriptor.Type fieldType = fieldDescriptor.getType();
-        String fieldName = fieldDescriptor.getName();
 
         RubyModule typeClass = null;
         if (fieldType == FieldDescriptor.Type.MESSAGE) {
@@ -1045,8 +1132,11 @@
 
         for (int i = 0; i < arr.size(); i++) {
             IRubyObject item = arr.eltInternal(i);
+            if (item.isNil()) {
+                throw Utils.createTypeError(context, "nil message not allowed here.");
+            }
             if (item instanceof RubyHash && typeClass != null) {
-                values[i] = (IRubyObject) ((RubyClass) typeClass).newInstance(context, item, Block.NULL_BLOCK);
+                values[i] = ((RubyClass) typeClass).newInstance(context, item, Block.NULL_BLOCK);
             } else {
                 if (fieldType == FieldDescriptor.Type.ENUM) {
                     item = enumToSymbol(context, fieldDescriptor.getEnumType(), item);
@@ -1086,13 +1176,6 @@
         }
     }
 
-    private FieldDescriptor getOneofCase(OneofDescriptor oneof) {
-        if (oneofCases.containsKey(oneof)) {
-            return oneofCases.get(oneof);
-        }
-        return builder.getOneofFieldDescriptor(oneof);
-    }
-
     private boolean isWrappable(FieldDescriptor fieldDescriptor) {
       if (fieldDescriptor.getType() != FieldDescriptor.Type.MESSAGE) return false;
 
@@ -1118,7 +1201,7 @@
 
     private void validateMessageType(ThreadContext context, FieldDescriptor fieldDescriptor, String methodName) {
         if (descriptor != fieldDescriptor.getContainingType()) {
-            throw context.runtime.newTypeError(methodName + " method called on wrong message type");
+            throw Utils.createTypeError(context, methodName + " method called on wrong message type");
         }
     }
 
@@ -1139,6 +1222,4 @@
     private RubyClass cMap;
     private boolean ignoreUnknownFieldsOnInit = false;
     private boolean proto3;
-
-
 }
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyMessageBuilderContext.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyMessageBuilderContext.java
deleted file mode 100644
index 211236c..0000000
--- a/ruby/src/main/java/com/google/protobuf/jruby/RubyMessageBuilderContext.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Protocol Buffers - Google's data interchange format
- * Copyright 2014 Google Inc.  All rights reserved.
- * https://developers.google.com/protocol-buffers/
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- *     * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package com.google.protobuf.jruby;
-
-import com.google.protobuf.DescriptorProtos.DescriptorProto;
-import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
-import com.google.protobuf.DescriptorProtos.OneofDescriptorProto;
-import org.jruby.*;
-import org.jruby.anno.JRubyClass;
-import org.jruby.anno.JRubyMethod;
-import org.jruby.runtime.Binding;
-import org.jruby.runtime.Block;
-import org.jruby.runtime.ObjectAllocator;
-import org.jruby.runtime.ThreadContext;
-import org.jruby.runtime.builtin.IRubyObject;
-
-@JRubyClass(name = "MessageBuilderContext")
-public class RubyMessageBuilderContext extends RubyObject {
-    public static void createRubyMessageBuilderContext(Ruby runtime) {
-        RubyModule internal = runtime.getClassFromPath("Google::Protobuf::Internal");
-        RubyClass cMessageBuilderContext = internal.defineClassUnder("MessageBuilderContext", runtime.getObject(), new ObjectAllocator() {
-            @Override
-            public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
-                return new RubyMessageBuilderContext(runtime, klazz);
-            }
-        });
-        cMessageBuilderContext.defineAnnotatedMethods(RubyMessageBuilderContext.class);
-
-        cFieldDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::FieldDescriptor");
-        cOneofBuilderContext = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Internal::OneofBuilderContext");
-    }
-
-    public RubyMessageBuilderContext(Ruby runtime, RubyClass klazz) {
-        super(runtime, klazz);
-    }
-
-    @JRubyMethod
-    public IRubyObject initialize(ThreadContext context, IRubyObject fileBuilderContext, IRubyObject name) {
-        this.fileBuilderContext = (RubyFileBuilderContext) fileBuilderContext;
-        this.builder = this.fileBuilderContext.getNewMessageBuilder();
-        this.builder.setName(name.asJavaString());
-
-        return this;
-    }
-
-    /*
-     * call-seq:
-     *     MessageBuilderContext.optional(name, type, number, type_class = nil,
-     *                                    options = nil)
-     *
-     * Defines a new optional field on this message type with the given type, tag
-     * number, and type class (for message and enum fields). The type must be a Ruby
-     * symbol (as accepted by FieldDescriptor#type=) and the type_class must be a
-     * string, if present (as accepted by FieldDescriptor#submsg_name=).
-     */
-    @JRubyMethod(required = 3, optional = 2)
-    public IRubyObject optional(ThreadContext context, IRubyObject[] args) {
-        addField(context, OPTIONAL, args, false);
-        return context.nil;
-    }
-
-    @JRubyMethod(required = 3, optional = 2)
-    public IRubyObject proto3_optional(ThreadContext context, IRubyObject[] args) {
-      addField(context, OPTIONAL, args, true);
-      return context.nil;
-    }
-
-    /*
-     * call-seq:
-     *     MessageBuilderContext.required(name, type, number, type_class = nil,
-     *                                    options = nil)
-     *
-     * Defines a new required field on this message type with the given type, tag
-     * number, and type class (for message and enum fields). The type must be a Ruby
-     * symbol (as accepted by FieldDescriptor#type=) and the type_class must be a
-     * string, if present (as accepted by FieldDescriptor#submsg_name=).
-     *
-     * Proto3 does not have required fields, but this method exists for
-     * completeness. Any attempt to add a message type with required fields to a
-     * pool will currently result in an error.
-     */
-    @JRubyMethod(required = 3, optional = 2)
-    public IRubyObject required(ThreadContext context, IRubyObject[] args) {
-        if (fileBuilderContext.isProto3()) throw Utils.createTypeError(context, "Required fields are unsupported in proto3");
-        addField(context, "required", args, false);
-        return context.nil;
-    }
-
-    /*
-     * call-seq:
-     *     MessageBuilderContext.repeated(name, type, number, type_class = nil)
-     *
-     * Defines a new repeated field on this message type with the given type, tag
-     * number, and type class (for message and enum fields). The type must be a Ruby
-     * symbol (as accepted by FieldDescriptor#type=) and the type_class must be a
-     * string, if present (as accepted by FieldDescriptor#submsg_name=).
-     */
-    @JRubyMethod(required = 3, optional = 1)
-    public IRubyObject repeated(ThreadContext context, IRubyObject[] args) {
-        addField(context, "repeated", args, false);
-        return context.nil;
-    }
-
-    /*
-     * call-seq:
-     *     MessageBuilderContext.map(name, key_type, value_type, number,
-     *                               value_type_class = nil)
-     *
-     * Defines a new map field on this message type with the given key and value
-     * types, tag number, and type class (for message and enum value types). The key
-     * type must be :int32/:uint32/:int64/:uint64, :bool, or :string. The value type
-     * type must be a Ruby symbol (as accepted by FieldDescriptor#type=) and the
-     * type_class must be a string, if present (as accepted by
-     * FieldDescriptor#submsg_name=).
-     */
-    @JRubyMethod(required = 4, optional = 1)
-    public IRubyObject map(ThreadContext context, IRubyObject[] args) {
-        Ruby runtime = context.runtime;
-        if (!fileBuilderContext.isProto3()) throw runtime.newArgumentError("Cannot add a native map field using proto2 syntax.");
-
-        RubySymbol messageSym = runtime.newSymbol("message");
-
-        IRubyObject name = args[0];
-        IRubyObject keyType = args[1];
-        IRubyObject valueType = args[2];
-        IRubyObject number = args[3];
-        IRubyObject typeClass = args.length > 4 ? args[4] : context.nil;
-
-        // Validate the key type. We can't accept enums, messages, or floats/doubles
-        // as map keys. (We exclude these explicitly, and the field-descriptor setter
-        // below then ensures that the type is one of the remaining valid options.)
-        if (keyType.equals(runtime.newSymbol("float")) ||
-                keyType.equals(runtime.newSymbol("double")) ||
-                keyType.equals(runtime.newSymbol("enum")) ||
-                keyType.equals(messageSym))
-            throw runtime.newArgumentError("Cannot add a map field with a float, double, enum, or message type.");
-
-        DescriptorProto.Builder mapEntryBuilder = fileBuilderContext.getNewMessageBuilder();
-        mapEntryBuilder.setName(builder.getName() + "_MapEntry_" + name.asJavaString());
-        mapEntryBuilder.getOptionsBuilder().setMapEntry(true);
-
-        mapEntryBuilder.addField(
-            Utils.createFieldBuilder(
-                context,
-                OPTIONAL,
-                new IRubyObject[] {
-                    runtime.newString("key"),
-                    keyType,
-                    runtime.newFixnum(1)
-                }
-            )
-        );
-
-        mapEntryBuilder.addField(
-            Utils.createFieldBuilder(
-                context,
-                OPTIONAL,
-                new IRubyObject[] {
-                    runtime.newString("value"),
-                    valueType,
-                    runtime.newFixnum(2),
-                    typeClass
-                }
-            )
-        );
-
-        IRubyObject[] addFieldArgs = {
-            name, messageSym, number, runtime.newString(mapEntryBuilder.getName())
-        };
-
-        repeated(context, addFieldArgs);
-
-        return context.nil;
-    }
-
-    /*
-     * call-seq:
-     *     MessageBuilderContext.oneof(name, &block) => nil
-     *
-     * Creates a new OneofDescriptor with the given name, creates a
-     * OneofBuilderContext attached to that OneofDescriptor, evaluates the given
-     * block in the context of that OneofBuilderContext with #instance_eval, and
-     * then adds the oneof to the message.
-     *
-     * This is the recommended, idiomatic way to build oneof definitions.
-     */
-    @JRubyMethod
-    public IRubyObject oneof(ThreadContext context, IRubyObject name, Block block) {
-        RubyOneofBuilderContext ctx = (RubyOneofBuilderContext)
-                cOneofBuilderContext.newInstance(
-                        context,
-                        context.runtime.newFixnum(builder.getOneofDeclCount()),
-                        this,
-                        Block.NULL_BLOCK
-                );
-
-        builder.addOneofDeclBuilder().setName(name.asJavaString());
-        ctx.instance_eval(context, block);
-
-        return context.nil;
-    }
-
-    protected void addFieldBuilder(FieldDescriptorProto.Builder fieldBuilder) {
-        builder.addField(fieldBuilder);
-    }
-
-    private FieldDescriptorProto.Builder addField(ThreadContext context, String label, IRubyObject[] args, boolean proto3Optional) {
-        FieldDescriptorProto.Builder fieldBuilder =
-                Utils.createFieldBuilder(context, label, args);
-
-        fieldBuilder.setProto3Optional(proto3Optional);
-        builder.addField(fieldBuilder);
-
-        return fieldBuilder;
-    }
-
-    private static RubyClass cFieldDescriptor;
-    private static RubyClass cOneofBuilderContext;
-
-    private static final String OPTIONAL = "optional";
-
-    private DescriptorProto.Builder builder;
-    private RubyClass cDescriptor;
-    private RubyFileBuilderContext fileBuilderContext;
-}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofBuilderContext.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofBuilderContext.java
deleted file mode 100644
index 1ce500e..0000000
--- a/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofBuilderContext.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Protocol Buffers - Google's data interchange format
- * Copyright 2014 Google Inc.  All rights reserved.
- * https://developers.google.com/protocol-buffers/
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- *     * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package com.google.protobuf.jruby;
-
-import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
-import org.jruby.Ruby;
-import org.jruby.RubyClass;
-import org.jruby.RubyHash;
-import org.jruby.RubyModule;
-import org.jruby.RubyNumeric;
-import org.jruby.RubyObject;
-import org.jruby.anno.JRubyClass;
-import org.jruby.anno.JRubyMethod;
-import org.jruby.runtime.ObjectAllocator;
-import org.jruby.runtime.ThreadContext;
-import org.jruby.runtime.builtin.IRubyObject;
-
-@JRubyClass(name = "OneofBuilderContext")
-public class RubyOneofBuilderContext extends RubyObject {
-    public static void createRubyOneofBuilderContext(Ruby runtime) {
-        RubyModule internal = runtime.getClassFromPath("Google::Protobuf::Internal");
-        RubyClass cRubyOneofBuidlerContext = internal.defineClassUnder("OneofBuilderContext", runtime.getObject(), new ObjectAllocator() {
-            @Override
-            public IRubyObject allocate(Ruby ruby, RubyClass rubyClass) {
-                return new RubyOneofBuilderContext(ruby, rubyClass);
-            }
-        });
-        cRubyOneofBuidlerContext.defineAnnotatedMethods(RubyOneofBuilderContext.class);
-    }
-
-    public RubyOneofBuilderContext(Ruby ruby, RubyClass rubyClass) {
-        super(ruby, rubyClass);
-    }
-
-    /*
-     * call-seq:
-     *     OneofBuilderContext.new(oneof_index, message_builder) => context
-     *
-     * Create a new oneof builder context around the given oneof descriptor and
-     * builder context. This class is intended to serve as a DSL context to be used
-     * with #instance_eval.
-     */
-    @JRubyMethod
-    public IRubyObject initialize(ThreadContext context, IRubyObject index, IRubyObject messageBuilder) {
-        this.builder = (RubyMessageBuilderContext) messageBuilder;
-        this.index = RubyNumeric.num2int(index);
-
-        return this;
-    }
-
-    /*
-     * call-seq:
-     *     OneofBuilderContext.optional(name, type, number, type_class = nil,
-     *                                  options = nil)
-     *
-     * Defines a new optional field in this oneof with the given type, tag number,
-     * and type class (for message and enum fields). The type must be a Ruby symbol
-     * (as accepted by FieldDescriptor#type=) and the type_class must be a string,
-     * if present (as accepted by FieldDescriptor#submsg_name=).
-     */
-    @JRubyMethod(required = 3, optional = 2)
-    public IRubyObject optional(ThreadContext context, IRubyObject[] args) {
-        FieldDescriptorProto.Builder fieldBuilder =
-                Utils.createFieldBuilder(context, "optional", args);
-        fieldBuilder.setOneofIndex(index);
-        builder.addFieldBuilder(fieldBuilder);
-
-        return context.nil;
-    }
-
-    private RubyMessageBuilderContext builder;
-    private int index;
-}
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/Utils.java b/ruby/src/main/java/com/google/protobuf/jruby/Utils.java
index 17c1c8d..cd27589 100644
--- a/ruby/src/main/java/com/google/protobuf/jruby/Utils.java
+++ b/ruby/src/main/java/com/google/protobuf/jruby/Utils.java
@@ -35,7 +35,6 @@
 import com.google.protobuf.ByteString;
 import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
 import com.google.protobuf.Descriptors.FieldDescriptor;
-import org.jcodings.Encoding;
 import org.jcodings.specific.ASCIIEncoding;
 import org.jruby.*;
 import org.jruby.exceptions.RaiseException;
@@ -70,6 +69,12 @@
         Ruby runtime = context.runtime;
 
         switch(fieldType) {
+            case SFIXED32:
+            case SFIXED64:
+            case FIXED64:
+            case SINT64:
+            case SINT32:
+            case FIXED32:
             case INT32:
             case INT64:
             case UINT32:
@@ -83,7 +88,8 @@
                         throw runtime.newRangeError("Non-integral floating point value assigned to integer field '" + fieldName + "' (given " + value.getMetaClass() + ").");
                     }
                 }
-                if (fieldType == FieldDescriptor.Type.UINT32 || fieldType == FieldDescriptor.Type.UINT64) {
+                if (fieldType == FieldDescriptor.Type.UINT32 || fieldType == FieldDescriptor.Type.UINT64 ||
+                    fieldType == FieldDescriptor.Type.FIXED32  || fieldType == FieldDescriptor.Type.FIXED64) {
                     if (((RubyNumeric) value).isNegative()) {
                         throw runtime.newRangeError("Assigning negative value to unsigned integer field '" + fieldName + "' (given " + value.getMetaClass() + ").");
                     }
@@ -94,9 +100,11 @@
                         RubyNumeric.num2int(value);
                         break;
                     case UINT32:
+                    case FIXED32:
                         num2uint(value);
                         break;
                     case UINT64:
+                    case FIXED64:
                         num2ulong(context.runtime, value);
                         break;
                     default:
@@ -183,14 +191,24 @@
     }
 
     public static IRubyObject wrapPrimaryValue(ThreadContext context, FieldDescriptor.Type fieldType, Object value) {
+        return wrapPrimaryValue(context, fieldType, value, false);
+    }
+
+    public static IRubyObject wrapPrimaryValue(ThreadContext context, FieldDescriptor.Type fieldType, Object value, boolean encodeBytes) {
         Ruby runtime = context.runtime;
         switch (fieldType) {
             case INT32:
+            case SFIXED32:
+            case SINT32:
                 return runtime.newFixnum((Integer) value);
+            case SFIXED64:
+            case SINT64:
             case INT64:
                 return runtime.newFixnum((Long) value);
+            case FIXED32:
             case UINT32:
                 return runtime.newFixnum(((Integer) value) & (-1l >>> 32));
+            case FIXED64:
             case UINT64:
                 long ret = (Long) value;
                 return ret >= 0 ? runtime.newFixnum(ret) :
@@ -202,7 +220,9 @@
             case BOOL:
                 return (Boolean) value ? runtime.getTrue() : runtime.getFalse();
             case BYTES: {
-                IRubyObject wrapped = RubyString.newString(runtime, ((ByteString) value).toStringUtf8(), ASCIIEncoding.INSTANCE);
+                IRubyObject wrapped = encodeBytes ?
+                    RubyString.newString(runtime, ((ByteString) value).toStringUtf8(), ASCIIEncoding.INSTANCE) :
+                    RubyString.newString(runtime, ((ByteString) value).toByteArray());
                 wrapped.setFrozen(true);
                 return wrapped;
             }
@@ -260,58 +280,6 @@
                 fieldDescriptor.getMessageType().getOptions().getMapEntry();
     }
 
-    /*
-     * call-seq:
-     *     Utils.createFieldBuilder(context, label, name, type, number, typeClass = nil, options = nil)
-     *
-     * Most places calling this are already dealing with an optional number of
-     * arguments so dealing with them here. This helper is a standard way to
-     * create a FieldDescriptor builder that handles some of the options that
-     * are used in different places.
-     */
-    public static FieldDescriptorProto.Builder createFieldBuilder(ThreadContext context,
-            String label, IRubyObject[] args) {
-
-        Ruby runtime = context.runtime;
-        IRubyObject options = context.nil;
-        IRubyObject typeClass = context.nil;
-
-        if (args.length > 4) {
-            options = args[4];
-            typeClass = args[3];
-        } else if (args.length > 3) {
-            if (args[3] instanceof RubyHash) {
-                options = args[3];
-            } else {
-                typeClass = args[3];
-            }
-        }
-
-        FieldDescriptorProto.Builder builder = FieldDescriptorProto.newBuilder();
-
-        builder.setLabel(FieldDescriptorProto.Label.valueOf("LABEL_" + label.toUpperCase()))
-            .setName(args[0].asJavaString())
-            .setNumber(RubyNumeric.num2int(args[2]))
-            .setType(FieldDescriptorProto.Type.valueOf("TYPE_" + args[1].asJavaString().toUpperCase()));
-
-        if (!typeClass.isNil()) {
-            if (!(typeClass instanceof RubyString)) {
-                throw runtime.newArgumentError("expected string for type class");
-            }
-            builder.setTypeName("." + typeClass.asJavaString());
-        }
-
-        if (options instanceof RubyHash) {
-            IRubyObject defaultValue = ((RubyHash) options).fastARef(runtime.newSymbol("default"));
-            if (defaultValue != null) {
-                builder.setDefaultValue(defaultValue.toString());
-            }
-        }
-
-        return builder;
-    }
-
-
     public static RaiseException createTypeError(ThreadContext context, String message) {
         if (cTypeError == null) {
             cTypeError = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::TypeError");
diff --git a/ruby/src/main/java/google/ProtobufJavaService.java b/ruby/src/main/java/google/ProtobufJavaService.java
index a364719..713891e 100644
--- a/ruby/src/main/java/google/ProtobufJavaService.java
+++ b/ruby/src/main/java/google/ProtobufJavaService.java
@@ -49,18 +49,13 @@
          * need to exist before we try to save a reference to them
          */
         RubyProtobuf.createProtobuf(ruby);
-        RubyBuilder.createRubyBuilder(ruby);
         RubyFileDescriptor.createRubyFileDescriptor(ruby);
         RubyEnumDescriptor.createRubyEnumDescriptor(ruby);
-        RubyEnumBuilderContext.createRubyEnumBuilderContext(ruby);
         RubyRepeatedField.createRubyRepeatedField(ruby);
         RubyFieldDescriptor.createRubyFieldDescriptor(ruby);
         RubyMap.createRubyMap(ruby);
         RubyOneofDescriptor.createRubyOneofDescriptor(ruby);
-        RubyOneofBuilderContext.createRubyOneofBuilderContext(ruby);
-        RubyMessageBuilderContext.createRubyMessageBuilderContext(ruby);
         RubyDescriptor.createRubyDescriptor(ruby);
-        RubyFileBuilderContext.createRubyFileBuilderContext(ruby);
         RubyDescriptorPool.createRubyDescriptorPool(ruby);
         return true;
     }
diff --git a/ruby/tests/basic.rb b/ruby/tests/basic.rb
index ed15bde..2a7a251 100755
--- a/ruby/tests/basic.rb
+++ b/ruby/tests/basic.rb
@@ -66,7 +66,8 @@
     def test_issue_8559_crash
       msg = TestMessage.new
       msg.repeated_int32 = ::Google::Protobuf::RepeatedField.new(:int32, [1, 2, 3])
-      GC.start(full_mark: true, immediate_sweep: true)
+      # TODO: Remove the platform check once https://github.com/jruby/jruby/issues/6818 is released in JRuby 9.3.0.0
+      GC.start(full_mark: true, immediate_sweep: true) unless RUBY_PLATFORM == "java"
       TestMessage.encode(msg)
     end
 
diff --git a/ruby/tests/common_tests.rb b/ruby/tests/common_tests.rb
index 7021d60..3d9f67e 100644
--- a/ruby/tests/common_tests.rb
+++ b/ruby/tests/common_tests.rb
@@ -1781,21 +1781,24 @@
   def test_object_gc
     m = proto_module::TestMessage.new(optional_msg: proto_module::TestMessage2.new)
     m.optional_msg
-    GC.start(full_mark: true, immediate_sweep: true)
+    # TODO: Remove the platform check once https://github.com/jruby/jruby/issues/6818 is released in JRuby 9.3.0.0
+    GC.start(full_mark: true, immediate_sweep: true) unless RUBY_PLATFORM == "java"
     m.optional_msg.inspect
   end
 
   def test_object_gc_freeze
     m = proto_module::TestMessage.new
     m.repeated_float.freeze
-    GC.start(full_mark: true)
+    # TODO: Remove the platform check once https://github.com/jruby/jruby/issues/6818 is released in JRuby 9.3.0.0
+    GC.start(full_mark: true) unless RUBY_PLATFORM == "java"
 
     # Make sure we remember that the object is frozen.
     # The wrapper object contains this information, so we need to ensure that
     # the previous GC did not collect it.
     assert m.repeated_float.frozen?
 
-    GC.start(full_mark: true, immediate_sweep: true)
+    # TODO: Remove the platform check once https://github.com/jruby/jruby/issues/6818 is released in JRuby 9.3.0.0
+    GC.start(full_mark: true, immediate_sweep: true) unless RUBY_PLATFORM == "java"
     assert m.repeated_float.frozen?
   end
 end
diff --git a/ruby/tests/stress.rb b/ruby/tests/stress.rb
index 082d5e2..6a3f51d 100755
--- a/ruby/tests/stress.rb
+++ b/ruby/tests/stress.rb
@@ -30,7 +30,7 @@
       100_000.times do
         mnew = TestMessage.decode(data)
         mnew = mnew.dup
-        assert_equal mnew.inspect, m.inspect
+        assert_equal m.inspect, mnew.inspect
         assert TestMessage.encode(mnew) == data
       end
     end
diff --git a/ruby/travis-test.sh b/ruby/travis-test.sh
index b57d8b2..1f6aea5 100755
--- a/ruby/travis-test.sh
+++ b/ruby/travis-test.sh
@@ -13,7 +13,7 @@
       "rvm install $version && rvm use $version && rvm get head && \
        which ruby && \
        git clean -f && \
-       gem install bundler && bundle && \
+       gem install --no-document bundler && bundle && \
        rake test &&
        rake gc_test &&
        cd ../conformance && make test_jruby &&
@@ -23,7 +23,7 @@
       "rvm install $version && rvm use $version && \
        which ruby && \
        git clean -f && \
-       gem install bundler -v 1.17.3 && bundle && \
+       gem install --no-document bundler -v 1.17.3 && bundle && \
        rake test &&
        rake gc_test &&
        cd ../conformance && make ${RUBY_CONFORMANCE} &&