Repository: square/retrofit
Issue: Protobuf extension registry issue

Hi, I'm trying to get protobuf object  deserialized with Retrofit and `ProtoConverterFactory`. The issue is that my message B is extension of another message A like this:

A.proto

```
message A {
  ...
}
```

B.proto

```
import "A.proto"

message B {
  ...
}

extends A {
  optional B b = 111;
}
```

As it is known, to get extension, I have to register it first and then parse the stream like this:

``` java
ExtensionRegistry reg = RextensionRegistry.newInstance();
registry.add(BProtos.b);
AProtos.A.parseFrom(stream, reg);
```

But as I can see from source code, converter only calls `parseFrom(stream)` and there are no hooks to attach `ExtensionRegistry` instance in the process.

Am I missing something?

My issue is very similar to this one http://stackoverflow.com/questions/21747336/protobuf-extension-registry-issue


Issue comments:
I'll have a look! We can probably provide a create method overload which
takes a registry.

On Sat, Apr 2, 2016, 12:28 PM vganin notifications@github.com wrote:

> Hi, I'm trying to get protobuf object deserialized with Retrofit and
> ProtoConverterFactory. The issue is that my message B is extension of
> another message A like this:
> 
> A.proto
> 
> message A {
>   ...
> }
> 
> B.proto
> 
> import "A.proto"
> 
> message B {
>   ...
> }
> 
> extends A {
>   optional B b = 111;
> }
> 
> As it is known, to get extension, I have to register it first and then
> parse the stream like this:
> 
> ExtensionRegistry reg = RextensionRegistry.newInstance();
> registry.add(BProtos.b);AProtos.A.parseFrom(stream, reg);
> 
> But as I can see from source code, converter only calls parseFrom(stream)
> and there are no hooks to attach ExtensionRegistry instance in the
> process.
> 
> Am I missing something?
> 
> My issue is very similar to this one
> http://stackoverflow.com/questions/21747336/protobuf-extension-registry-issue
> 
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly or view it on GitHub
> https://github.com/square/retrofit/issues/1710


Pull request review comments:

Patch that resolved the issue:
diff --git a/retrofit-converters/protobuf/src/main/java/retrofit2/converter/protobuf/ProtoConverterFactory.java b/retrofit-converters/protobuf/src/main/java/retrofit2/converter/protobuf/ProtoConverterFactory.java
index aa4420c9d8..9b544eb0dd 100644
--- a/retrofit-converters/protobuf/src/main/java/retrofit2/converter/protobuf/ProtoConverterFactory.java
+++ b/retrofit-converters/protobuf/src/main/java/retrofit2/converter/protobuf/ProtoConverterFactory.java
@@ -15,6 +15,7 @@
  */
 package retrofit2.converter.protobuf;
 
+import com.google.protobuf.ExtensionRegistryLite;
 import com.google.protobuf.MessageLite;
 import com.google.protobuf.Parser;
 import java.lang.annotation.Annotation;
@@ -33,7 +34,18 @@
  */
 public final class ProtoConverterFactory extends Converter.Factory {
   public static ProtoConverterFactory create() {
-    return new ProtoConverterFactory();
+    return new ProtoConverterFactory(null);
+  }
+
+  /** Create an instance which uses {@code registry} when deserializing. */
+  public static ProtoConverterFactory createWithRegistry(ExtensionRegistryLite registry) {
+    return new ProtoConverterFactory(registry);
+  }
+
+  private final ExtensionRegistryLite registry;
+
+  private ProtoConverterFactory(ExtensionRegistryLite registry) {
+    this.registry = registry;
   }
 
   @Override
@@ -56,7 +68,7 @@ public static ProtoConverterFactory create() {
       throw new IllegalArgumentException(
           "Found a protobuf message but " + c.getName() + " had no PARSER field.");
     }
-    return new ProtoResponseBodyConverter<>(parser);
+    return new ProtoResponseBodyConverter<>(parser, registry);
   }
 
   @Override
diff --git a/retrofit-converters/protobuf/src/main/java/retrofit2/converter/protobuf/ProtoResponseBodyConverter.java b/retrofit-converters/protobuf/src/main/java/retrofit2/converter/protobuf/ProtoResponseBodyConverter.java
index f2b78e1ded..3241994585 100644
--- a/retrofit-converters/protobuf/src/main/java/retrofit2/converter/protobuf/ProtoResponseBodyConverter.java
+++ b/retrofit-converters/protobuf/src/main/java/retrofit2/converter/protobuf/ProtoResponseBodyConverter.java
@@ -15,6 +15,7 @@
  */
 package retrofit2.converter.protobuf;
 
+import com.google.protobuf.ExtensionRegistryLite;
 import com.google.protobuf.InvalidProtocolBufferException;
 import com.google.protobuf.MessageLite;
 import com.google.protobuf.Parser;
@@ -25,14 +26,16 @@
 final class ProtoResponseBodyConverter<T extends MessageLite>
     implements Converter<ResponseBody, T> {
   private final Parser<T> parser;
+  private final ExtensionRegistryLite registry;
 
-  ProtoResponseBodyConverter(Parser<T> parser) {
+  ProtoResponseBodyConverter(Parser<T> parser, ExtensionRegistryLite registry) {
     this.parser = parser;
+    this.registry = registry;
   }
 
   @Override public T convert(ResponseBody value) throws IOException {
     try {
-      return parser.parseFrom(value.byteStream());
+      return parser.parseFrom(value.byteStream(), registry);
     } catch (InvalidProtocolBufferException e) {
       throw new RuntimeException(e); // Despite extending IOException, this is data mismatch.
     } finally {


Test patch:
diff --git a/retrofit-converters/protobuf/src/test/java/retrofit2/converter/protobuf/PhoneProtos.java b/retrofit-converters/protobuf/src/test/java/retrofit2/converter/protobuf/PhoneProtos.java
index 69b41325b9..99d2bb2088 100644
--- a/retrofit-converters/protobuf/src/test/java/retrofit2/converter/protobuf/PhoneProtos.java
+++ b/retrofit-converters/protobuf/src/test/java/retrofit2/converter/protobuf/PhoneProtos.java
@@ -3,17 +3,17 @@
 
 package retrofit2.converter.protobuf;
 
-import com.google.protobuf.AbstractMessage;
-
 public final class PhoneProtos {
   private PhoneProtos() {}
   public static void registerAllExtensions(
       com.google.protobuf.ExtensionRegistry registry) {
+    registry.add(retrofit2.converter.protobuf.PhoneProtos.voicemail);
   }
-  public interface PhoneOrBuilder
-      extends com.google.protobuf.MessageOrBuilder {
+  public interface PhoneOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:retrofit2.converter.protobuf.Phone)
+      com.google.protobuf.GeneratedMessage.
+          ExtendableMessageOrBuilder<Phone> {
 
-    // optional string number = 1;
     /**
      * <code>optional string number = 1;</code>
      */
@@ -29,13 +29,15 @@ public interface PhoneOrBuilder
         getNumberBytes();
   }
   /**
-   * Protobuf type {@code retrofit2.Phone}
+   * Protobuf type {@code retrofit2.converter.protobuf.Phone}
    */
   public static final class Phone extends
-      com.google.protobuf.GeneratedMessage
-      implements PhoneOrBuilder {
+      com.google.protobuf.GeneratedMessage.ExtendableMessage<
+        Phone> implements
+      // @@protoc_insertion_point(message_implements:retrofit2.converter.protobuf.Phone)
+      PhoneOrBuilder {
     // Use Phone.newBuilder() to construct.
-    private Phone(com.google.protobuf.GeneratedMessage.Builder<?> builder) {
+    private Phone(com.google.protobuf.GeneratedMessage.ExtendableBuilder<retrofit2.converter.protobuf.PhoneProtos.Phone, ?> builder) {
       super(builder);
       this.unknownFields = builder.getUnknownFields();
     }
@@ -80,8 +82,9 @@ private Phone(
               break;
             }
             case 10: {
+              com.google.protobuf.ByteString bs = input.readBytes();
               bitField0_ |= 0x00000001;
-              number_ = input.readBytes();
+              number_ = bs;
               break;
             }
           }
@@ -98,14 +101,14 @@ private Phone(
     }
     public static final com.google.protobuf.Descriptors.Descriptor
         getDescriptor() {
-      return PhoneProtos.internal_static_retrofit_Phone_descriptor;
+      return retrofit2.converter.protobuf.PhoneProtos.internal_static_retrofit2_converter_protobuf_Phone_descriptor;
     }
 
     protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
         internalGetFieldAccessorTable() {
-      return PhoneProtos.internal_static_retrofit_Phone_fieldAccessorTable
+      return retrofit2.converter.protobuf.PhoneProtos.internal_static_retrofit2_converter_protobuf_Phone_fieldAccessorTable
           .ensureFieldAccessorsInitialized(
-              PhoneProtos.Phone.class, PhoneProtos.Phone.Builder.class);
+              retrofit2.converter.protobuf.PhoneProtos.Phone.class, retrofit2.converter.protobuf.PhoneProtos.Phone.Builder.class);
     }
 
     public static com.google.protobuf.Parser<Phone> PARSER =
@@ -124,7 +127,6 @@ public com.google.protobuf.Parser<Phone> getParserForType() {
     }
 
     private int bitField0_;
-    // optional string number = 1;
     public static final int NUMBER_FIELD_NUMBER = 1;
     private java.lang.Object number_;
     /**
@@ -141,7 +143,7 @@ public java.lang.String getNumber() {
       if (ref instanceof java.lang.String) {
         return (java.lang.String) ref;
       } else {
-        com.google.protobuf.ByteString bs =
+        com.google.protobuf.ByteString bs = 
             (com.google.protobuf.ByteString) ref;
         java.lang.String s = bs.toStringUtf8();
         if (bs.isValidUtf8()) {
@@ -157,7 +159,7 @@ public java.lang.String getNumber() {
         getNumberBytes() {
       java.lang.Object ref = number_;
       if (ref instanceof java.lang.String) {
-        com.google.protobuf.ByteString b =
+        com.google.protobuf.ByteString b = 
             com.google.protobuf.ByteString.copyFromUtf8(
                 (java.lang.String) ref);
         number_ = b;
@@ -173,8 +175,13 @@ private void initFields() {
     private byte memoizedIsInitialized = -1;
     public final boolean isInitialized() {
       byte isInitialized = memoizedIsInitialized;
-      if (isInitialized != -1) return isInitialized == 1;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
 
+      if (!extensionsAreInitialized()) {
+        memoizedIsInitialized = 0;
+        return false;
+      }
       memoizedIsInitialized = 1;
       return true;
     }
@@ -182,9 +189,13 @@ public final boolean isInitialized() {
     public void writeTo(com.google.protobuf.CodedOutputStream output)
                         throws java.io.IOException {
       getSerializedSize();
+      com.google.protobuf.GeneratedMessage
+        .ExtendableMessage<retrofit2.converter.protobuf.PhoneProtos.Phone>.ExtensionWriter extensionWriter =
+          newExtensionWriter();
       if (((bitField0_ & 0x00000001) == 0x00000001)) {
         output.writeBytes(1, getNumberBytes());
       }
+      extensionWriter.writeUntil(3, output);
       getUnknownFields().writeTo(output);
     }
 
@@ -198,6 +209,7 @@ public int getSerializedSize() {
         size += com.google.protobuf.CodedOutputStream
           .computeBytesSize(1, getNumberBytes());
       }
+      size += extensionsSerializedSize();
       size += getUnknownFields().getSerializedSize();
       memoizedSerializedSize = size;
       return size;
@@ -210,53 +222,53 @@ protected java.lang.Object writeReplace()
       return super.writeReplace();
     }
 
-    public static PhoneProtos.Phone parseFrom(
+    public static retrofit2.converter.protobuf.PhoneProtos.Phone parseFrom(
         com.google.protobuf.ByteString data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static PhoneProtos.Phone parseFrom(
+    public static retrofit2.converter.protobuf.PhoneProtos.Phone parseFrom(
         com.google.protobuf.ByteString data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static PhoneProtos.Phone parseFrom(byte[] data)
+    public static retrofit2.converter.protobuf.PhoneProtos.Phone parseFrom(byte[] data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static PhoneProtos.Phone parseFrom(
+    public static retrofit2.converter.protobuf.PhoneProtos.Phone parseFrom(
         byte[] data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static PhoneProtos.Phone parseFrom(java.io.InputStream input)
+    public static retrofit2.converter.protobuf.PhoneProtos.Phone parseFrom(java.io.InputStream input)
         throws java.io.IOException {
       return PARSER.parseFrom(input);
     }
-    public static PhoneProtos.Phone parseFrom(
+    public static retrofit2.converter.protobuf.PhoneProtos.Phone parseFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
       return PARSER.parseFrom(input, extensionRegistry);
     }
-    public static PhoneProtos.Phone parseDelimitedFrom(java.io.InputStream input)
+    public static retrofit2.converter.protobuf.PhoneProtos.Phone parseDelimitedFrom(java.io.InputStream input)
         throws java.io.IOException {
       return PARSER.parseDelimitedFrom(input);
     }
-    public static PhoneProtos.Phone parseDelimitedFrom(
+    public static retrofit2.converter.protobuf.PhoneProtos.Phone parseDelimitedFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
       return PARSER.parseDelimitedFrom(input, extensionRegistry);
     }
-    public static PhoneProtos.Phone parseFrom(
+    public static retrofit2.converter.protobuf.PhoneProtos.Phone parseFrom(
         com.google.protobuf.CodedInputStream input)
         throws java.io.IOException {
       return PARSER.parseFrom(input);
     }
-    public static PhoneProtos.Phone parseFrom(
+    public static retrofit2.converter.protobuf.PhoneProtos.Phone parseFrom(
         com.google.protobuf.CodedInputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -265,7 +277,7 @@ public static PhoneProtos.Phone parseFrom(
 
     public static Builder newBuilder() { return Builder.create(); }
     public Builder newBuilderForType() { return newBuilder(); }
-    public static Builder newBuilder(PhoneProtos.Phone prototype) {
+    public static Builder newBuilder(retrofit2.converter.protobuf.PhoneProtos.Phone prototype) {
       return newBuilder().mergeFrom(prototype);
     }
     public Builder toBuilder() { return newBuilder(this); }
@@ -277,21 +289,23 @@ protected Builder newBuilderForType(
       return builder;
     }
     /**
-     * Protobuf type {@code retrofit2.Phone}
+     * Protobuf type {@code retrofit2.converter.protobuf.Phone}
      */
     public static final class Builder extends
-        com.google.protobuf.GeneratedMessage.Builder<Builder>
-       implements PhoneProtos.PhoneOrBuilder {
+        com.google.protobuf.GeneratedMessage.ExtendableBuilder<
+          retrofit2.converter.protobuf.PhoneProtos.Phone, Builder> implements
+        // @@protoc_insertion_point(builder_implements:retrofit2.converter.protobuf.Phone)
+        retrofit2.converter.protobuf.PhoneProtos.PhoneOrBuilder {
       public static final com.google.protobuf.Descriptors.Descriptor
           getDescriptor() {
-        return PhoneProtos.internal_static_retrofit_Phone_descriptor;
+        return retrofit2.converter.protobuf.PhoneProtos.internal_static_retrofit2_converter_protobuf_Phone_descriptor;
       }
 
       protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
           internalGetFieldAccessorTable() {
-        return PhoneProtos.internal_static_retrofit_Phone_fieldAccessorTable
+        return retrofit2.converter.protobuf.PhoneProtos.internal_static_retrofit2_converter_protobuf_Phone_fieldAccessorTable
             .ensureFieldAccessorsInitialized(
-                PhoneProtos.Phone.class, PhoneProtos.Phone.Builder.class);
+                retrofit2.converter.protobuf.PhoneProtos.Phone.class, retrofit2.converter.protobuf.PhoneProtos.Phone.Builder.class);
       }
 
       // Construct using retrofit2.converter.protobuf.PhoneProtos.Phone.newBuilder()
@@ -325,23 +339,23 @@ public Builder clone() {
 
       public com.google.protobuf.Descriptors.Descriptor
           getDescriptorForType() {
-        return PhoneProtos.internal_static_retrofit_Phone_descriptor;
+        return retrofit2.converter.protobuf.PhoneProtos.internal_static_retrofit2_converter_protobuf_Phone_descriptor;
       }
 
-      public PhoneProtos.Phone getDefaultInstanceForType() {
-        return PhoneProtos.Phone.getDefaultInstance();
+      public retrofit2.converter.protobuf.PhoneProtos.Phone getDefaultInstanceForType() {
+        return retrofit2.converter.protobuf.PhoneProtos.Phone.getDefaultInstance();
       }
 
-      public PhoneProtos.Phone build() {
-        PhoneProtos.Phone result = buildPartial();
+      public retrofit2.converter.protobuf.PhoneProtos.Phone build() {
+        retrofit2.converter.protobuf.PhoneProtos.Phone result = buildPartial();
         if (!result.isInitialized()) {
-          throw AbstractMessage.Builder.newUninitializedMessageException(result);
+          throw newUninitializedMessageException(result);
         }
         return result;
       }
 
-      public PhoneProtos.Phone buildPartial() {
-        PhoneProtos.Phone result = new PhoneProtos.Phone(this);
+      public retrofit2.converter.protobuf.PhoneProtos.Phone buildPartial() {
+        retrofit2.converter.protobuf.PhoneProtos.Phone result = new retrofit2.converter.protobuf.PhoneProtos.Phone(this);
         int from_bitField0_ = bitField0_;
         int to_bitField0_ = 0;
         if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
@@ -354,26 +368,31 @@ public PhoneProtos.Phone buildPartial() {
       }
 
       public Builder mergeFrom(com.google.protobuf.Message other) {
-        if (other instanceof PhoneProtos.Phone) {
-          return mergeFrom((PhoneProtos.Phone)other);
+        if (other instanceof retrofit2.converter.protobuf.PhoneProtos.Phone) {
+          return mergeFrom((retrofit2.converter.protobuf.PhoneProtos.Phone)other);
         } else {
           super.mergeFrom(other);
           return this;
         }
       }
 
-      public Builder mergeFrom(PhoneProtos.Phone other) {
-        if (other == PhoneProtos.Phone.getDefaultInstance()) return this;
+      public Builder mergeFrom(retrofit2.converter.protobuf.PhoneProtos.Phone other) {
+        if (other == retrofit2.converter.protobuf.PhoneProtos.Phone.getDefaultInstance()) return this;
         if (other.hasNumber()) {
           bitField0_ |= 0x00000001;
           number_ = other.number_;
           onChanged();
         }
+        this.mergeExtensionFields(other);
         this.mergeUnknownFields(other.getUnknownFields());
         return this;
       }
 
       public final boolean isInitialized() {
+        if (!extensionsAreInitialized()) {
+          
+          return false;
+        }
         return true;
       }
 
@@ -381,11 +400,11 @@ public Builder mergeFrom(
           com.google.protobuf.CodedInputStream input,
           com.google.protobuf.ExtensionRegistryLite extensionRegistry)
           throws java.io.IOException {
-        PhoneProtos.Phone parsedMessage = null;
+        retrofit2.converter.protobuf.PhoneProtos.Phone parsedMessage = null;
         try {
           parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-          parsedMessage = (PhoneProtos.Phone) e.getUnfinishedMessage();
+          parsedMessage = (retrofit2.converter.protobuf.PhoneProtos.Phone) e.getUnfinishedMessage();
           throw e;
         } finally {
           if (parsedMessage != null) {
@@ -396,7 +415,6 @@ public Builder mergeFrom(
       }
       private int bitField0_;
 
-      // optional string number = 1;
       private java.lang.Object number_ = "";
       /**
        * <code>optional string number = 1;</code>
@@ -410,9 +428,12 @@ public boolean hasNumber() {
       public java.lang.String getNumber() {
         java.lang.Object ref = number_;
         if (!(ref instanceof java.lang.String)) {
-          java.lang.String s = ((com.google.protobuf.ByteString) ref)
-              .toStringUtf8();
-          number_ = s;
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          if (bs.isValidUtf8()) {
+            number_ = s;
+          }
           return s;
         } else {
           return (java.lang.String) ref;
@@ -425,7 +446,7 @@ public java.lang.String getNumber() {
           getNumberBytes() {
         java.lang.Object ref = number_;
         if (ref instanceof String) {
-          com.google.protobuf.ByteString b =
+          com.google.protobuf.ByteString b = 
               com.google.protobuf.ByteString.copyFromUtf8(
                   (java.lang.String) ref);
           number_ = b;
@@ -470,7 +491,7 @@ public Builder setNumberBytes(
         return this;
       }
 
-      // @@protoc_insertion_point(builder_scope:retrofit2.Phone)
+      // @@protoc_insertion_point(builder_scope:retrofit2.converter.protobuf.Phone)
     }
 
     static {
@@ -478,14 +499,25 @@ public Builder setNumberBytes(
       defaultInstance.initFields();
     }
 
-    // @@protoc_insertion_point(class_scope:retrofit2.Phone)
+    // @@protoc_insertion_point(class_scope:retrofit2.converter.protobuf.Phone)
   }
 
-  private static com.google.protobuf.Descriptors.Descriptor
-    internal_static_retrofit_Phone_descriptor;
+  public static final int VOICEMAIL_FIELD_NUMBER = 2;
+  /**
+   * <code>extend .retrofit2.converter.protobuf.Phone { ... }</code>
+   */
+  public static final
+    com.google.protobuf.GeneratedMessage.GeneratedExtension<
+      retrofit2.converter.protobuf.PhoneProtos.Phone,
+      java.lang.Boolean> voicemail = com.google.protobuf.GeneratedMessage
+          .newFileScopedGeneratedExtension(
+        java.lang.Boolean.class,
+        null);
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_retrofit2_converter_protobuf_Phone_descriptor;
   private static
     com.google.protobuf.GeneratedMessage.FieldAccessorTable
-      internal_static_retrofit_Phone_fieldAccessorTable;
+      internal_static_retrofit2_converter_protobuf_Phone_fieldAccessorTable;
 
   public static com.google.protobuf.Descriptors.FileDescriptor
       getDescriptor() {
@@ -495,28 +527,31 @@ public Builder setNumberBytes(
       descriptor;
   static {
     java.lang.String[] descriptorData = {
-      "\n\022protos/phone.proto\022\010retrofit\"\027\n\005Phone\022" +
-      "\016\n\006number\030\001 \001(\tB!\n\022retrofit.converterB\013P" +
-      "honeProtos"
+      "\n\022protos/phone.proto\022\034retrofit2.converte" +
+      "r.protobuf\"\035\n\005Phone\022\016\n\006number\030\001 \001(\t*\004\010\002\020" +
+      "\003:6\n\tvoicemail\022#.retrofit2.converter.pro" +
+      "tobuf.Phone\030\002 \001(\010B+\n\034retrofit2.converter" +
+      ".protobufB\013PhoneProtos"
     };
     com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
-      new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
-        public com.google.protobuf.ExtensionRegistry assignDescriptors(
-            com.google.protobuf.Descriptors.FileDescriptor root) {
-          descriptor = root;
-          internal_static_retrofit_Phone_descriptor =
-            getDescriptor().getMessageTypes().get(0);
-          internal_static_retrofit_Phone_fieldAccessorTable = new
-            com.google.protobuf.GeneratedMessage.FieldAccessorTable(
-              internal_static_retrofit_Phone_descriptor,
-              new java.lang.String[] { "Number", });
-          return null;
-        }
-      };
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
     com.google.protobuf.Descriptors.FileDescriptor
       .internalBuildGeneratedFileFrom(descriptorData,
         new com.google.protobuf.Descriptors.FileDescriptor[] {
         }, assigner);
+    internal_static_retrofit2_converter_protobuf_Phone_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_retrofit2_converter_protobuf_Phone_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessage.FieldAccessorTable(
+        internal_static_retrofit2_converter_protobuf_Phone_descriptor,
+        new java.lang.String[] { "Number", });
+    voicemail.internalInit(descriptor.getExtensions().get(0));
   }
 
   // @@protoc_insertion_point(outer_class_scope)
diff --git a/retrofit-converters/protobuf/src/test/java/retrofit2/converter/protobuf/ProtoConverterFactoryTest.java b/retrofit-converters/protobuf/src/test/java/retrofit2/converter/protobuf/ProtoConverterFactoryTest.java
index f2ca958f47..5a585b21f3 100644
--- a/retrofit-converters/protobuf/src/test/java/retrofit2/converter/protobuf/ProtoConverterFactoryTest.java
+++ b/retrofit-converters/protobuf/src/test/java/retrofit2/converter/protobuf/ProtoConverterFactoryTest.java
@@ -15,6 +15,7 @@
  */
 package retrofit2.converter.protobuf;
 
+import com.google.protobuf.ExtensionRegistry;
 import com.google.protobuf.InvalidProtocolBufferException;
 import java.io.IOException;
 import java.util.List;
@@ -44,10 +45,14 @@ interface Service {
     @GET("/") Call<String> wrongClass();
     @GET("/") Call<List<String>> wrongType();
   }
+  interface ServiceWithRegistry {
+    @GET("/") Call<Phone> get();
+  }
 
   @Rule public final MockWebServer server = new MockWebServer();
 
   private Service service;
+  private ServiceWithRegistry serviceWithRegistry;
 
   @Before public void setUp() {
     Retrofit retrofit = new Retrofit.Builder()
@@ -55,6 +60,14 @@ interface Service {
         .addConverterFactory(ProtoConverterFactory.create())
         .build();
     service = retrofit.create(Service.class);
+
+    ExtensionRegistry registry = ExtensionRegistry.newInstance();
+    PhoneProtos.registerAllExtensions(registry);
+    Retrofit retrofitWithRegistry = new Retrofit.Builder()
+        .baseUrl(server.url("/"))
+        .addConverterFactory(ProtoConverterFactory.createWithRegistry(registry))
+        .build();
+    serviceWithRegistry = retrofitWithRegistry.create(ServiceWithRegistry.class);
   }
 
   @Test public void serializeAndDeserialize() throws IOException, InterruptedException {
@@ -80,6 +93,17 @@ interface Service {
     assertThat(body.hasNumber()).isFalse();
   }
 
+  @Test public void deserializeUsesRegistry() throws IOException {
+    ByteString encoded = ByteString.decodeBase64("Cg4oNTE5KSA4NjctNTMwORAB");
+    server.enqueue(new MockResponse().setBody(new Buffer().write(encoded)));
+
+    Call<Phone> call = serviceWithRegistry.get();
+    Response<Phone> response = call.execute();
+    Phone body = response.body();
+    assertThat(body.getNumber()).isEqualTo("(519) 867-5309");
+    assertThat(body.getExtension(PhoneProtos.voicemail)).isEqualTo(true);
+  }
+
   @Test public void deserializeWrongClass() throws IOException {
     ByteString encoded = ByteString.decodeBase64("Cg4oNTE5KSA4NjctNTMwOQ==");
     server.enqueue(new MockResponse().setBody(new Buffer().write(encoded)));
diff --git a/retrofit-converters/protobuf/src/test/protos/phone.proto b/retrofit-converters/protobuf/src/test/protos/phone.proto
index 69876668b4..02e4b9268f 100644
--- a/retrofit-converters/protobuf/src/test/protos/phone.proto
+++ b/retrofit-converters/protobuf/src/test/protos/phone.proto
@@ -1,8 +1,14 @@
 package retrofit2.converter.protobuf;
 
-option java_package = "retrofit2";
+option java_package = "retrofit2.converter.protobuf";
 option java_outer_classname = "PhoneProtos";
 
 message Phone {
   optional string number = 1;
+
+  extensions 2;
+}
+
+extend Phone {
+  optional bool voicemail = 2;
 }