Down-integrate from google internal.
diff --git a/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java b/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java
index a535b71..4316efe 100644
--- a/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java
+++ b/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java
@@ -62,9 +62,8 @@
 
   private static final long serialVersionUID = 1L;
 
-  /** For use by generated code only.  */
-  protected UnknownFieldSetLite unknownFields =
-      UnknownFieldSetLite.getDefaultInstance();
+  /** For use by generated code only. Lazily initialized to reduce allocations. */
+  protected UnknownFieldSetLite unknownFields = null;
   
   /** For use by generated code only.  */
   protected int memoizedSerializedSize = -1;
@@ -84,19 +83,56 @@
     return (BuilderType) dynamicMethod(MethodToInvoke.NEW_BUILDER);
   }
 
+  // The general strategy for unknown fields is to use an UnknownFieldSetLite that is treated as
+  // mutable during the parsing constructor and immutable after. This allows us to avoid
+  // any unnecessary intermediary allocations while reducing the generated code size.
+
   /**
-   * Called by subclasses to parse an unknown field. For use by generated code
-   * only.
+   * Lazily initializes unknown fields.
+   */
+  private final void ensureUnknownFieldsInitialized() {
+    if (unknownFields == null) {
+      unknownFields = UnknownFieldSetLite.newInstance();
+    }
+  }
+  
+  /**
+   * Called by subclasses to parse an unknown field. For use by generated code only.
+   * 
    * @return {@code true} unless the tag is an end-group tag.
    */
-  protected static boolean parseUnknownField(
-      CodedInputStream input,
-      UnknownFieldSetLite.Builder unknownFields,
-      ExtensionRegistryLite extensionRegistry,
-      int tag) throws IOException {
+  protected boolean parseUnknownField(int tag, CodedInputStream input) throws IOException {
+    ensureUnknownFieldsInitialized();
     return unknownFields.mergeFieldFrom(tag, input);
   }
 
+  /**
+   * Called by subclasses to parse an unknown field. For use by generated code only.
+   */
+  protected void mergeVarintField(int tag, int value) {
+    ensureUnknownFieldsInitialized();
+    unknownFields.mergeVarintField(tag, value);
+  }
+  
+  /**
+   * Called by subclasses to parse an unknown field. For use by generated code only.
+   */
+  protected void mergeLengthDelimitedField(int fieldNumber, ByteString value) {
+    ensureUnknownFieldsInitialized();
+    unknownFields.mergeLengthDelimitedField(fieldNumber, value);
+  }
+  
+  /**
+   * Called by subclasses to complete parsing. For use by generated code only.
+   */
+  protected void doneParsing() {
+    if (unknownFields == null) {
+      unknownFields = UnknownFieldSetLite.getDefaultInstance();
+    } else {
+      unknownFields.makeImmutable();
+    }
+  }
+
   public final boolean isInitialized() {
     return dynamicMethod(MethodToInvoke.IS_INITIALIZED, Boolean.TRUE) != null;
   }
@@ -171,7 +207,7 @@
    * <p>For use by generated code only.
    */
   protected final void mergeUnknownFields(UnknownFieldSetLite unknownFields) {
-    this.unknownFields = UnknownFieldSetLite.concat(this.unknownFields, unknownFields);
+    this.unknownFields = UnknownFieldSetLite.mutableCopyOf(this.unknownFields, unknownFields);
   }
 
   @SuppressWarnings("unchecked")
@@ -225,7 +261,13 @@
 
     //@Override (Java 1.6 override semantics, but we must support 1.5)
     public MessageType buildPartial() {
+      if (isBuilt) {
+        return instance;
+      }
+      
       instance.dynamicMethod(MethodToInvoke.MAKE_IMMUTABLE);
+      instance.unknownFields.makeImmutable();
+      
       isBuilt = true;
       return instance;
     }
@@ -249,18 +291,6 @@
     public MessageType getDefaultInstanceForType() {
       return defaultInstance;
     }
-
-    /**
-     * Called by subclasses to parse an unknown field.
-     * @return {@code true} unless the tag is an end-group tag.
-     */
-    protected boolean parseUnknownField(
-        CodedInputStream input,
-        UnknownFieldSetLite.Builder unknownFields,
-        ExtensionRegistryLite extensionRegistry,
-        int tag) throws IOException {
-      return unknownFields.mergeFieldFrom(tag, input);
-    }
     
     public BuilderType mergeFrom(
         com.google.protobuf.CodedInputStream input,
@@ -334,6 +364,130 @@
       extensions.mergeFrom(((ExtendableMessage) other).extensions);
     }
     
+    /**
+     * Parse an unknown field or an extension. For use by generated code only.
+     * 
+     * <p>For use by generated code only.
+     * 
+     * @return {@code true} unless the tag is an end-group tag.
+     */
+    protected <MessageType extends MessageLite> boolean parseUnknownField(
+        MessageType defaultInstance,
+        CodedInputStream input,
+        ExtensionRegistryLite extensionRegistry,
+        int tag) throws IOException {
+      int wireType = WireFormat.getTagWireType(tag);
+      int fieldNumber = WireFormat.getTagFieldNumber(tag);
+
+      // TODO(dweis): How much bytecode would be saved by not requiring the generated code to
+      //     provide the default instance?
+      GeneratedExtension<MessageType, ?> extension = extensionRegistry.findLiteExtensionByNumber(
+          defaultInstance, fieldNumber);
+
+      boolean unknown = false;
+      boolean packed = false;
+      if (extension == null) {
+        unknown = true;  // Unknown field.
+      } else if (wireType == FieldSet.getWireFormatForFieldType(
+                   extension.descriptor.getLiteType(),
+                   false  /* isPacked */)) {
+        packed = false;  // Normal, unpacked value.
+      } else if (extension.descriptor.isRepeated &&
+                 extension.descriptor.type.isPackable() &&
+                 wireType == FieldSet.getWireFormatForFieldType(
+                   extension.descriptor.getLiteType(),
+                   true  /* isPacked */)) {
+        packed = true;  // Packed value.
+      } else {
+        unknown = true;  // Wrong wire type.
+      }
+
+      if (unknown) {  // Unknown field or wrong wire type.  Skip.
+        return parseUnknownField(tag, input);
+      }
+
+      if (packed) {
+        int length = input.readRawVarint32();
+        int limit = input.pushLimit(length);
+        if (extension.descriptor.getLiteType() == WireFormat.FieldType.ENUM) {
+          while (input.getBytesUntilLimit() > 0) {
+            int rawValue = input.readEnum();
+            Object value =
+                extension.descriptor.getEnumType().findValueByNumber(rawValue);
+            if (value == null) {
+              // If the number isn't recognized as a valid value for this
+              // enum, drop it (don't even add it to unknownFields).
+              return true;
+            }
+            extensions.addRepeatedField(extension.descriptor,
+                                        extension.singularToFieldSetType(value));
+          }
+        } else {
+          while (input.getBytesUntilLimit() > 0) {
+            Object value =
+                FieldSet.readPrimitiveField(input,
+                                            extension.descriptor.getLiteType(),
+                                            /*checkUtf8=*/ false);
+            extensions.addRepeatedField(extension.descriptor, value);
+          }
+        }
+        input.popLimit(limit);
+      } else {
+        Object value;
+        switch (extension.descriptor.getLiteJavaType()) {
+          case MESSAGE: {
+            MessageLite.Builder subBuilder = null;
+            if (!extension.descriptor.isRepeated()) {
+              MessageLite existingValue =
+                  (MessageLite) extensions.getField(extension.descriptor);
+              if (existingValue != null) {
+                subBuilder = existingValue.toBuilder();
+              }
+            }
+            if (subBuilder == null) {
+              subBuilder = extension.getMessageDefaultInstance()
+                  .newBuilderForType();
+            }
+            if (extension.descriptor.getLiteType() ==
+                WireFormat.FieldType.GROUP) {
+              input.readGroup(extension.getNumber(),
+                              subBuilder, extensionRegistry);
+            } else {
+              input.readMessage(subBuilder, extensionRegistry);
+            }
+            value = subBuilder.build();
+            break;
+          }
+          case ENUM:
+            int rawValue = input.readEnum();
+            value = extension.descriptor.getEnumType()
+                             .findValueByNumber(rawValue);
+            // If the number isn't recognized as a valid value for this enum,
+            // write it to unknown fields object.
+            if (value == null) {
+              mergeVarintField(fieldNumber, rawValue);
+              return true;
+            }
+            break;
+          default:
+            value = FieldSet.readPrimitiveField(input,
+                extension.descriptor.getLiteType(),
+                /*checkUtf8=*/ false);
+            break;
+        }
+
+        if (extension.descriptor.isRepeated()) {
+          extensions.addRepeatedField(extension.descriptor,
+                                      extension.singularToFieldSetType(value));
+        } else {
+          extensions.setField(extension.descriptor,
+                              extension.singularToFieldSetType(value));
+        }
+      }
+
+      return true;
+    }
+    
     private void verifyExtensionContainingType(
         final GeneratedExtension<MessageType, ?> extension) {
       if (extension.getContainingTypeDefaultInstance() !=
@@ -404,11 +558,10 @@
     }
 
 
-    /**
-     * Used by parsing constructors in generated classes.
-     */
-    protected static void makeExtensionsImmutable(
-        FieldSet<ExtensionDescriptor> extensions) {
+    @Override
+    protected final void doneParsing() {
+      super.doneParsing();
+      
       extensions.makeImmutable();
     }
 
@@ -619,131 +772,6 @@
     }
   }
 
-  //-----------------------------------------------------------------
-
-  /**
-   * Parse an unknown field or an extension. For use by generated code only.
-   * @return {@code true} unless the tag is an end-group tag.
-   */
-  protected static <MessageType extends MessageLite>
-      boolean parseUnknownField(
-          FieldSet<ExtensionDescriptor> extensions,
-          MessageType defaultInstance,
-          CodedInputStream input,
-          UnknownFieldSetLite.Builder unknownFields,
-          ExtensionRegistryLite extensionRegistry,
-          int tag) throws IOException {
-    int wireType = WireFormat.getTagWireType(tag);
-    int fieldNumber = WireFormat.getTagFieldNumber(tag);
-
-    GeneratedExtension<MessageType, ?> extension =
-      extensionRegistry.findLiteExtensionByNumber(
-          defaultInstance, fieldNumber);
-
-    boolean unknown = false;
-    boolean packed = false;
-    if (extension == null) {
-      unknown = true;  // Unknown field.
-    } else if (wireType == FieldSet.getWireFormatForFieldType(
-                 extension.descriptor.getLiteType(),
-                 false  /* isPacked */)) {
-      packed = false;  // Normal, unpacked value.
-    } else if (extension.descriptor.isRepeated &&
-               extension.descriptor.type.isPackable() &&
-               wireType == FieldSet.getWireFormatForFieldType(
-                 extension.descriptor.getLiteType(),
-                 true  /* isPacked */)) {
-      packed = true;  // Packed value.
-    } else {
-      unknown = true;  // Wrong wire type.
-    }
-
-    if (unknown) {  // Unknown field or wrong wire type.  Skip.
-      return unknownFields.mergeFieldFrom(tag, input);
-    }
-
-    if (packed) {
-      int length = input.readRawVarint32();
-      int limit = input.pushLimit(length);
-      if (extension.descriptor.getLiteType() == WireFormat.FieldType.ENUM) {
-        while (input.getBytesUntilLimit() > 0) {
-          int rawValue = input.readEnum();
-          Object value =
-              extension.descriptor.getEnumType().findValueByNumber(rawValue);
-          if (value == null) {
-            // If the number isn't recognized as a valid value for this
-            // enum, drop it (don't even add it to unknownFields).
-            return true;
-          }
-          extensions.addRepeatedField(extension.descriptor,
-                                      extension.singularToFieldSetType(value));
-        }
-      } else {
-        while (input.getBytesUntilLimit() > 0) {
-          Object value =
-              FieldSet.readPrimitiveField(input,
-                                          extension.descriptor.getLiteType(),
-                                          /*checkUtf8=*/ false);
-          extensions.addRepeatedField(extension.descriptor, value);
-        }
-      }
-      input.popLimit(limit);
-    } else {
-      Object value;
-      switch (extension.descriptor.getLiteJavaType()) {
-        case MESSAGE: {
-          MessageLite.Builder subBuilder = null;
-          if (!extension.descriptor.isRepeated()) {
-            MessageLite existingValue =
-                (MessageLite) extensions.getField(extension.descriptor);
-            if (existingValue != null) {
-              subBuilder = existingValue.toBuilder();
-            }
-          }
-          if (subBuilder == null) {
-            subBuilder = extension.getMessageDefaultInstance()
-                .newBuilderForType();
-          }
-          if (extension.descriptor.getLiteType() ==
-              WireFormat.FieldType.GROUP) {
-            input.readGroup(extension.getNumber(),
-                            subBuilder, extensionRegistry);
-          } else {
-            input.readMessage(subBuilder, extensionRegistry);
-          }
-          value = subBuilder.build();
-          break;
-        }
-        case ENUM:
-          int rawValue = input.readEnum();
-          value = extension.descriptor.getEnumType()
-                           .findValueByNumber(rawValue);
-          // If the number isn't recognized as a valid value for this enum,
-          // write it to unknown fields object.
-          if (value == null) {
-            unknownFields.mergeVarintField(fieldNumber, rawValue);
-            return true;
-          }
-          break;
-        default:
-          value = FieldSet.readPrimitiveField(input,
-              extension.descriptor.getLiteType(),
-              /*checkUtf8=*/ false);
-          break;
-      }
-
-      if (extension.descriptor.isRepeated()) {
-        extensions.addRepeatedField(extension.descriptor,
-                                    extension.singularToFieldSetType(value));
-      } else {
-        extensions.setField(extension.descriptor,
-                            extension.singularToFieldSetType(value));
-      }
-    }
-
-    return true;
-  }
-
   // -----------------------------------------------------------------
 
   /** For use by generated code only. */
@@ -893,7 +921,7 @@
           extends ExtensionLite<ContainingType, Type> {
 
     /**
-     * Create a new isntance with the given parameters.
+     * Create a new instance with the given parameters.
      *
      * The last parameter {@code singularType} is only needed for enum types.
      * We store integer values for enum types in a {@link ExtendableMessage}
@@ -905,7 +933,7 @@
         final Type defaultValue,
         final MessageLite messageDefaultInstance,
         final ExtensionDescriptor descriptor,
-        Class singularType) {
+        final Class singularType) {
       // Defensive checks to verify the correct initialization order of
       // GeneratedExtensions and their related GeneratedMessages.
       if (containingTypeDefaultInstance == null) {
@@ -921,24 +949,12 @@
       this.defaultValue = defaultValue;
       this.messageDefaultInstance = messageDefaultInstance;
       this.descriptor = descriptor;
-
-      // Use Java reflection to invoke the static method {@code valueOf} of
-      // enum types in order to convert integers to concrete enum objects.
-      this.singularType = singularType;
-      if (Internal.EnumLite.class.isAssignableFrom(singularType)) {
-        this.enumValueOf = getMethodOrDie(
-            singularType, "valueOf", int.class);
-      } else {
-        this.enumValueOf = null;
-      }
     }
 
     final ContainingType containingTypeDefaultInstance;
     final Type defaultValue;
     final MessageLite messageDefaultInstance;
     final ExtensionDescriptor descriptor;
-    final Class singularType;
-    final Method enumValueOf;
 
     /**
      * Default instance of the type being extended, used to identify that type.
@@ -980,7 +996,7 @@
 
     Object singularFromFieldSetType(final Object value) {
       if (descriptor.getLiteJavaType() == WireFormat.JavaType.ENUM) {
-        return invokeOrDie(enumValueOf, null, (Integer) value);
+        return descriptor.enumTypeMap.findValueByNumber((Integer) value);
       } else {
         return value;
       }
diff --git a/java/src/main/java/com/google/protobuf/TextFormat.java b/java/src/main/java/com/google/protobuf/TextFormat.java
index b4f4ce7..44d036c 100644
--- a/java/src/main/java/com/google/protobuf/TextFormat.java
+++ b/java/src/main/java/com/google/protobuf/TextFormat.java
@@ -119,6 +119,21 @@
   }
 
   /**
+   * Generates a human readable form of the field, useful for debugging
+   * and other purposes, with no newline characters.
+   */
+  public static String shortDebugString(final FieldDescriptor field,
+                                        final Object value) {
+    try {
+      final StringBuilder sb = new StringBuilder();
+      SINGLE_LINE_PRINTER.printField(field, value, new TextGenerator(sb));
+      return sb.toString().trim();
+    } catch (IOException e) {
+        throw new IllegalStateException(e);
+    }
+  }
+
+  /**
    * Generates a human readable form of the unknown fields, useful for debugging
    * and other purposes, with no newline characters.
    */
diff --git a/java/src/main/java/com/google/protobuf/UnknownFieldSetLite.java b/java/src/main/java/com/google/protobuf/UnknownFieldSetLite.java
index 45d5fc3..435ad4d 100644
--- a/java/src/main/java/com/google/protobuf/UnknownFieldSetLite.java
+++ b/java/src/main/java/com/google/protobuf/UnknownFieldSetLite.java
@@ -45,12 +45,13 @@
  * @author dweis@google.com (Daniel Weis)
  */
 public final class UnknownFieldSetLite {
-
-  private static final int[] EMPTY_INT_ARRAY = new int[0];
-  private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
+  
+  // Arbitrarily chosen.
+  // TODO(dweis): Tune this number?
+  private static final int MIN_CAPACITY = 8;
 
   private static final UnknownFieldSetLite DEFAULT_INSTANCE =
-      new UnknownFieldSetLite(0, EMPTY_INT_ARRAY, EMPTY_OBJECT_ARRAY);
+      new UnknownFieldSetLite(0, new int[0], new Object[0], false /* isMutable */);
 
   /**
    * Get an empty {@code UnknownFieldSetLite}.
@@ -62,25 +63,32 @@
   }
 
   /**
-   * Create a new {@link Builder}.
+   * Returns an empty {@code UnknownFieldSetLite.Builder}.
    *
    * <p>For use by generated code only.
    */
   public static Builder newBuilder() {
     return new Builder();
   }
+  
+  /**
+   * Returns a new mutable instance.
+   */
+  static UnknownFieldSetLite newInstance() {
+    return new UnknownFieldSetLite();
+  }
 
   /**
-   * Returns an {@code UnknownFieldSetLite} that is the composite of {@code first} and
+   * Returns a mutable {@code UnknownFieldSetLite} that is the composite of {@code first} and
    * {@code second}.
    */
-  static UnknownFieldSetLite concat(UnknownFieldSetLite first, UnknownFieldSetLite second) {
+  static UnknownFieldSetLite mutableCopyOf(UnknownFieldSetLite first, UnknownFieldSetLite second) {
     int count = first.count + second.count;
     int[] tags = Arrays.copyOf(first.tags, count);
     System.arraycopy(second.tags, 0, tags, first.count, second.count);
     Object[] objects = Arrays.copyOf(first.objects, count);
     System.arraycopy(second.objects, 0, objects, first.count, second.count);
-    return new UnknownFieldSetLite(count, tags, objects);
+    return new UnknownFieldSetLite(count, tags, objects, true /* isMutable */);
   }
   
   /**
@@ -102,14 +110,45 @@
    * The lazily computed serialized size of the set.
    */
   private int memoizedSerializedSize = -1;
+  
+  /**
+   * Indicates that this object is mutable. 
+   */
+  private boolean isMutable;
 
   /**
+   * Constructs a mutable {@code UnknownFieldSetLite}.
+   */
+  private UnknownFieldSetLite() {
+    this(0, new int[MIN_CAPACITY], new Object[MIN_CAPACITY], true /* isMutable */);
+  }
+  
+  /**
    * Constructs the {@code UnknownFieldSetLite}.
    */
-  private UnknownFieldSetLite(int count, int[] tags, Object[] objects) {
+  private UnknownFieldSetLite(int count, int[] tags, Object[] objects, boolean isMutable) {
     this.count = count;
     this.tags = tags;
     this.objects = objects;
+    this.isMutable = isMutable;
+  }
+  
+  /**
+   * Marks this object as immutable.
+   * 
+   * <p>Future calls to methods that attempt to modify this object will throw.
+   */
+  public void makeImmutable() {
+    this.isMutable = false;
+  }
+  
+  /**
+   * Throws an {@link UnsupportedOperationException} if immutable.
+   */
+  void checkMutable() {
+    if (!isMutable) {
+      throw new UnsupportedOperationException(); 
+    }
   }
 
   /**
@@ -223,6 +262,114 @@
     return hashCode;
   }
 
+  private void storeField(int tag, Object value) {
+    ensureCapacity();
+    
+    tags[count] = tag;
+    objects[count] = value;
+    count++;
+  }
+  
+  /**
+   * Ensures that our arrays are long enough to store more metadata.
+   */
+  private void ensureCapacity() {
+    if (count == tags.length) {        
+      int increment = count < (MIN_CAPACITY / 2) ? MIN_CAPACITY : count >> 1;
+      int newLength = count + increment;
+        
+      tags = Arrays.copyOf(tags, newLength);
+      objects = Arrays.copyOf(objects, newLength);
+    }
+  }
+  
+  /**
+   * Parse a single field from {@code input} and merge it into this set.
+   *
+   * <p>For use by generated code only.
+   *
+   * @param tag The field's tag number, which was already parsed.
+   * @return {@code false} if the tag is an end group tag.
+   */
+  boolean mergeFieldFrom(final int tag, final CodedInputStream input) throws IOException {
+    checkMutable();
+    final int fieldNumber = WireFormat.getTagFieldNumber(tag);
+    switch (WireFormat.getTagWireType(tag)) {
+      case WireFormat.WIRETYPE_VARINT:
+        storeField(tag, input.readInt64());
+        return true;
+      case WireFormat.WIRETYPE_FIXED32:
+        storeField(tag, input.readFixed32());
+        return true;
+      case WireFormat.WIRETYPE_FIXED64:
+        storeField(tag, input.readFixed64());
+        return true;
+      case WireFormat.WIRETYPE_LENGTH_DELIMITED:
+        storeField(tag, input.readBytes());
+        return true;
+      case WireFormat.WIRETYPE_START_GROUP:
+        final UnknownFieldSetLite subFieldSet = new UnknownFieldSetLite();
+        subFieldSet.mergeFrom(input);
+        input.checkLastTagWas(
+            WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP));
+        storeField(tag, subFieldSet);
+        return true;
+      case WireFormat.WIRETYPE_END_GROUP:
+        return false;
+      default:
+        throw InvalidProtocolBufferException.invalidWireType();
+    }
+  }
+
+  /**
+   * Convenience method for merging a new field containing a single varint
+   * value. This is used in particular when an unknown enum value is
+   * encountered.
+   *
+   * <p>For use by generated code only.
+   */
+  UnknownFieldSetLite mergeVarintField(int fieldNumber, int value) {
+    checkMutable();
+    if (fieldNumber == 0) {
+      throw new IllegalArgumentException("Zero is not a valid field number.");
+    }
+
+    storeField(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_VARINT), (long) value);
+    
+    return this;
+  }
+
+  /**
+   * Convenience method for merging a length-delimited field.
+   *
+   * <p>For use by generated code only.
+   */
+  UnknownFieldSetLite mergeLengthDelimitedField(final int fieldNumber, final ByteString value) {  
+    checkMutable();
+    if (fieldNumber == 0) {
+      throw new IllegalArgumentException("Zero is not a valid field number.");
+    }
+
+    storeField(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED), value);
+    
+    return this;
+  }
+  
+  /**
+   * Parse an entire message from {@code input} and merge its fields into
+   * this set.
+   */
+  private UnknownFieldSetLite mergeFrom(final CodedInputStream input) throws IOException {
+    // Ensures initialization in mergeFieldFrom.
+    while (true) {
+      final int tag = input.readTag();
+      if (tag == 0 || !mergeFieldFrom(tag, input)) {
+        break;
+      }
+    }
+    return this;
+  }
+  
   /**
    * Builder for {@link UnknownFieldSetLite}s.
    *
@@ -230,54 +377,27 @@
    *
    * <p>For use by generated code only.
    */
+  // TODO(dweis): Update the mutable API to no longer need this builder and delete.
   public static final class Builder {
-    
-    // Arbitrarily chosen.
-    // TODO(dweis): Tune this number?
-    private static final int MIN_CAPACITY = 8;
-    
-    private int count = 0;
-    private int[] tags = EMPTY_INT_ARRAY;
-    private Object[] objects = EMPTY_OBJECT_ARRAY;
 
-    private boolean built;
-
-    /**
-     * Constructs a {@code Builder}.
-     */
-    private Builder() {}
+    private UnknownFieldSetLite set;
+    
+    private Builder() {
+      this.set = null;
+    }
 
     /**
      * Ensures internal state is initialized for use.
      */
     private void ensureNotBuilt() {
-      if (built) {
-        throw new IllegalStateException("Do not reuse UnknownFieldSetLite Builders.");
+      if (set == null) {
+        set = new UnknownFieldSetLite();
       }
-    }
-    
-    private void storeField(int tag, Object value) {
-      ensureCapacity();
       
-      tags[count] = tag;
-      objects[count] = value;
-      count++;
+      set.checkMutable();
     }
     
     /**
-     * Ensures that our arrays are long enough to store more metadata.
-     */
-    private void ensureCapacity() {
-      if (count == tags.length) {        
-        int increment = count < (MIN_CAPACITY / 2) ? MIN_CAPACITY : count >> 1;
-        int newLength = count + increment;
-          
-        tags = Arrays.copyOf(tags, newLength);
-        objects = Arrays.copyOf(objects, newLength);
-      }
-    }
-
-    /**
      * Parse a single field from {@code input} and merge it into this set.
      *
      * <p>For use by generated code only.
@@ -285,36 +405,9 @@
      * @param tag The field's tag number, which was already parsed.
      * @return {@code false} if the tag is an end group tag.
      */
-    public boolean mergeFieldFrom(final int tag, final CodedInputStream input)
-                                  throws IOException {
+    boolean mergeFieldFrom(final int tag, final CodedInputStream input) throws IOException {
       ensureNotBuilt();
-
-      final int fieldNumber = WireFormat.getTagFieldNumber(tag);
-      switch (WireFormat.getTagWireType(tag)) {
-        case WireFormat.WIRETYPE_VARINT:
-          storeField(tag, input.readInt64());
-          return true;
-        case WireFormat.WIRETYPE_FIXED32:
-          storeField(tag, input.readFixed32());
-          return true;
-        case WireFormat.WIRETYPE_FIXED64:
-          storeField(tag, input.readFixed64());
-          return true;
-        case WireFormat.WIRETYPE_LENGTH_DELIMITED:
-          storeField(tag, input.readBytes());
-          return true;
-        case WireFormat.WIRETYPE_START_GROUP:
-          final Builder subBuilder = newBuilder();
-          subBuilder.mergeFrom(input);
-          input.checkLastTagWas(
-              WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP));
-          storeField(tag, subBuilder.build());
-          return true;
-        case WireFormat.WIRETYPE_END_GROUP:
-          return false;
-        default:
-          throw InvalidProtocolBufferException.invalidWireType();
-      }
+      return set.mergeFieldFrom(tag, input);
     }
 
     /**
@@ -324,71 +417,42 @@
      *
      * <p>For use by generated code only.
      */
-    public Builder mergeVarintField(int fieldNumber, int value) {
-      if (fieldNumber == 0) {
-        throw new IllegalArgumentException("Zero is not a valid field number.");
-      }
+    Builder mergeVarintField(int fieldNumber, int value) {
       ensureNotBuilt();
-
-      storeField(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_VARINT), (long) value);
-      
+      set.mergeVarintField(fieldNumber, value);
       return this;
     }
-
+    
     /**
      * Convenience method for merging a length-delimited field.
      *
      * <p>For use by generated code only.
      */
-    public Builder mergeLengthDelimitedField(
-        final int fieldNumber, final ByteString value) {  
-      if (fieldNumber == 0) {
-        throw new IllegalArgumentException("Zero is not a valid field number.");
-      }
-      ensureNotBuilt();
-
-      storeField(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED), value);
-      
+    public Builder mergeLengthDelimitedField(final int fieldNumber, final ByteString value) {
+      ensureNotBuilt();  
+      set.mergeLengthDelimitedField(fieldNumber, value);
       return this;
     }
     
     /**
-     * Parse an entire message from {@code input} and merge its fields into
-     * this set.
-     */
-    private Builder mergeFrom(final CodedInputStream input) throws IOException {
-      // Ensures initialization in mergeFieldFrom.
-      while (true) {
-        final int tag = input.readTag();
-        if (tag == 0 || !mergeFieldFrom(tag, input)) {
-          break;
-        }
-      }
-      return this;
-    }
-
-    /**
      * Build the {@link UnknownFieldSetLite} and return it.
      *
      * <p>Once {@code build()} has been called, the {@code Builder} will no
      * longer be usable.  Calling any method after {@code build()} will result
-     * in undefined behavior and can cause a {@code IllegalStateException} to be
-     * thrown.
+     * in undefined behavior and can cause an
+     * {@code UnsupportedOperationException} to be thrown.
      *
      * <p>For use by generated code only.
      */
     public UnknownFieldSetLite build() {
-      if (built) {
-        throw new IllegalStateException("Do not reuse UnknownFieldSetLite Builders.");
-      }
-
-      built = true;
-      
-      if (count == 0) {
+      if (set == null) {
         return DEFAULT_INSTANCE;
       }
+      
+      set.checkMutable();
+      set.makeImmutable();
 
-      return new UnknownFieldSetLite(count, tags, objects);
+      return set;
     }
   }
 }
diff --git a/java/src/main/java/com/google/protobuf/Utf8.java b/java/src/main/java/com/google/protobuf/Utf8.java
index 0699778..48c7e9e 100644
--- a/java/src/main/java/com/google/protobuf/Utf8.java
+++ b/java/src/main/java/com/google/protobuf/Utf8.java
@@ -360,8 +360,8 @@
   
   static class UnpairedSurrogateException extends IllegalArgumentException {
     
-    private UnpairedSurrogateException(int index) {
-      super("Unpaired surrogate at index " + index);
+    private UnpairedSurrogateException(int index, int length) {
+      super("Unpaired surrogate at index " + index + " of " + length);
     }
   }
   
@@ -417,7 +417,7 @@
           // Check that we have a well-formed surrogate pair.
           int cp = Character.codePointAt(sequence, i);
           if (cp < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
-            throw new UnpairedSurrogateException(i);
+            throw new UnpairedSurrogateException(i, utf16Length);
           }
           i++;
         }
@@ -457,7 +457,7 @@
         final char low;
         if (i + 1 == sequence.length()
                 || !Character.isSurrogatePair(c, (low = sequence.charAt(++i)))) {
-          throw new UnpairedSurrogateException((i - 1));
+          throw new UnpairedSurrogateException((i - 1), utf16Length);
         }
         int codePoint = Character.toCodePoint(c, low);
         bytes[j++] = (byte) ((0xF << 4) | (codePoint >>> 18));
@@ -470,7 +470,7 @@
         if ((Character.MIN_SURROGATE <= c && c <= Character.MAX_SURROGATE)
             && (i + 1 == sequence.length()
                 || !Character.isSurrogatePair(c, sequence.charAt(i + 1)))) {
-          throw new UnpairedSurrogateException(i);
+          throw new UnpairedSurrogateException(i, utf16Length);
         }
         throw new ArrayIndexOutOfBoundsException("Failed writing " + c + " at index " + j);
       }