Add native support for Protobuf Lite

Lite already worked by using the protobuf project, but would bring in
extra dependencies that are not intended to work with lite. Although
protobuf is not yet providing a lite package on Maven Central, we will
be able to swap to it once it is available.

There isn't any new original code in the Java portion, except for a new
overload in ProtoUtils that accepts Message instead of MessageLite.
Depending on Message in ProtoUtils allows us to support extra features
out-of-the-box without any changes to the generated code. For example,
JSON encoding could be supported in this way if Marshaller is enhanced.

However, now codegen must be aware of Lite in order to choose with Util
class to use. That is new code.
diff --git a/.gitattributes b/.gitattributes
index af34525..6754557 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,2 +1,3 @@
 TestService.java.txt binary
+TestServiceLite.java.txt binary
 TestServiceNano.java.txt binary
diff --git a/compiler/build.gradle b/compiler/build.gradle
index a487992..740bf4b 100644
--- a/compiler/build.gradle
+++ b/compiler/build.gradle
@@ -127,6 +127,11 @@
   }
 }
 
+compileTestJava {
+  // Protobuf-generated Lite produces quite a few warnings.
+  it.options.compilerArgs.removeAll(["-Xlint:unchecked", "-Xlint:rawtypes"])
+}
+
 protobuf {
   protoc {
     if (project.hasProperty('protoc')) {
@@ -223,10 +228,11 @@
   }
 }
 
-test.dependsOn('testGolden', 'testNanoGolden')
+test.dependsOn('testGolden', 'testLiteGolden', 'testNanoGolden')
 
-def configureTestTask(Task task, String suffix, String extraPackage) {
-  task.dependsOn "generateTest${suffix}Proto"
+def configureTestTask(Task task, String suffix, String dep,
+    String extraPackage) {
+  task.dependsOn "generateTest${dep}Proto"
   if (osdetector.os != 'windows') {
     task.executable "diff"
   } else {
@@ -234,11 +240,13 @@
   }
   // File isn't found on Windows if last slash is forward-slash
   def slash = System.getProperty("file.separator")
-  task.args "$buildDir/generated/source/proto/test${suffix}/grpc/io/grpc/testing/integration${extraPackage}${slash}TestServiceGrpc.java",
+  task.args "$buildDir/generated/source/proto/test${dep}/grpc/io/grpc/testing/integration${extraPackage}${slash}TestServiceGrpc.java",
        "$projectDir/src/test/golden/TestService${suffix}.java.txt"
 }
 
 task testGolden(type: Exec)
+task testLiteGolden(type: Exec)
 task testNanoGolden(type: Exec)
-configureTestTask(testGolden, '', '')
-configureTestTask(testNanoGolden, 'Nano', '/nano')
+configureTestTask(testGolden, '', '', '')
+configureTestTask(testLiteGolden, 'Lite', '', '/lite')
+configureTestTask(testNanoGolden, 'Nano', 'Nano', '/nano')
diff --git a/compiler/src/java_plugin/cpp/java_generator.cpp b/compiler/src/java_plugin/cpp/java_generator.cpp
index 30a6222..cf8cdbe 100644
--- a/compiler/src/java_plugin/cpp/java_generator.cpp
+++ b/compiler/src/java_plugin/cpp/java_generator.cpp
@@ -83,7 +83,7 @@
 
 static void PrintMethodFields(
     const ServiceDescriptor* service, map<string, string>* vars, Printer* p,
-    bool generate_nano) {
+    ProtoFlavor flavor) {
   p->Print("// Static method descriptors that strictly reflect the proto.\n");
   (*vars)["service_name"] = service->name();
   for (int i = 0; i < service->method_count(); ++i) {
@@ -91,9 +91,9 @@
     (*vars)["arg_in_id"] = to_string(2 * i);
     (*vars)["arg_out_id"] = to_string(2 * i + 1);
     (*vars)["method_name"] = method->name();
-    (*vars)["input_type"] = MessageFullJavaName(generate_nano,
+    (*vars)["input_type"] = MessageFullJavaName(flavor == ProtoFlavor::NANO,
                                                 method->input_type());
-    (*vars)["output_type"] = MessageFullJavaName(generate_nano,
+    (*vars)["output_type"] = MessageFullJavaName(flavor == ProtoFlavor::NANO,
                                                  method->output_type());
     (*vars)["method_field_name"] = MethodPropertiesFieldName(method);
     bool client_streaming = method->client_streaming();
@@ -112,7 +112,7 @@
       }
     }
 
-    if (generate_nano) {
+    if (flavor == ProtoFlavor::NANO) {
       // TODO(zsurocking): we're creating two NanoFactories for each method right now.
       // We could instead create static NanoFactories and reuse them if some methods
       // share the same request or response messages.
@@ -133,6 +133,11 @@
           "            new NanoFactory<$output_type$>(ARG_OUT_$method_field_name$))\n"
           "        );\n");
     } else {
+      if (flavor == ProtoFlavor::LITE) {
+        (*vars)["ProtoUtils"] = "io.grpc.protobuf.lite.ProtoLiteUtils";
+      } else {
+        (*vars)["ProtoUtils"] = "io.grpc.protobuf.ProtoUtils";
+      }
       p->Print(
           *vars,
           "@$ExperimentalApi$\n"
@@ -148,7 +153,7 @@
   }
   p->Print("\n");
 
-  if (generate_nano) {
+  if (flavor == ProtoFlavor::NANO) {
     p->Print(
         "private static final class NanoFactory<T extends com.google.protobuf.nano.MessageNano>\n"
         "    implements io.grpc.protobuf.nano.MessageNanoFactory<T> {\n"
@@ -162,6 +167,7 @@
         "  public T newInstance() {\n"
         "    Object o;\n"
         "    switch (id) {\n");
+    bool generate_nano = true;
     for (int i = 0; i < service->method_count(); ++i) {
       const MethodDescriptor* method = service->method(i);
       (*vars)["input_type"] = MessageFullJavaName(generate_nano,
@@ -639,7 +645,7 @@
 static void PrintService(const ServiceDescriptor* service,
                          map<string, string>* vars,
                          Printer* p,
-                         bool generate_nano) {
+                         ProtoFlavor flavor) {
   (*vars)["service_name"] = service->name();
   (*vars)["file_name"] = service->file()->name();
   (*vars)["service_class_name"] = ServiceClassName(service);
@@ -659,7 +665,7 @@
       "public static final String SERVICE_NAME = "
       "\"$Package$$service_name$\";\n\n");
 
-  PrintMethodFields(service, vars, p, generate_nano);
+  PrintMethodFields(service, vars, p, flavor);
 
   p->Print(
       *vars,
@@ -691,6 +697,7 @@
   p->Outdent();
   p->Print("}\n\n");
 
+  bool generate_nano = flavor == ProtoFlavor::NANO;
   PrintStub(service, vars, p, ASYNC_INTERFACE, generate_nano);
   PrintStub(service, vars, p, BLOCKING_CLIENT_INTERFACE, generate_nano);
   PrintStub(service, vars, p, FUTURE_CLIENT_INTERFACE, generate_nano);
@@ -736,7 +743,7 @@
 
 void GenerateService(const ServiceDescriptor* service,
                      google::protobuf::io::ZeroCopyOutputStream* out,
-                     bool generate_nano) {
+                     ProtoFlavor flavor) {
   // All non-generated classes must be referred by fully qualified names to
   // avoid collision with generated classes.
   map<string, string> vars;
@@ -753,7 +760,6 @@
   vars["ImmutableList"] = "com.google.common.collect.ImmutableList";
   vars["Collection"] = "java.util.Collection";
   vars["MethodDescriptor"] = "io.grpc.MethodDescriptor";
-  vars["ProtoUtils"] = "io.grpc.protobuf.ProtoUtils";
   vars["NanoUtils"] = "io.grpc.protobuf.nano.NanoUtils";
   vars["StreamObserver"] = "io.grpc.stub.StreamObserver";
   vars["Iterator"] = "java.util.Iterator";
@@ -766,20 +772,21 @@
   vars["ExperimentalApi"] = "io.grpc.ExperimentalApi";
 
   Printer printer(out, '$');
-  string package_name = ServiceJavaPackage(service->file(), generate_nano);
+  string package_name = ServiceJavaPackage(service->file(),
+                                           flavor == ProtoFlavor::NANO);
   if (!package_name.empty()) {
     printer.Print(
         "package $package_name$;\n\n",
         "package_name", package_name);
   }
-  PrintImports(&printer, generate_nano);
+  PrintImports(&printer, flavor == ProtoFlavor::NANO);
 
   // Package string is used to fully qualify method names.
   vars["Package"] = service->file()->package();
   if (!vars["Package"].empty()) {
     vars["Package"].append(".");
   }
-  PrintService(service, &vars, &printer, generate_nano);
+  PrintService(service, &vars, &printer, flavor);
 }
 
 string ServiceJavaPackage(const FileDescriptor* file, bool nano) {
diff --git a/compiler/src/java_plugin/cpp/java_generator.h b/compiler/src/java_plugin/cpp/java_generator.h
index 96b3b8a..29c6fd9 100644
--- a/compiler/src/java_plugin/cpp/java_generator.h
+++ b/compiler/src/java_plugin/cpp/java_generator.h
@@ -38,6 +38,10 @@
 
 namespace java_grpc_generator {
 
+enum ProtoFlavor {
+  NORMAL, LITE, NANO
+};
+
 // Returns the package name of the gRPC services defined in the given file.
 string ServiceJavaPackage(const google::protobuf::FileDescriptor* file, bool nano);
 
@@ -48,7 +52,7 @@
 // Writes the generated service interface into the given ZeroCopyOutputStream
 void GenerateService(const google::protobuf::ServiceDescriptor* service,
                      google::protobuf::io::ZeroCopyOutputStream* out,
-                     bool generate_nano);
+                     ProtoFlavor flavor);
 
 }  // namespace java_grpc_generator
 
diff --git a/compiler/src/java_plugin/cpp/java_plugin.cpp b/compiler/src/java_plugin/cpp/java_plugin.cpp
index 8f9a2e8..1c95497 100644
--- a/compiler/src/java_plugin/cpp/java_plugin.cpp
+++ b/compiler/src/java_plugin/cpp/java_plugin.cpp
@@ -9,6 +9,7 @@
 #include <google/protobuf/compiler/code_generator.h>
 #include <google/protobuf/compiler/plugin.h>
 #include <google/protobuf/descriptor.h>
+#include <google/protobuf/descriptor.pb.h>
 #include <google/protobuf/io/zero_copy_stream.h>
 
 static string JavaPackageToDir(const string& package_name) {
@@ -34,14 +35,20 @@
     vector<pair<string, string> > options;
     google::protobuf::compiler::ParseGeneratorParameter(parameter, &options);
 
-    bool generate_nano = false;
+    java_grpc_generator::ProtoFlavor flavor =
+        java_grpc_generator::ProtoFlavor::NORMAL;
+    if (file->options().optimize_for() ==
+        google::protobuf::FileOptions::LITE_RUNTIME) {
+      flavor = java_grpc_generator::ProtoFlavor::LITE;
+    }
     for (int i = 0; i < options.size(); i++) {
       if (options[i].first == "nano" && options[i].second == "true") {
-        generate_nano = true;
+        flavor = java_grpc_generator::ProtoFlavor::NANO;
       }
     }
 
-    string package_name = java_grpc_generator::ServiceJavaPackage(file, generate_nano);
+    string package_name = java_grpc_generator::ServiceJavaPackage(
+        file, flavor == java_grpc_generator::ProtoFlavor::NANO);
     string package_filename = JavaPackageToDir(package_name);
     for (int i = 0; i < file->service_count(); ++i) {
       const google::protobuf::ServiceDescriptor* service = file->service(i);
@@ -49,7 +56,7 @@
           + java_grpc_generator::ServiceClassName(service) + ".java";
       std::unique_ptr<google::protobuf::io::ZeroCopyOutputStream> output(
           context->Open(filename));
-      java_grpc_generator::GenerateService(service, output.get(), generate_nano);
+      java_grpc_generator::GenerateService(service, output.get(), flavor);
     }
     return true;
   }
diff --git a/compiler/src/test/golden/TestServiceLite.java.txt b/compiler/src/test/golden/TestServiceLite.java.txt
new file mode 100644
index 0000000..86da7e7
--- /dev/null
+++ b/compiler/src/test/golden/TestServiceLite.java.txt
@@ -0,0 +1,323 @@
+package io.grpc.testing.integration.lite;
+
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler",
+    comments = "Source: test_lite.proto")
+public class TestServiceGrpc {
+
+  private TestServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.testing.lite.TestService";
+
+  // Static method descriptors that strictly reflect the proto.
+  @io.grpc.ExperimentalApi
+  public static final io.grpc.MethodDescriptor<io.grpc.testing.integration.lite.TestLite.SimpleRequest,
+      io.grpc.testing.integration.lite.TestLite.SimpleResponse> METHOD_UNARY_CALL =
+      io.grpc.MethodDescriptor.create(
+          io.grpc.MethodDescriptor.MethodType.UNARY,
+          generateFullMethodName(
+              "grpc.testing.lite.TestService", "UnaryCall"),
+          io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.SimpleRequest.getDefaultInstance()),
+          io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.SimpleResponse.getDefaultInstance()));
+  @io.grpc.ExperimentalApi
+  public static final io.grpc.MethodDescriptor<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest,
+      io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> METHOD_STREAMING_OUTPUT_CALL =
+      io.grpc.MethodDescriptor.create(
+          io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING,
+          generateFullMethodName(
+              "grpc.testing.lite.TestService", "StreamingOutputCall"),
+          io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest.getDefaultInstance()),
+          io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse.getDefaultInstance()));
+  @io.grpc.ExperimentalApi
+  public static final io.grpc.MethodDescriptor<io.grpc.testing.integration.lite.TestLite.StreamingInputCallRequest,
+      io.grpc.testing.integration.lite.TestLite.StreamingInputCallResponse> METHOD_STREAMING_INPUT_CALL =
+      io.grpc.MethodDescriptor.create(
+          io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING,
+          generateFullMethodName(
+              "grpc.testing.lite.TestService", "StreamingInputCall"),
+          io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.StreamingInputCallRequest.getDefaultInstance()),
+          io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.StreamingInputCallResponse.getDefaultInstance()));
+  @io.grpc.ExperimentalApi
+  public static final io.grpc.MethodDescriptor<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest,
+      io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> METHOD_FULL_BIDI_CALL =
+      io.grpc.MethodDescriptor.create(
+          io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING,
+          generateFullMethodName(
+              "grpc.testing.lite.TestService", "FullBidiCall"),
+          io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest.getDefaultInstance()),
+          io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse.getDefaultInstance()));
+  @io.grpc.ExperimentalApi
+  public static final io.grpc.MethodDescriptor<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest,
+      io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> METHOD_HALF_BIDI_CALL =
+      io.grpc.MethodDescriptor.create(
+          io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING,
+          generateFullMethodName(
+              "grpc.testing.lite.TestService", "HalfBidiCall"),
+          io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest.getDefaultInstance()),
+          io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse.getDefaultInstance()));
+
+  public static TestServiceStub newStub(io.grpc.Channel channel) {
+    return new TestServiceStub(channel);
+  }
+
+  public static TestServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new TestServiceBlockingStub(channel);
+  }
+
+  public static TestServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new TestServiceFutureStub(channel);
+  }
+
+  public static interface TestService {
+
+    public void unaryCall(io.grpc.testing.integration.lite.TestLite.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.SimpleResponse> responseObserver);
+
+    public void streamingOutputCall(io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> responseObserver);
+
+    public io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingInputCallRequest> streamingInputCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingInputCallResponse> responseObserver);
+
+    public io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest> fullBidiCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> responseObserver);
+
+    public io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest> halfBidiCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> responseObserver);
+  }
+
+  public static interface TestServiceBlockingClient {
+
+    public io.grpc.testing.integration.lite.TestLite.SimpleResponse unaryCall(io.grpc.testing.integration.lite.TestLite.SimpleRequest request);
+
+    public java.util.Iterator<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> streamingOutputCall(
+        io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest request);
+  }
+
+  public static interface TestServiceFutureClient {
+
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.testing.integration.lite.TestLite.SimpleResponse> unaryCall(
+        io.grpc.testing.integration.lite.TestLite.SimpleRequest request);
+  }
+
+  public static class TestServiceStub extends io.grpc.stub.AbstractStub<TestServiceStub>
+      implements TestService {
+    private TestServiceStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestServiceStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestServiceStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestServiceStub(channel, callOptions);
+    }
+
+    @java.lang.Override
+    public void unaryCall(io.grpc.testing.integration.lite.TestLite.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.SimpleResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(METHOD_UNARY_CALL, getCallOptions()), request, responseObserver);
+    }
+
+    @java.lang.Override
+    public void streamingOutputCall(io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> responseObserver) {
+      asyncServerStreamingCall(
+          getChannel().newCall(METHOD_STREAMING_OUTPUT_CALL, getCallOptions()), request, responseObserver);
+    }
+
+    @java.lang.Override
+    public io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingInputCallRequest> streamingInputCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingInputCallResponse> responseObserver) {
+      return asyncClientStreamingCall(
+          getChannel().newCall(METHOD_STREAMING_INPUT_CALL, getCallOptions()), responseObserver);
+    }
+
+    @java.lang.Override
+    public io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest> fullBidiCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(METHOD_FULL_BIDI_CALL, getCallOptions()), responseObserver);
+    }
+
+    @java.lang.Override
+    public io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest> halfBidiCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(METHOD_HALF_BIDI_CALL, getCallOptions()), responseObserver);
+    }
+  }
+
+  public static class TestServiceBlockingStub extends io.grpc.stub.AbstractStub<TestServiceBlockingStub>
+      implements TestServiceBlockingClient {
+    private TestServiceBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestServiceBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestServiceBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestServiceBlockingStub(channel, callOptions);
+    }
+
+    @java.lang.Override
+    public io.grpc.testing.integration.lite.TestLite.SimpleResponse unaryCall(io.grpc.testing.integration.lite.TestLite.SimpleRequest request) {
+      return blockingUnaryCall(
+          getChannel(), METHOD_UNARY_CALL, getCallOptions(), request);
+    }
+
+    @java.lang.Override
+    public java.util.Iterator<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse> streamingOutputCall(
+        io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest request) {
+      return blockingServerStreamingCall(
+          getChannel(), METHOD_STREAMING_OUTPUT_CALL, getCallOptions(), request);
+    }
+  }
+
+  public static class TestServiceFutureStub extends io.grpc.stub.AbstractStub<TestServiceFutureStub>
+      implements TestServiceFutureClient {
+    private TestServiceFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestServiceFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestServiceFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestServiceFutureStub(channel, callOptions);
+    }
+
+    @java.lang.Override
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.testing.integration.lite.TestLite.SimpleResponse> unaryCall(
+        io.grpc.testing.integration.lite.TestLite.SimpleRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(METHOD_UNARY_CALL, getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_UNARY_CALL = 0;
+  private static final int METHODID_STREAMING_OUTPUT_CALL = 1;
+  private static final int METHODID_STREAMING_INPUT_CALL = 2;
+  private static final int METHODID_FULL_BIDI_CALL = 3;
+  private static final int METHODID_HALF_BIDI_CALL = 4;
+
+  private static class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final TestService serviceImpl;
+    private final int methodId;
+
+    public MethodHandlers(TestService serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_UNARY_CALL:
+          serviceImpl.unaryCall((io.grpc.testing.integration.lite.TestLite.SimpleRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.SimpleResponse>) responseObserver);
+          break;
+        case METHODID_STREAMING_OUTPUT_CALL:
+          serviceImpl.streamingOutputCall((io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_STREAMING_INPUT_CALL:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.streamingInputCall(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingInputCallResponse>) responseObserver);
+        case METHODID_FULL_BIDI_CALL:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.fullBidiCall(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse>) responseObserver);
+        case METHODID_HALF_BIDI_CALL:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.halfBidiCall(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse>) responseObserver);
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  public static io.grpc.ServerServiceDefinition bindService(
+      final TestService serviceImpl) {
+    return io.grpc.ServerServiceDefinition.builder(SERVICE_NAME)
+        .addMethod(
+          METHOD_UNARY_CALL,
+          asyncUnaryCall(
+            new MethodHandlers<
+              io.grpc.testing.integration.lite.TestLite.SimpleRequest,
+              io.grpc.testing.integration.lite.TestLite.SimpleResponse>(
+                serviceImpl, METHODID_UNARY_CALL)))
+        .addMethod(
+          METHOD_STREAMING_OUTPUT_CALL,
+          asyncServerStreamingCall(
+            new MethodHandlers<
+              io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest,
+              io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse>(
+                serviceImpl, METHODID_STREAMING_OUTPUT_CALL)))
+        .addMethod(
+          METHOD_STREAMING_INPUT_CALL,
+          asyncClientStreamingCall(
+            new MethodHandlers<
+              io.grpc.testing.integration.lite.TestLite.StreamingInputCallRequest,
+              io.grpc.testing.integration.lite.TestLite.StreamingInputCallResponse>(
+                serviceImpl, METHODID_STREAMING_INPUT_CALL)))
+        .addMethod(
+          METHOD_FULL_BIDI_CALL,
+          asyncBidiStreamingCall(
+            new MethodHandlers<
+              io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest,
+              io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse>(
+                serviceImpl, METHODID_FULL_BIDI_CALL)))
+        .addMethod(
+          METHOD_HALF_BIDI_CALL,
+          asyncBidiStreamingCall(
+            new MethodHandlers<
+              io.grpc.testing.integration.lite.TestLite.StreamingOutputCallRequest,
+              io.grpc.testing.integration.lite.TestLite.StreamingOutputCallResponse>(
+                serviceImpl, METHODID_HALF_BIDI_CALL)))
+        .build();
+  }
+}
diff --git a/compiler/src/test/proto/test_lite.proto b/compiler/src/test/proto/test_lite.proto
new file mode 100644
index 0000000..0e42203
--- /dev/null
+++ b/compiler/src/test/proto/test_lite.proto
@@ -0,0 +1,54 @@
+// A simple service definition for testing the protoc plugin.
+syntax = "proto2";
+
+package grpc.testing.lite;
+
+option java_package = "io.grpc.testing.integration.lite";
+option optimize_for = LITE_RUNTIME;
+
+message SimpleRequest {
+}
+
+message SimpleResponse {
+}
+
+message StreamingInputCallRequest {
+}
+
+message StreamingInputCallResponse {
+}
+
+message StreamingOutputCallRequest {
+}
+
+message StreamingOutputCallResponse {
+}
+
+service TestService {
+  // One request followed by one response.
+  // The server returns the client payload as-is.
+  rpc UnaryCall(SimpleRequest) returns (SimpleResponse);
+
+  // One request followed by a sequence of responses (streamed download).
+  // The server returns the payload with client desired type and sizes.
+  rpc StreamingOutputCall(StreamingOutputCallRequest)
+      returns (stream StreamingOutputCallResponse);
+
+  // A sequence of requests followed by one response (streamed upload).
+  // The server returns the aggregated size of client payload as the result.
+  rpc StreamingInputCall(stream StreamingInputCallRequest)
+      returns (StreamingInputCallResponse);
+
+  // A sequence of requests with each request served by the server immediately.
+  // As one request could lead to multiple responses, this interface
+  // demonstrates the idea of full bidirectionality.
+  rpc FullBidiCall(stream StreamingOutputCallRequest)
+      returns (stream StreamingOutputCallResponse);
+
+  // A sequence of requests followed by a sequence of responses.
+  // The server buffers all the client requests and then serves them in order. A
+  // stream of responses are returned to the client when the server starts with
+  // first request.
+  rpc HalfBidiCall(stream StreamingOutputCallRequest)
+      returns (stream StreamingOutputCallResponse);
+}
diff --git a/protobuf-lite/build.gradle b/protobuf-lite/build.gradle
new file mode 100644
index 0000000..1425a3b
--- /dev/null
+++ b/protobuf-lite/build.gradle
@@ -0,0 +1,15 @@
+plugins {
+    id "be.insaneprogramming.gradle.animalsniffer" version "1.4.0"
+}
+
+description = 'gRPC: Protobuf Lite'
+
+dependencies {
+    compile project(':grpc-core'),
+            libraries.protobuf,
+            libraries.guava
+}
+
+animalsniffer {
+    signature = "org.codehaus.mojo.signature:java16:+@signature"
+}
diff --git a/protobuf/src/main/java/io/grpc/protobuf/ProtoInputStream.java b/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoInputStream.java
similarity index 98%
rename from protobuf/src/main/java/io/grpc/protobuf/ProtoInputStream.java
rename to protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoInputStream.java
index 186e3c1..b2e4282 100644
--- a/protobuf/src/main/java/io/grpc/protobuf/ProtoInputStream.java
+++ b/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoInputStream.java
@@ -29,7 +29,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package io.grpc.protobuf;
+package io.grpc.protobuf.lite;
 
 import com.google.common.io.ByteStreams;
 import com.google.protobuf.CodedOutputStream;
diff --git a/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoLiteUtils.java b/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoLiteUtils.java
new file mode 100644
index 0000000..c5677ff
--- /dev/null
+++ b/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoLiteUtils.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2014, Google Inc. All rights reserved.
+ *
+ * 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 io.grpc.protobuf.lite;
+
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.MessageLite;
+import com.google.protobuf.Parser;
+
+import io.grpc.ExperimentalApi;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor.Marshaller;
+import io.grpc.Status;
+
+import java.io.InputStream;
+
+/**
+ * Utility methods for using protobuf with grpc.
+ */
+@ExperimentalApi("Experimental until Lite is stable in protobuf")
+public class ProtoLiteUtils {
+
+  /** Create a {@code Marshaller} for protos of the same type as {@code defaultInstance}. */
+  public static <T extends MessageLite> Marshaller<T> marshaller(final T defaultInstance) {
+    @SuppressWarnings("unchecked")
+    final Parser<T> parser = (Parser<T>) defaultInstance.getParserForType();
+    return new Marshaller<T>() {
+      @Override
+      public InputStream stream(T value) {
+        return new ProtoInputStream(value, parser);
+      }
+
+      @Override
+      public T parse(InputStream stream) {
+        if (stream instanceof ProtoInputStream) {
+          ProtoInputStream protoStream = (ProtoInputStream) stream;
+          // Optimization for in-memory transport. Returning provided object is safe since protobufs
+          // are immutable.
+          //
+          // However, we can't assume the types match, so we have to verify the parser matches.
+          // Today the parser is always the same for a given proto, but that isn't guaranteed. Even
+          // if not, using the same MethodDescriptor would ensure the parser matches and permit us
+          // to enable this optimization.
+          if (protoStream.parser() == parser) {
+            try {
+              @SuppressWarnings("unchecked")
+              T message = (T) ((ProtoInputStream) stream).message();
+              return message;
+            } catch (IllegalStateException ex) {
+              // Stream must have been read from, which is a strange state. Since the point of this
+              // optimization is to be transparent, instead of throwing an error we'll continue,
+              // even though it seems likely there's a bug.
+            }
+          }
+        }
+        try {
+          return parseFrom(stream);
+        } catch (InvalidProtocolBufferException ipbe) {
+          throw Status.INTERNAL.withDescription("Invalid protobuf byte sequence")
+            .withCause(ipbe).asRuntimeException();
+        }
+      }
+
+      private T parseFrom(InputStream stream) throws InvalidProtocolBufferException {
+        // Pre-create the CodedInputStream so that we can remove the size limit restriction
+        // when parsing.
+        CodedInputStream codedInput = CodedInputStream.newInstance(stream);
+        codedInput.setSizeLimit(Integer.MAX_VALUE);
+
+        T message = parser.parseFrom(codedInput);
+        try {
+          codedInput.checkLastTagWas(0);
+          return message;
+        } catch (InvalidProtocolBufferException e) {
+          e.setUnfinishedMessage(message);
+          throw e;
+        }
+      }
+    };
+  }
+
+  /**
+   * Produce a metadata marshaller for a protobuf type.
+   */
+  public static <T extends MessageLite> Metadata.BinaryMarshaller<T> metadataMarshaller(
+      final T instance) {
+    return new Metadata.BinaryMarshaller<T>() {
+      @Override
+      public byte[] toBytes(T value) {
+        return value.toByteArray();
+      }
+
+      @Override
+      @SuppressWarnings("unchecked")
+      public T parseBytes(byte[] serialized) {
+        try {
+          return (T) instance.getParserForType().parseFrom(serialized);
+        } catch (InvalidProtocolBufferException ipbe) {
+          throw new IllegalArgumentException(ipbe);
+        }
+      }
+    };
+  }
+
+  private ProtoLiteUtils() {
+  }
+}
diff --git a/protobuf-lite/src/test/java/io/grpc/protobuf/lite/ProtoLiteUtilsTest.java b/protobuf-lite/src/test/java/io/grpc/protobuf/lite/ProtoLiteUtilsTest.java
new file mode 100644
index 0000000..a5042a4
--- /dev/null
+++ b/protobuf-lite/src/test/java/io/grpc/protobuf/lite/ProtoLiteUtilsTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2015, Google Inc. All rights reserved.
+ *
+ * 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 io.grpc.protobuf.lite;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import com.google.common.io.ByteStreams;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.Empty;
+import com.google.protobuf.Enum;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.Type;
+
+import io.grpc.Drainable;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor.Marshaller;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+/** Unit tests for {@link ProtoLiteUtils}. */
+@RunWith(JUnit4.class)
+public class ProtoLiteUtilsTest {
+  private Marshaller<Type> marshaller = ProtoLiteUtils.marshaller(Type.getDefaultInstance());
+  private Type proto = Type.newBuilder().setName("name").build();
+
+  @Test
+  public void testPassthrough() {
+    assertSame(proto, marshaller.parse(marshaller.stream(proto)));
+  }
+
+  @Test
+  public void testRoundtrip() throws Exception {
+    InputStream is = marshaller.stream(proto);
+    is = new ByteArrayInputStream(ByteStreams.toByteArray(is));
+    assertEquals(proto, marshaller.parse(is));
+  }
+
+  @Test
+  public void testInvalidatedMessage() throws Exception {
+    InputStream is = marshaller.stream(proto);
+    // Invalidates message, and drains all bytes
+    ByteStreams.toByteArray(is);
+    try {
+      ((ProtoInputStream) is).message();
+      fail("Expected exception");
+    } catch (IllegalStateException ex) {
+      // expected
+    }
+    // Zero bytes is the default message
+    assertEquals(Type.getDefaultInstance(), marshaller.parse(is));
+  }
+
+  @Test
+  public void parseInvalid() throws Exception {
+    InputStream is = new ByteArrayInputStream(new byte[] {-127});
+    try {
+      marshaller.parse(is);
+      fail("Expected exception");
+    } catch (StatusRuntimeException ex) {
+      assertEquals(Status.Code.INTERNAL, ex.getStatus().getCode());
+      assertNotNull(((InvalidProtocolBufferException) ex.getCause()).getUnfinishedMessage());
+    }
+  }
+
+  @Test
+  public void testMismatch() throws Exception {
+    Marshaller<Enum> enumMarshaller = ProtoLiteUtils.marshaller(Enum.getDefaultInstance());
+    // Enum's name and Type's name are both strings with tag 1.
+    Enum altProto = Enum.newBuilder().setName(proto.getName()).build();
+    assertEquals(proto, marshaller.parse(enumMarshaller.stream(altProto)));
+  }
+
+  @Test
+  public void marshallerShouldNotLimitProtoSize() throws Exception {
+    // The default limit is 64MB. Using a larger proto to verify that the limit is not enforced.
+    byte[] bigName = new byte[70 * 1024 * 1024];
+    Arrays.fill(bigName, (byte) 32);
+
+    proto = Type.newBuilder().setNameBytes(ByteString.copyFrom(bigName)).build();
+
+    // Just perform a round trip to verify that it works.
+    testRoundtrip();
+  }
+
+  @Test
+  public void testAvailable() throws Exception {
+    InputStream is = marshaller.stream(proto);
+    assertEquals(proto.getSerializedSize(), is.available());
+    is.read();
+    assertEquals(proto.getSerializedSize() - 1, is.available());
+    while (is.read() != -1) {}
+    assertEquals(-1, is.read());
+    assertEquals(0, is.available());
+  }
+
+  @Test
+  public void testEmpty() throws IOException {
+    Marshaller<Empty> marshaller = ProtoLiteUtils.marshaller(Empty.getDefaultInstance());
+    InputStream is = marshaller.stream(Empty.getDefaultInstance());
+    assertEquals(0, is.available());
+    byte[] b = new byte[10];
+    assertEquals(-1, is.read(b));
+    assertArrayEquals(new byte[10], b);
+    // Do the same thing again, because the internal state may be different
+    assertEquals(-1, is.read(b));
+    assertArrayEquals(new byte[10], b);
+    assertEquals(-1, is.read());
+    assertEquals(0, is.available());
+  }
+
+  @Test
+  public void testDrainTo_all() throws Exception {
+    byte[] golden = ByteStreams.toByteArray(marshaller.stream(proto));
+    InputStream is = marshaller.stream(proto);
+    Drainable d = (Drainable) is;
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    int drained = d.drainTo(baos);
+    assertEquals(baos.size(), drained);
+    assertArrayEquals(golden, baos.toByteArray());
+    assertEquals(0, is.available());
+  }
+
+  @Test
+  public void testDrainTo_partial() throws Exception {
+    final byte[] golden;
+    {
+      InputStream is = marshaller.stream(proto);
+      is.read();
+      golden = ByteStreams.toByteArray(is);
+    }
+    InputStream is = marshaller.stream(proto);
+    is.read();
+    Drainable d = (Drainable) is;
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    int drained = d.drainTo(baos);
+    assertEquals(baos.size(), drained);
+    assertArrayEquals(golden, baos.toByteArray());
+    assertEquals(0, is.available());
+  }
+
+  @Test
+  public void testDrainTo_none() throws Exception {
+    byte[] golden = ByteStreams.toByteArray(marshaller.stream(proto));
+    InputStream is = marshaller.stream(proto);
+    ByteStreams.toByteArray(is);
+    Drainable d = (Drainable) is;
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    assertEquals(0, d.drainTo(baos));
+    assertArrayEquals(new byte[0], baos.toByteArray());
+    assertEquals(0, is.available());
+  }
+
+  @Test
+  public void metadataMarshaller_roundtrip() {
+    Metadata.BinaryMarshaller<Type> metadataMarshaller =
+        ProtoLiteUtils.metadataMarshaller(Type.getDefaultInstance());
+    assertEquals(proto, metadataMarshaller.parseBytes(metadataMarshaller.toBytes(proto)));
+  }
+
+  @Test
+  public void metadataMarshaller_invalid() {
+    Metadata.BinaryMarshaller<Type> metadataMarshaller =
+        ProtoLiteUtils.metadataMarshaller(Type.getDefaultInstance());
+    try {
+      metadataMarshaller.parseBytes(new byte[] {-127});
+      fail("Expected exception");
+    } catch (IllegalArgumentException ex) {
+      assertNotNull(((InvalidProtocolBufferException) ex.getCause()).getUnfinishedMessage());
+    }
+  }
+}
diff --git a/protobuf/build.gradle b/protobuf/build.gradle
index 89da4b0..6b54173 100644
--- a/protobuf/build.gradle
+++ b/protobuf/build.gradle
@@ -6,6 +6,7 @@
 
 dependencies {
     compile project(':grpc-core'),
+            project(':grpc-protobuf-lite'),
             libraries.protobuf,
             libraries.guava,
             libraries.protobuf_util
diff --git a/protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java b/protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java
index ae8f001..48cb6e1 100644
--- a/protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java
+++ b/protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java
@@ -31,13 +31,10 @@
 
 package io.grpc.protobuf;
 
-import com.google.common.annotations.VisibleForTesting;
-import com.google.protobuf.CodedInputStream;
 import com.google.protobuf.InvalidProtocolBufferException;
 import com.google.protobuf.Message;
 import com.google.protobuf.Message.Builder;
 import com.google.protobuf.MessageLite;
-import com.google.protobuf.Parser;
 import com.google.protobuf.util.JsonFormat;
 import com.google.protobuf.util.JsonFormat.Printer;
 
@@ -45,6 +42,7 @@
 import io.grpc.Metadata;
 import io.grpc.MethodDescriptor.Marshaller;
 import io.grpc.Status;
+import io.grpc.protobuf.lite.ProtoLiteUtils;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
@@ -58,63 +56,19 @@
  */
 public class ProtoUtils {
 
-  /** Create a {@code Marshaller} for protos of the same type as {@code defaultInstance}. */
+  /**
+   * Create a {@code Marshaller} for protos of the same type as {@code defaultInstance}.
+   *
+   * @deprecated Use ProtoLiteUtils.marshaller() or Message-based marshaller() instead
+   */
+  @Deprecated
   public static <T extends MessageLite> Marshaller<T> marshaller(final T defaultInstance) {
-    @SuppressWarnings("unchecked")
-    final Parser<T> parser = (Parser<T>) defaultInstance.getParserForType();
-    return new Marshaller<T>() {
-      @Override
-      public InputStream stream(T value) {
-        return new ProtoInputStream(value, parser);
-      }
+    return ProtoLiteUtils.marshaller(defaultInstance);
+  }
 
-      @Override
-      public T parse(InputStream stream) {
-        if (stream instanceof ProtoInputStream) {
-          ProtoInputStream protoStream = (ProtoInputStream) stream;
-          // Optimization for in-memory transport. Returning provided object is safe since protobufs
-          // are immutable.
-          //
-          // However, we can't assume the types match, so we have to verify the parser matches.
-          // Today the parser is always the same for a given proto, but that isn't guaranteed. Even
-          // if not, using the same MethodDescriptor would ensure the parser matches and permit us
-          // to enable this optimization.
-          if (protoStream.parser() == parser) {
-            try {
-              @SuppressWarnings("unchecked")
-              T message = (T) ((ProtoInputStream) stream).message();
-              return message;
-            } catch (IllegalStateException ex) {
-              // Stream must have been read from, which is a strange state. Since the point of this
-              // optimization is to be transparent, instead of throwing an error we'll continue,
-              // even though it seems likely there's a bug.
-            }
-          }
-        }
-        try {
-          return parseFrom(stream);
-        } catch (InvalidProtocolBufferException ipbe) {
-          throw Status.INTERNAL.withDescription("Invalid protobuf byte sequence")
-            .withCause(ipbe).asRuntimeException();
-        }
-      }
-
-      private T parseFrom(InputStream stream) throws InvalidProtocolBufferException {
-        // Pre-create the CodedInputStream so that we can remove the size limit restriction
-        // when parsing.
-        CodedInputStream codedInput = CodedInputStream.newInstance(stream);
-        codedInput.setSizeLimit(Integer.MAX_VALUE);
-
-        T message = parser.parseFrom(codedInput);
-        try {
-          codedInput.checkLastTagWas(0);
-          return message;
-        } catch (InvalidProtocolBufferException e) {
-          e.setUnfinishedMessage(message);
-          throw e;
-        }
-      }
-    };
+  /** Create a {@code Marshaller} for protos of the same type as {@code defaultInstance}. */
+  public static <T extends Message> Marshaller<T> marshaller(final T defaultInstance) {
+    return ProtoLiteUtils.marshaller(defaultInstance);
   }
 
   /**
@@ -170,27 +124,7 @@
   public static <T extends Message> Metadata.Key<T> keyForProto(T instance) {
     return Metadata.Key.of(
         instance.getDescriptorForType().getFullName() + Metadata.BINARY_HEADER_SUFFIX,
-        keyMarshaller(instance));
-  }
-
-  @VisibleForTesting
-  static <T extends MessageLite> Metadata.BinaryMarshaller<T> keyMarshaller(final T instance) {
-    return new Metadata.BinaryMarshaller<T>() {
-      @Override
-      public byte[] toBytes(T value) {
-        return value.toByteArray();
-      }
-
-      @Override
-      @SuppressWarnings("unchecked")
-      public T parseBytes(byte[] serialized) {
-        try {
-          return (T) instance.getParserForType().parseFrom(serialized);
-        } catch (InvalidProtocolBufferException ipbe) {
-          throw new IllegalArgumentException(ipbe);
-        }
-      }
-    };
+        ProtoLiteUtils.metadataMarshaller(instance));
   }
 
   private ProtoUtils() {
diff --git a/protobuf/src/test/java/io/grpc/protobuf/ProtoUtilsTest.java b/protobuf/src/test/java/io/grpc/protobuf/ProtoUtilsTest.java
index dc9ede4..8281da8 100644
--- a/protobuf/src/test/java/io/grpc/protobuf/ProtoUtilsTest.java
+++ b/protobuf/src/test/java/io/grpc/protobuf/ProtoUtilsTest.java
@@ -31,166 +31,42 @@
 
 package io.grpc.protobuf;
 
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.fail;
 
 import com.google.common.io.ByteStreams;
-import com.google.protobuf.ByteString;
-import com.google.protobuf.Empty;
-import com.google.protobuf.Enum;
-import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.MessageLite;
 import com.google.protobuf.Type;
 
-import io.grpc.Drainable;
-import io.grpc.Metadata;
 import io.grpc.MethodDescriptor.Marshaller;
-import io.grpc.Status;
-import io.grpc.StatusRuntimeException;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
 import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
 import java.io.InputStream;
-import java.util.Arrays;
 
 /** Unit tests for {@link ProtoUtils}. */
 @RunWith(JUnit4.class)
 public class ProtoUtilsTest {
-  private Marshaller<Type> marshaller = ProtoUtils.marshaller(Type.getDefaultInstance());
   private Type proto = Type.newBuilder().setName("name").build();
 
   @Test
-  public void testPassthrough() {
-    assertSame(proto, marshaller.parse(marshaller.stream(proto)));
-  }
-
-  @Test
   public void testRoundtrip() throws Exception {
+    Marshaller<Type> marshaller = ProtoUtils.marshaller(Type.getDefaultInstance());
     InputStream is = marshaller.stream(proto);
     is = new ByteArrayInputStream(ByteStreams.toByteArray(is));
     assertEquals(proto, marshaller.parse(is));
   }
 
   @Test
-  public void testInvalidatedMessage() throws Exception {
+  @Deprecated
+  public void testRoundtripLite() throws Exception {
+    Marshaller<MessageLite> marshaller
+        = ProtoUtils.marshaller((MessageLite) Type.getDefaultInstance());
     InputStream is = marshaller.stream(proto);
-    // Invalidates message, and drains all bytes
-    ByteStreams.toByteArray(is);
-    try {
-      ((ProtoInputStream) is).message();
-      fail("Expected exception");
-    } catch (IllegalStateException ex) {
-      // expected
-    }
-    // Zero bytes is the default message
-    assertEquals(Type.getDefaultInstance(), marshaller.parse(is));
-  }
-
-  @Test
-  public void parseInvalid() throws Exception {
-    InputStream is = new ByteArrayInputStream(new byte[] {-127});
-    try {
-      marshaller.parse(is);
-      fail("Expected exception");
-    } catch (StatusRuntimeException ex) {
-      assertEquals(Status.Code.INTERNAL, ex.getStatus().getCode());
-      assertNotNull(((InvalidProtocolBufferException) ex.getCause()).getUnfinishedMessage());
-    }
-  }
-
-  @Test
-  public void testMismatch() throws Exception {
-    Marshaller<Enum> enumMarshaller = ProtoUtils.marshaller(Enum.getDefaultInstance());
-    // Enum's name and Type's name are both strings with tag 1.
-    Enum altProto = Enum.newBuilder().setName(proto.getName()).build();
-    assertEquals(proto, marshaller.parse(enumMarshaller.stream(altProto)));
-  }
-
-  @Test
-  public void marshallerShouldNotLimitProtoSize() throws Exception {
-    // The default limit is 64MB. Using a larger proto to verify that the limit is not enforced.
-    byte[] bigName = new byte[70 * 1024 * 1024];
-    Arrays.fill(bigName, (byte) 32);
-
-    proto = Type.newBuilder().setNameBytes(ByteString.copyFrom(bigName)).build();
-
-    // Just perform a round trip to verify that it works.
-    testRoundtrip();
-  }
-
-  @Test
-  public void testAvailable() throws Exception {
-    InputStream is = marshaller.stream(proto);
-    assertEquals(proto.getSerializedSize(), is.available());
-    is.read();
-    assertEquals(proto.getSerializedSize() - 1, is.available());
-    while (is.read() != -1) {}
-    assertEquals(-1, is.read());
-    assertEquals(0, is.available());
-  }
-
-  @Test
-  public void testEmpty() throws IOException {
-    Marshaller<Empty> marshaller = ProtoUtils.marshaller(Empty.getDefaultInstance());
-    InputStream is = marshaller.stream(Empty.getDefaultInstance());
-    assertEquals(0, is.available());
-    byte[] b = new byte[10];
-    assertEquals(-1, is.read(b));
-    assertArrayEquals(new byte[10], b);
-    // Do the same thing again, because the internal state may be different
-    assertEquals(-1, is.read(b));
-    assertArrayEquals(new byte[10], b);
-    assertEquals(-1, is.read());
-    assertEquals(0, is.available());
-  }
-
-  @Test
-  public void testDrainTo_all() throws Exception {
-    byte[] golden = ByteStreams.toByteArray(marshaller.stream(proto));
-    InputStream is = marshaller.stream(proto);
-    Drainable d = (Drainable) is;
-    ByteArrayOutputStream baos = new ByteArrayOutputStream();
-    int drained = d.drainTo(baos);
-    assertEquals(baos.size(), drained);
-    assertArrayEquals(golden, baos.toByteArray());
-    assertEquals(0, is.available());
-  }
-
-  @Test
-  public void testDrainTo_partial() throws Exception {
-    final byte[] golden;
-    {
-      InputStream is = marshaller.stream(proto);
-      is.read();
-      golden = ByteStreams.toByteArray(is);
-    }
-    InputStream is = marshaller.stream(proto);
-    is.read();
-    Drainable d = (Drainable) is;
-    ByteArrayOutputStream baos = new ByteArrayOutputStream();
-    int drained = d.drainTo(baos);
-    assertEquals(baos.size(), drained);
-    assertArrayEquals(golden, baos.toByteArray());
-    assertEquals(0, is.available());
-  }
-
-  @Test
-  public void testDrainTo_none() throws Exception {
-    byte[] golden = ByteStreams.toByteArray(marshaller.stream(proto));
-    InputStream is = marshaller.stream(proto);
-    ByteStreams.toByteArray(is);
-    Drainable d = (Drainable) is;
-    ByteArrayOutputStream baos = new ByteArrayOutputStream();
-    assertEquals(0, d.drainTo(baos));
-    assertArrayEquals(new byte[0], baos.toByteArray());
-    assertEquals(0, is.available());
+    is = new ByteArrayInputStream(ByteStreams.toByteArray(is));
+    assertEquals(proto, marshaller.parse(is));
   }
 
   @Test
@@ -198,25 +74,4 @@
     assertEquals("google.protobuf.Type-bin",
         ProtoUtils.keyForProto(Type.getDefaultInstance()).originalName());
   }
-
-  @Test
-  public void keyMarshaller_roundtrip() {
-    Metadata.BinaryMarshaller<Type> keyMarshaller =
-        ProtoUtils.keyMarshaller(Type.getDefaultInstance());
-    assertEquals(proto, keyMarshaller.parseBytes(keyMarshaller.toBytes(proto)));
-  }
-
-  @Test
-  public void keyMarshaller_invalid() {
-    Metadata.BinaryMarshaller<Type> keyMarshaller =
-        ProtoUtils.keyMarshaller(Type.getDefaultInstance());
-    try {
-      keyMarshaller.parseBytes(new byte[] {-127});
-      fail("Expected exception");
-    } catch (IllegalArgumentException ex) {
-      assertNotNull(((InvalidProtocolBufferException) ex.getCause()).getUnfinishedMessage());
-    }
-  }
-
-
 }
diff --git a/settings.gradle b/settings.gradle
index f235a33..9bed7a1 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -4,6 +4,7 @@
 include ":grpc-auth"
 include ":grpc-okhttp"
 include ":grpc-protobuf"
+include ":grpc-protobuf-lite"
 include ":grpc-protobuf-nano"
 include ":grpc-netty"
 include ":grpc-grpclb"
@@ -18,6 +19,7 @@
 project(':grpc-auth').projectDir = "$rootDir/auth" as File
 project(':grpc-okhttp').projectDir = "$rootDir/okhttp" as File
 project(':grpc-protobuf').projectDir = "$rootDir/protobuf" as File
+project(':grpc-protobuf-lite').projectDir = "$rootDir/protobuf-lite" as File
 project(':grpc-protobuf-nano').projectDir = "$rootDir/protobuf-nano" as File
 project(':grpc-netty').projectDir = "$rootDir/netty" as File
 project(':grpc-grpclb').projectDir = "$rootDir/grpclb" as File