Merge remote-tracking branch 'remotes/upstream/master' into kotlinProtos
diff --git a/BUILD b/BUILD
index 07ee629..46d284e 100644
--- a/BUILD
+++ b/BUILD
@@ -401,6 +401,7 @@
         "src/google/protobuf/compiler/java/java_generator.cc",
         "src/google/protobuf/compiler/java/java_generator_factory.cc",
         "src/google/protobuf/compiler/java/java_helpers.cc",
+        "src/google/protobuf/compiler/java/java_kotlin_generator.cc",
         "src/google/protobuf/compiler/java/java_map_field.cc",
         "src/google/protobuf/compiler/java/java_map_field_lite.cc",
         "src/google/protobuf/compiler/java/java_message.cc",
diff --git a/Makefile.am b/Makefile.am
index 9151842..3f94f85 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -530,6 +530,35 @@
   java/core/src/test/proto/com/google/protobuf/test_check_utf8_size.proto          \
   java/core/src/test/proto/com/google/protobuf/test_custom_options.proto           \
   java/core/src/test/proto/com/google/protobuf/wrappers_test.proto                 \
+  java/kotlin/generate-sources-build.xml                                           \
+  java/kotlin/generate-test-sources-build.xml                                      \
+  java/kotlin/pom.xml                                                              \
+  java/kotlin/src/main/kotlin/com/google/protobuf/DslList.kt                       \
+  java/kotlin/src/main/kotlin/com/google/protobuf/DslMap.kt                        \
+  java/kotlin/src/main/kotlin/com/google/protobuf/DslProxy.kt                      \
+  java/kotlin/src/main/kotlin/com/google/protobuf/ExtendableMessageExtensions.kt   \
+  java/kotlin/src/main/kotlin/com/google/protobuf/ExtendableMessageLiteExtensions.kt\
+  java/kotlin/src/main/kotlin/com/google/protobuf/ExtensionList.kt                 \
+  java/kotlin/src/main/kotlin/com/google/protobuf/OnlyForUseByGeneratedProtoCode.kt\
+  java/kotlin/src/main/kotlin/com/google/protobuf/ProtoDslMarker.kt                \
+  java/kotlin/src/main/kotlin/com/google/protobuf/UnmodifiableCollections.kt       \
+  java/kotlin/src/test/kotlin/com/google/protobuf/DslListTest.kt                   \
+  java/kotlin/src/test/kotlin/com/google/protobuf/DslMapTest.kt                    \
+  java/kotlin/src/test/kotlin/com/google/protobuf/ExtendableMessageExtensionsTest.kt\
+  java/kotlin/src/test/kotlin/com/google/protobuf/ExtensionListTest.kt             \
+  java/kotlin/src/test/kotlin/com/google/protobuf/Proto2Test.kt                    \
+  java/kotlin/src/test/kotlin/com/google/protobuf/Proto3Test.kt                    \
+  java/kotlin/src/test/proto/com/google/protobuf/evil_names_proto2.proto           \
+  java/kotlin/src/test/proto/com/google/protobuf/evil_names_proto3.proto           \
+  java/kotlin/src/test/proto/com/google/protobuf/example_extensible_message.proto  \
+  java/kotlin/src/test/proto/com/google/protobuf/multiple_files_proto3.proto       \
+  java/kotlin-lite/generate-sources-build.xml                                      \
+  java/kotlin-lite/generate-test-sources-build.xml                                 \
+  java/kotlin-lite/lite.awk                                                        \
+  java/kotlin-lite/pom.xml                                                         \
+  java/kotlin-lite/process-lite-sources-build.xml                                  \
+  java/kotlin-lite/src/test/kotlin/com/google/protobuf/ExtendableMessageLiteExtensionsTest.kt\
+  java/kotlin-lite/src/test/kotlin/com/google/protobuf/Proto2LiteTest.kt           \
   java/lite.md                                                                     \
   java/lite/BUILD                                                                  \
   java/lite/generate-sources-build.xml                                             \
diff --git a/cmake/extract_includes.bat.in b/cmake/extract_includes.bat.in
index 5c5799e..ad630af 100644
--- a/cmake/extract_includes.bat.in
+++ b/cmake/extract_includes.bat.in
@@ -26,6 +26,7 @@
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\csharp\csharp_names.h" include\google\protobuf\compiler\csharp\csharp_names.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\importer.h" include\google\protobuf\compiler\importer.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\java\java_generator.h" include\google\protobuf\compiler\java\java_generator.h
+copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\java\java_kotlin_generator.h" include\google\protobuf\compiler\java\java_kotlin_generator.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\java\java_names.h" include\google\protobuf\compiler\java\java_names.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\js\js_generator.h" include\google\protobuf\compiler\js\js_generator.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\js\well_known_types_embed.h" include\google\protobuf\compiler\js\well_known_types_embed.h
diff --git a/cmake/libprotoc.cmake b/cmake/libprotoc.cmake
index ecb5a85..6316d83 100644
--- a/cmake/libprotoc.cmake
+++ b/cmake/libprotoc.cmake
@@ -44,6 +44,7 @@
   ${protobuf_source_dir}/src/google/protobuf/compiler/java/java_generator.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/java/java_generator_factory.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/java/java_helpers.cc
+  ${protobuf_source_dir}/src/google/protobuf/compiler/java/java_kotlin_generator.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/java/java_map_field.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/java/java_map_field_lite.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/java/java_message.cc
diff --git a/java/kotlin-lite/generate-sources-build.xml b/java/kotlin-lite/generate-sources-build.xml
new file mode 100644
index 0000000..74d96a2
--- /dev/null
+++ b/java/kotlin-lite/generate-sources-build.xml
@@ -0,0 +1,19 @@
+<project name="generate-sources">
+    <echo message="Running protoc ..."/>
+    <mkdir dir="${generated.sources.dir}"/>
+    <exec executable="${protoc}">
+        <arg value="--java_out=lite:${generated.sources.dir}"/>
+        <arg value="--proto_path=${protobuf.source.dir}"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/any.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/api.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/duration.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/empty.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/field_mask.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/source_context.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/struct.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/timestamp.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/type.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/wrappers.proto"/>
+    </exec>
+</project>
+
diff --git a/java/kotlin-lite/generate-test-sources-build.xml b/java/kotlin-lite/generate-test-sources-build.xml
new file mode 100644
index 0000000..c5f60b8
--- /dev/null
+++ b/java/kotlin-lite/generate-test-sources-build.xml
@@ -0,0 +1,34 @@
+<project name="generate-test-sources">
+    <mkdir dir="${generated.testsources.dir}"/>
+    <exec executable="${protoc}">
+        <arg value="--java_out=lite:${generated.testsources.dir}"/>
+        <arg value="--proto_path=${protobuf.source.dir}"/>
+        <arg value="--proto_path=${protobuf.basedir}/java/kotlin/${test.proto.dir}"/>
+        <arg value="--experimental_allow_proto3_optional"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/map_lite_unittest.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/unittest.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/unittest_import.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/unittest_import_lite.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/unittest_import_public.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/unittest_import_public_lite.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/unittest_lite.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/unittest_proto3.proto"/>
+        <arg value="${protobuf.basedir}/java/kotlin/${test.proto.dir}/com/google/protobuf/evil_names_proto2.proto"/>
+        <arg value="${protobuf.basedir}/java/kotlin/${test.proto.dir}/com/google/protobuf/evil_names_proto3.proto"/>
+        <arg value="${protobuf.basedir}/java/kotlin/${test.proto.dir}/com/google/protobuf/example_extensible_message.proto"/>
+        <arg value="${protobuf.basedir}/java/kotlin/${test.proto.dir}/com/google/protobuf/multiple_files_proto3.proto"/>
+    </exec>
+    <exec executable="${protoc}">
+      <arg value="--kotlin_out=lite:${generated.testsources.dir}"/>
+      <arg value="--proto_path=${protobuf.source.dir}"/>
+      <arg value="--proto_path=${protobuf.basedir}/java/kotlin/${test.proto.dir}"/>
+      <arg value="--experimental_allow_proto3_optional"/>
+      <arg value="${protobuf.source.dir}/google/protobuf/map_lite_unittest.proto"/>
+      <arg value="${protobuf.source.dir}/google/protobuf/unittest_lite.proto"/>
+      <arg value="${protobuf.source.dir}/google/protobuf/unittest_proto3.proto"/>
+      <arg value="${protobuf.basedir}/java/kotlin/${test.proto.dir}/com/google/protobuf/evil_names_proto2.proto"/>
+      <arg value="${protobuf.basedir}/java/kotlin/${test.proto.dir}/com/google/protobuf/evil_names_proto3.proto"/>
+      <arg value="${protobuf.basedir}/java/kotlin/${test.proto.dir}/com/google/protobuf/example_extensible_message.proto"/>
+      <arg value="${protobuf.basedir}/java/kotlin/${test.proto.dir}/com/google/protobuf/multiple_files_proto3.proto"/>
+    </exec>
+</project>
diff --git a/java/kotlin-lite/lite.awk b/java/kotlin-lite/lite.awk
new file mode 100644
index 0000000..b22d965
--- /dev/null
+++ b/java/kotlin-lite/lite.awk
@@ -0,0 +1,25 @@
+# Remove code enclosed by "BEGIN FULL-RUNTIME" and "END FULL-RUNTIME" to
+# create the lite-only version of a test file.
+
+BEGIN {
+  in_full_runtime = 0;
+}
+
+/BEGIN FULL-RUNTIME/ {
+  in_full_runtime = 1;
+  next;
+}
+
+/END FULL-RUNTIME/ {
+  in_full_runtime = 0;
+  next;
+}
+
+in_full_runtime {
+  # Skip full runtime code path.
+  next;
+}
+
+{
+  print;
+}
diff --git a/java/kotlin-lite/pom.xml b/java/kotlin-lite/pom.xml
new file mode 100644
index 0000000..1d75251
--- /dev/null
+++ b/java/kotlin-lite/pom.xml
@@ -0,0 +1,383 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>com.google.protobuf</groupId>
+    <artifactId>protobuf-parent</artifactId>
+    <version>3.15.6</version>
+  </parent>
+
+  <artifactId>protobuf-kotlin-lite</artifactId>
+  <packaging>bundle</packaging>
+
+  <name>Protocol Buffers [Lite]</name>
+  <description>
+    Lite version of Protocol Buffers library. This version is optimized for code size, but does
+    not guarantee API/ABI stability.
+  </description>
+
+  <properties>
+    <kotlin.version>1.4.31</kotlin.version>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.easymock</groupId>
+      <artifactId>easymock</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.easymock</groupId>
+      <artifactId>easymockclassextension</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava-testlib</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.truth</groupId>
+      <artifactId>truth</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.jetbrains.kotlin</groupId>
+      <artifactId>kotlin-stdlib</artifactId>
+      <version>${kotlin.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.jetbrains.kotlin</groupId>
+      <artifactId>kotlin-test</artifactId>
+      <version>${kotlin.version}</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+     <!-- Include core protos in the bundle as resources -->
+     <resources>
+      <resource>
+        <directory>${protobuf.source.dir}</directory>
+        <includes>
+          <include>google/protobuf/any.proto</include>
+          <include>google/protobuf/api.proto</include>
+          <include>google/protobuf/duration.proto</include>
+          <include>google/protobuf/empty.proto</include>
+          <include>google/protobuf/field_mask.proto</include>
+          <include>google/protobuf/source_context.proto</include>
+          <include>google/protobuf/struct.proto</include>
+          <include>google/protobuf/timestamp.proto</include>
+          <include>google/protobuf/type.proto</include>
+          <include>google/protobuf/wrappers.proto</include>
+        </includes>
+      </resource>
+    </resources>
+    <testResources>
+      <testResource>
+        <directory>${protobuf.source.dir}</directory>
+        <includes>
+          <include>google/protobuf/testdata/golden_message_oneof_implemented</include>
+          <include>google/protobuf/testdata/golden_packed_fields_message</include>
+        </includes>
+      </testResource>
+    </testResources>
+
+    <plugins>
+      <plugin>
+        <artifactId>maven-resources-plugin</artifactId>
+        <version>3.1.0</version>
+        <executions>
+          <execution>
+            <id>copy-source-files</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>copy-resources</goal>
+            </goals>
+            <configuration>
+              <outputDirectory>${generated.sources.dir}/com/google/protobuf</outputDirectory>
+              <resources>
+                <resource>
+                  <directory>${basedir}/../core/src/main/java/com/google/protobuf</directory>
+                  <includes>
+                    <!-- Keep in sync with //java/core:BUILD  -->
+                    <include>AbstractMessageLite.java</include>
+                    <include>AbstractParser.java</include>
+                    <include>AbstractProtobufList.java</include>
+                    <include>AllocatedBuffer.java</include>
+                    <include>Android.java</include>
+                    <include>ArrayDecoders.java</include>
+                    <include>BinaryReader.java</include>
+                    <include>BinaryWriter.java</include>
+                    <include>BooleanArrayList.java</include>
+                    <include>BufferAllocator.java</include>
+                    <include>ByteBufferWriter.java</include>
+                    <include>ByteOutput.java</include>
+                    <include>ByteString.java</include>
+                    <include>CodedInputStream.java</include>
+                    <include>CodedInputStreamReader.java</include>
+                    <include>CodedOutputStream.java</include>
+                    <include>CodedOutputStreamWriter.java</include>
+                    <include>DoubleArrayList.java</include>
+                    <include>ExperimentalApi.java</include>
+                    <include>ExtensionLite.java</include>
+                    <include>ExtensionRegistryFactory.java</include>
+                    <include>ExtensionRegistryLite.java</include>
+                    <include>ExtensionSchema.java</include>
+                    <include>ExtensionSchemaLite.java</include>
+                    <include>ExtensionSchemas.java</include>
+                    <include>FieldInfo.java</include>
+                    <include>FieldSet.java</include>
+                    <include>FieldType.java</include>
+                    <include>FloatArrayList.java</include>
+                    <include>GeneratedMessageInfoFactory.java</include>
+                    <include>GeneratedMessageLite.java</include>
+                    <include>IntArrayList.java</include>
+                    <include>Internal.java</include>
+                    <include>InvalidProtocolBufferException.java</include>
+                    <include>IterableByteBufferInputStream.java</include>
+                    <include>JavaType.java</include>
+                    <include>LazyField.java</include>
+                    <include>LazyFieldLite.java</include>
+                    <include>LazyStringArrayList.java</include>
+                    <include>LazyStringList.java</include>
+                    <include>ListFieldSchema.java</include>
+                    <include>LongArrayList.java</include>
+                    <include>ManifestSchemaFactory.java</include>
+                    <include>MapEntryLite.java</include>
+                    <include>MapFieldLite.java</include>
+                    <include>MapFieldSchema.java</include>
+                    <include>MapFieldSchemaLite.java</include>
+                    <include>MapFieldSchemas.java</include>
+                    <include>MessageInfo.java</include>
+                    <include>MessageInfoFactory.java</include>
+                    <include>MessageLite.java</include>
+                    <include>MessageLiteOrBuilder.java</include>
+                    <include>MessageLiteToString.java</include>
+                    <include>MessageSchema.java</include>
+                    <include>MessageSetSchema.java</include>
+                    <include>MutabilityOracle.java</include>
+                    <include>NewInstanceSchema.java</include>
+                    <include>NewInstanceSchemaLite.java</include>
+                    <include>NewInstanceSchemas.java</include>
+                    <include>NioByteString.java</include>
+                    <include>OneofInfo.java</include>
+                    <include>Parser.java</include>
+                    <include>PrimitiveNonBoxingCollection.java</include>
+                    <include>ProtoSyntax.java</include>
+                    <include>Protobuf.java</include>
+                    <include>ProtobufArrayList.java</include>
+                    <include>ProtobufLists.java</include>
+                    <include>ProtocolStringList.java</include>
+                    <include>RawMessageInfo.java</include>
+                    <include>Reader.java</include>
+                    <include>RopeByteString.java</include>
+                    <include>Schema.java</include>
+                    <include>SchemaFactory.java</include>
+                    <include>SchemaUtil.java</include>
+                    <include>SmallSortedMap.java</include>
+                    <include>StructuralMessageInfo.java</include>
+                    <include>TextFormatEscaper.java</include>
+                    <include>UninitializedMessageException.java</include>
+                    <include>UnknownFieldSchema.java</include>
+                    <include>UnknownFieldSetLite.java</include>
+                    <include>UnknownFieldSetLiteSchema.java</include>
+                    <include>UnmodifiableLazyStringList.java</include>
+                    <include>UnsafeUtil.java</include>
+                    <include>Utf8.java</include>
+                    <include>WireFormat.java</include>
+                    <include>Writer.java</include>
+                  </includes>
+                </resource>
+              </resources>
+            </configuration>
+          </execution>
+          <execution>
+            <id>copy-kotlin-source-files</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>copy-resources</goal>
+            </goals>
+            <configuration>
+              <outputDirectory>${generated.sources.dir}/com/google/protobuf</outputDirectory>
+              <resources>
+                <resource>
+                  <directory>${basedir}/../kotlin/src/main/kotlin/com/google/protobuf</directory>
+                  <includes>
+                    <include>DslList.kt</include>
+                    <include>DslMap.kt</include>
+                    <include>DslProxy.kt</include>
+                    <include>ExtendableMessageLiteExtensions.kt</include>
+                    <include>ExtensionList.kt</include>
+                    <include>OnlyForUseByGeneratedProtoCode.kt</include>
+                    <include>ProtoDslMarker.kt</include>
+                    <include>UnmodifiableCollections.kt</include>
+                  </includes>
+                </resource>
+              </resources>
+            </configuration>
+          </execution>
+          <execution>
+            <id>copy-test-source-files</id>
+            <phase>generate-test-sources</phase>
+            <goals>
+              <goal>copy-resources</goal>
+            </goals>
+            <configuration>
+              <outputDirectory>${generated.testsources.dir}/com/google/protobuf</outputDirectory>
+              <resources>
+                <resource>
+                  <directory>${basedir}/../core/src/test/java/com/google/protobuf</directory>
+                  <includes>
+                    <include>TestUtilLite.java</include>
+                  </includes>
+                </resource>
+                <resource>
+                  <directory>${basedir}/../kotlin/src/test/kotlin/com/google/protobuf</directory>
+                  <excludes>
+                    <exclude>ExtendableMessageExtensionsTest.kt</exclude>
+                    <exclude>Proto2Test.kt</exclude>
+                    <exclude>ProtoUtil.java</exclude>
+                  </excludes>
+                </resource>
+              </resources>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <!-- Use Antrun plugin to generate sources with protoc -->
+      <plugin>
+        <artifactId>maven-antrun-plugin</artifactId>
+        <executions>
+          <!-- Generate core protos -->
+          <execution>
+            <id>generate-sources</id>
+            <phase>generate-sources</phase>
+            <configuration>
+              <target>
+                <ant antfile="generate-sources-build.xml"/>
+              </target>
+            </configuration>
+            <goals>
+              <goal>run</goal>
+            </goals>
+          </execution>
+
+          <!-- Generate the test protos -->
+          <execution>
+            <id>generate-test-sources</id>
+            <phase>generate-test-sources</phase>
+            <configuration>
+              <target>
+                <ant antfile="generate-test-sources-build.xml"/>
+              </target>
+            </configuration>
+            <goals>
+              <goal>run</goal>
+            </goals>
+          </execution>
+
+          <execution>
+            <id>process-lite-sources</id>
+            <phase>generate-test-sources</phase>
+            <configuration>
+              <target>
+                <ant antfile="process-lite-sources-build.xml"/>
+              </target>
+            </configuration>
+            <goals>
+              <goal>run</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>build-helper-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>add-generated-sources</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>add-source</goal>
+            </goals>
+            <configuration>
+              <sources>
+                <source>${generated.sources.dir}</source>
+              </sources>
+            </configuration>
+          </execution>
+
+          <execution>
+            <id>add-generated-test-sources</id>
+            <phase>generate-test-sources</phase>
+            <goals>
+              <goal>add-test-source</goal>
+            </goals>
+            <configuration>
+              <sources>
+                <source>${generated.testsources.dir}</source>
+              </sources>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.jetbrains.kotlin</groupId>
+        <artifactId>kotlin-maven-plugin</artifactId>
+        <version>${kotlin.version}</version>
+        <extensions>true</extensions>
+        <executions>
+          <execution>
+            <id>compile</id>
+            <goals> <goal>compile</goal> </goals>
+            <configuration>
+              <sourceDirs>
+                <sourceDir>${generated.sources.dir}</sourceDir>
+                <sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
+              </sourceDirs>
+            </configuration>
+          </execution>
+          <execution>
+            <id>test-compile</id>
+            <goals> <goal>test-compile</goal> </goals>
+            <configuration>
+              <sourceDirs>
+                <sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
+                <sourceDir>${generated.testsources.dir}</sourceDir>
+              </sourceDirs>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <!-- OSGI bundle configuration -->
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Automatic-Module-Name>com.google.protobuf</Automatic-Module-Name> <!-- Java9+ Jigsaw module name -->
+            <Bundle-DocURL>https://developers.google.com/protocol-buffers/</Bundle-DocURL>
+            <Bundle-SymbolicName>com.google.protobuf</Bundle-SymbolicName>
+            <Export-Package>com.google.protobuf;version=${project.version}</Export-Package>
+            <Import-Package>sun.misc;resolution:=optional,*</Import-Package>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/java/kotlin-lite/process-lite-sources-build.xml b/java/kotlin-lite/process-lite-sources-build.xml
new file mode 100644
index 0000000..d49cf3a
--- /dev/null
+++ b/java/kotlin-lite/process-lite-sources-build.xml
@@ -0,0 +1,7 @@
+<project name="process-lite-sources">
+    <exec executable="awk" output="${generated.testsources.dir}/com/google/protobuf/TestUtil.java">
+      <arg value="-f" />
+      <arg value="${basedir}/lite.awk" />
+      <arg value="${basedir}/../core/src/test/java/com/google/protobuf/TestUtil.java" />
+    </exec>
+</project>
diff --git a/java/kotlin-lite/src/test/kotlin/com/google/protobuf/ExtendableMessageLiteExtensionsTest.kt b/java/kotlin-lite/src/test/kotlin/com/google/protobuf/ExtendableMessageLiteExtensionsTest.kt
new file mode 100644
index 0000000..18933d1
--- /dev/null
+++ b/java/kotlin-lite/src/test/kotlin/com/google/protobuf/ExtendableMessageLiteExtensionsTest.kt
@@ -0,0 +1,60 @@
+package com.google.protobuf.kotlin
+
+import com.google.common.truth.Truth.assertThat
+import example_extensible_message.ExampleExtensibleMessage
+import example_extensible_message.ExampleExtensibleMessageOuterClass as TestProto
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ExtendableMessageLiteExtensionsTest {
+  @Test
+  fun setOnBuilder() {
+    val builder = ExampleExtensibleMessage.newBuilder()
+    builder[TestProto.int32Extension] = 5
+    assertThat(builder.build().getExtension(TestProto.int32Extension)).isEqualTo(5)
+  }
+
+  @Test
+  fun getOnBuilder() {
+    val builder = ExampleExtensibleMessage.newBuilder()
+      .setExtension(TestProto.int32Extension, 6)
+    assertThat(builder[TestProto.int32Extension]).isEqualTo(6)
+  }
+
+  @Test
+  fun getOnMessage() {
+    val message = ExampleExtensibleMessage.newBuilder()
+      .setExtension(TestProto.int32Extension, 6)
+      .build()
+    assertThat(message[TestProto.int32Extension]).isEqualTo(6)
+  }
+
+  @Test
+  fun containsPositiveOnMessage() {
+    val message = ExampleExtensibleMessage.newBuilder()
+      .setExtension(TestProto.int32Extension, 6)
+      .build()
+    assertThat(TestProto.int32Extension in message).isTrue()
+  }
+
+  @Test
+  fun containsPositiveOnBuilder() {
+    val builder = ExampleExtensibleMessage.newBuilder()
+      .setExtension(TestProto.int32Extension, 6)
+    assertThat(TestProto.int32Extension in builder).isTrue()
+  }
+
+  @Test
+  fun containsNegativeOnMessage() {
+    val message = ExampleExtensibleMessage.newBuilder().build()
+    assertThat(TestProto.int32Extension in message).isFalse()
+  }
+
+  @Test
+  fun containsNegativeOnBuilder() {
+    val builder = ExampleExtensibleMessage.newBuilder()
+    assertThat(TestProto.int32Extension in builder).isFalse()
+  }
+}
diff --git a/java/kotlin-lite/src/test/kotlin/com/google/protobuf/Proto2LiteTest.kt b/java/kotlin-lite/src/test/kotlin/com/google/protobuf/Proto2LiteTest.kt
new file mode 100644
index 0000000..8726bbf
--- /dev/null
+++ b/java/kotlin-lite/src/test/kotlin/com/google/protobuf/Proto2LiteTest.kt
@@ -0,0 +1,993 @@
+package com.google.protobuf.kotlin
+
+import com.google.common.truth.Truth.assertThat
+import com.google.protobuf.TestAllTypesLiteKt
+import com.google.protobuf.TestAllTypesLiteKt.nestedMessage
+import com.google.protobuf.TestUtilLite
+import com.google.protobuf.TestUtilLite.toBytes
+import com.google.protobuf.UnittestImportLite.ImportEnumLite
+import com.google.protobuf.UnittestImportLite.ImportMessageLite
+import com.google.protobuf.UnittestImportPublicLite.PublicImportMessageLite
+import com.google.protobuf.UnittestLite
+import com.google.protobuf.UnittestLite.ForeignEnumLite
+import com.google.protobuf.UnittestLite.TestAllTypesLite
+import com.google.protobuf.UnittestLite.TestAllTypesLite.NestedEnum
+import com.google.protobuf.UnittestLite.TestEmptyMessageLite
+import com.google.protobuf.UnittestLite.TestEmptyMessageWithExtensionsLite
+import com.google.protobuf.copy
+import com.google.protobuf.foreignMessageLite
+import evil_names_proto2.EvilNamesProto2OuterClass.EvilNamesProto2
+import evil_names_proto2.EvilNamesProto2OuterClass.HardKeywordsAllTypes
+import evil_names_proto2.EvilNamesProto2OuterClass.Interface
+import evil_names_proto2.HardKeywordsAllTypesKt
+import evil_names_proto2.evilNamesProto2
+import evil_names_proto2.hardKeywordsAllTypes
+import evil_names_proto2.interface_
+import com.google.protobuf.optionalGroupExtensionLite
+import com.google.protobuf.repeatedGroupExtensionLite
+import com.google.protobuf.testAllExtensionsLite
+import com.google.protobuf.testAllTypesLite
+import com.google.protobuf.testEmptyMessageLite
+import com.google.protobuf.testEmptyMessageWithExtensionsLite
+import protobuf_unittest.MapLiteUnittest.MapEnumLite
+import protobuf_unittest.MapLiteUnittest.TestMapLite
+import protobuf_unittest.testMapLite
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class Proto2LiteTest {
+  @Test
+  fun testSetters() {
+    assertThat(
+      testAllTypesLite {
+        optionalInt32 = 101
+        optionalInt64 = 102
+        optionalUint32 = 103
+        optionalUint64 = 104
+        optionalSint32 = 105
+        optionalSint64 = 106
+        optionalFixed32 = 107
+        optionalFixed64 = 108
+        optionalSfixed32 = 109
+        optionalSfixed64 = 110
+        optionalFloat = 111.0f
+        optionalDouble = 112.0
+        optionalBool = true
+        optionalString = "115"
+        optionalBytes = toBytes("116")
+        optionalGroup =
+          TestAllTypesLiteKt.optionalGroup { a = 117 }
+        optionalNestedMessage = nestedMessage { bb = 118 }
+        optionalForeignMessage =
+          foreignMessageLite { c = 119 }
+        optionalImportMessage =
+          ImportMessageLite.newBuilder().setD(120).build()
+        optionalPublicImportMessage =
+          PublicImportMessageLite.newBuilder().setE(126).build()
+        optionalLazyMessage = nestedMessage { bb = 127 }
+        optionalNestedEnum = NestedEnum.BAZ
+        optionalForeignEnum = ForeignEnumLite.FOREIGN_LITE_BAZ
+        optionalImportEnum = ImportEnumLite.IMPORT_LITE_BAZ
+        optionalStringPiece = "124"
+        optionalCord = "125"
+        repeatedInt32.add(201)
+        repeatedInt64.add(202)
+        repeatedUint32.add(203)
+        repeatedUint64.add(204)
+        repeatedSint32.add(205)
+        repeatedSint64.add(206)
+        repeatedFixed32.add(207)
+        repeatedFixed64.add(208)
+        repeatedSfixed32.add(209)
+        repeatedSfixed64.add(210)
+        repeatedFloat.add(211f)
+        repeatedDouble.add(212.0)
+        repeatedBool.add(true)
+        repeatedString.add("215")
+        repeatedBytes.add(toBytes("216"))
+        repeatedGroup.add(TestAllTypesLiteKt.repeatedGroup { a = 217 })
+        repeatedNestedMessage.add(nestedMessage { bb = 218 })
+        repeatedForeignMessage.add(
+          foreignMessageLite { c = 219 }
+        )
+        repeatedImportMessage.add(
+          ImportMessageLite.newBuilder().setD(220).build()
+        )
+        repeatedLazyMessage.add(nestedMessage { bb = 227 })
+        repeatedNestedEnum.add(NestedEnum.BAR)
+        repeatedForeignEnum.add(ForeignEnumLite.FOREIGN_LITE_BAR)
+        repeatedImportEnum.add(ImportEnumLite.IMPORT_LITE_BAR)
+        repeatedStringPiece.add("224")
+        repeatedCord.add("225")
+        repeatedInt32 += 301
+        repeatedInt64 += 302
+        repeatedUint32 += 303
+        repeatedUint64 += 304
+        repeatedSint32 += 305
+        repeatedSint64 += 306
+        repeatedFixed32 += 307
+        repeatedFixed64 += 308
+        repeatedSfixed32 += 309
+        repeatedSfixed64 += 310
+        repeatedFloat += 311f
+        repeatedDouble += 312.0
+        repeatedBool += false
+        repeatedString += "315"
+        repeatedBytes += toBytes("316")
+        repeatedGroup += TestAllTypesLiteKt.repeatedGroup { a = 317 }
+        repeatedNestedMessage += nestedMessage { bb = 318 }
+        repeatedForeignMessage +=
+          foreignMessageLite { c = 319 }
+        repeatedImportMessage +=
+          ImportMessageLite.newBuilder().setD(320).build()
+        repeatedLazyMessage +=
+          TestAllTypesLiteKt.nestedMessage { bb = 327 }
+        repeatedNestedEnum += NestedEnum.BAZ
+        repeatedForeignEnum += ForeignEnumLite.FOREIGN_LITE_BAZ
+        repeatedImportEnum += ImportEnumLite.IMPORT_LITE_BAZ
+        repeatedStringPiece += "324"
+        repeatedCord += "325"
+        defaultInt32 = 401
+        defaultInt64 = 402
+        defaultUint32 = 403
+        defaultUint64 = 404
+        defaultSint32 = 405
+        defaultSint64 = 406
+        defaultFixed32 = 407
+        defaultFixed64 = 408
+        defaultSfixed32 = 409
+        defaultSfixed64 = 410
+        defaultFloat = 411f
+        defaultDouble = 412.0
+        defaultBool = false
+        defaultString = "415"
+        defaultBytes = toBytes("416")
+        defaultNestedEnum = NestedEnum.FOO
+        defaultForeignEnum = ForeignEnumLite.FOREIGN_LITE_FOO
+        defaultImportEnum = ImportEnumLite.IMPORT_LITE_FOO
+        defaultStringPiece = "424"
+        defaultCord = "425"
+        oneofUint32 = 601
+        oneofNestedMessage =
+          TestAllTypesLiteKt.nestedMessage { bb = 602 }
+        oneofString = "603"
+        oneofBytes = toBytes("604")
+      }
+    ).isEqualTo(
+      TestUtilLite.getAllLiteSetBuilder().build()
+    )
+  }
+
+  @Test
+  fun testGetters() {
+    testAllTypesLite {
+      optionalInt32 = 101
+      assertThat(optionalInt32).isEqualTo(101)
+      optionalString = "115"
+      assertThat(optionalString).isEqualTo("115")
+      optionalGroup = TestAllTypesLiteKt.optionalGroup { a = 117 }
+      assertThat(optionalGroup).isEqualTo(TestAllTypesLiteKt.optionalGroup { a = 117 })
+      optionalNestedMessage = TestAllTypesLiteKt.nestedMessage { bb = 118 }
+      assertThat(optionalNestedMessage).isEqualTo(TestAllTypesLiteKt.nestedMessage { bb = 118 })
+      optionalNestedEnum = NestedEnum.BAZ
+      assertThat(optionalNestedEnum).isEqualTo(NestedEnum.BAZ)
+      defaultInt32 = 401
+      assertThat(defaultInt32).isEqualTo(401)
+      oneofUint32 = 601
+      assertThat(oneofUint32).isEqualTo(601)
+    }
+  }
+
+  @Test
+  fun testDefaultGetters() {
+    testAllTypesLite {
+      assertThat(defaultInt32).isEqualTo(41)
+      assertThat(defaultString).isEqualTo("hello")
+      assertThat(defaultNestedEnum).isEqualTo(NestedEnum.BAR)
+      assertThat(defaultStringPiece).isEqualTo("abc")
+    }
+  }
+
+  @Test
+  fun testRepeatedGettersAndSetters() {
+    testAllTypesLite {
+      repeatedInt32.addAll(listOf(1, 2))
+      assertThat(repeatedInt32).isEqualTo(listOf(1, 2))
+      repeatedInt32 += listOf(3, 4)
+      assertThat(repeatedInt32).isEqualTo(listOf(1, 2, 3, 4))
+      repeatedInt32[0] = 5
+      assertThat(repeatedInt32).isEqualTo(listOf(5, 2, 3, 4))
+
+      repeatedString.addAll(listOf("1", "2"))
+      assertThat(repeatedString).isEqualTo(listOf("1", "2"))
+      repeatedString += listOf("3", "4")
+      assertThat(repeatedString).isEqualTo(listOf("1", "2", "3", "4"))
+      repeatedString[0] = "5"
+      assertThat(repeatedString).isEqualTo(listOf("5", "2", "3", "4"))
+
+      repeatedGroup.addAll(
+        listOf(
+          TestAllTypesLiteKt.repeatedGroup { a = 1 },
+          TestAllTypesLiteKt.repeatedGroup { a = 2 }
+        )
+      )
+      assertThat(repeatedGroup).isEqualTo(
+        listOf(
+          TestAllTypesLiteKt.repeatedGroup { a = 1 },
+          TestAllTypesLiteKt.repeatedGroup { a = 2 }
+        )
+      )
+      repeatedGroup +=
+        listOf(
+          TestAllTypesLiteKt.repeatedGroup { a = 3 },
+          TestAllTypesLiteKt.repeatedGroup { a = 4 }
+        )
+      assertThat(repeatedGroup).isEqualTo(
+        listOf(
+          TestAllTypesLiteKt.repeatedGroup { a = 1 },
+          TestAllTypesLiteKt.repeatedGroup { a = 2 },
+          TestAllTypesLiteKt.repeatedGroup { a = 3 },
+          TestAllTypesLiteKt.repeatedGroup { a = 4 }
+        )
+      )
+      repeatedGroup[0] = TestAllTypesLiteKt.repeatedGroup { a = 5 }
+      assertThat(repeatedGroup).isEqualTo(
+        listOf(
+          TestAllTypesLiteKt.repeatedGroup { a = 5 },
+          TestAllTypesLiteKt.repeatedGroup { a = 2 },
+          TestAllTypesLiteKt.repeatedGroup { a = 3 },
+          TestAllTypesLiteKt.repeatedGroup { a = 4 }
+        )
+      )
+
+      repeatedNestedMessage.addAll(listOf(nestedMessage { bb = 1 }, nestedMessage { bb = 2 }))
+      assertThat(repeatedNestedMessage).isEqualTo(
+        listOf(
+          nestedMessage { bb = 1 },
+          nestedMessage { bb = 2 }
+        )
+      )
+      repeatedNestedMessage += listOf(nestedMessage { bb = 3 }, nestedMessage { bb = 4 })
+      assertThat(repeatedNestedMessage).isEqualTo(
+        listOf(
+          nestedMessage { bb = 1 },
+          nestedMessage { bb = 2 },
+          nestedMessage { bb = 3 },
+          nestedMessage { bb = 4 }
+        )
+      )
+      repeatedNestedMessage[0] = nestedMessage { bb = 5 }
+      assertThat(repeatedNestedMessage).isEqualTo(
+        listOf(
+          nestedMessage { bb = 5 },
+          nestedMessage { bb = 2 },
+          nestedMessage { bb = 3 },
+          nestedMessage { bb = 4 }
+        )
+      )
+
+      repeatedNestedEnum.addAll(listOf(NestedEnum.FOO, NestedEnum.BAR))
+      assertThat(repeatedNestedEnum).isEqualTo(listOf(NestedEnum.FOO, NestedEnum.BAR))
+      repeatedNestedEnum += listOf(NestedEnum.BAZ, NestedEnum.FOO)
+      assertThat(repeatedNestedEnum).isEqualTo(
+        listOf(NestedEnum.FOO, NestedEnum.BAR, NestedEnum.BAZ, NestedEnum.FOO)
+      )
+      repeatedNestedEnum[0] = NestedEnum.BAR
+      assertThat(repeatedNestedEnum).isEqualTo(
+        listOf(NestedEnum.BAR, NestedEnum.BAR, NestedEnum.BAZ, NestedEnum.FOO)
+      )
+    }
+  }
+
+  @Test
+  fun testHazzers() {
+    testAllTypesLite {
+      optionalInt32 = 101
+      assertThat(hasOptionalInt32()).isTrue()
+      assertThat(hasOptionalString()).isFalse()
+      optionalGroup = TestAllTypesLiteKt.optionalGroup { a = 117 }
+      assertThat(hasOptionalGroup()).isTrue()
+      assertThat(hasOptionalNestedMessage()).isFalse()
+      optionalNestedEnum = NestedEnum.BAZ
+      assertThat(hasOptionalNestedEnum()).isTrue()
+      assertThat(hasDefaultInt32()).isFalse()
+      oneofUint32 = 601
+      assertThat(hasOneofUint32()).isTrue()
+    }
+
+    testAllTypesLite {
+      assertThat(hasOptionalInt32()).isFalse()
+      optionalString = "115"
+      assertThat(hasOptionalString()).isTrue()
+      assertThat(hasOptionalGroup()).isFalse()
+      optionalNestedMessage = TestAllTypesLiteKt.nestedMessage { bb = 118 }
+      assertThat(hasOptionalNestedMessage()).isTrue()
+      assertThat(hasOptionalNestedEnum()).isFalse()
+      defaultInt32 = 401
+      assertThat(hasDefaultInt32()).isTrue()
+      assertThat(hasOneofUint32()).isFalse()
+    }
+  }
+
+  @Test
+  fun testClears() {
+    testAllTypesLite {
+      optionalInt32 = 101
+      clearOptionalInt32()
+      assertThat(hasOptionalInt32()).isFalse()
+
+      optionalString = "115"
+      clearOptionalString()
+      assertThat(hasOptionalString()).isFalse()
+
+      optionalGroup = TestAllTypesLiteKt.optionalGroup { a = 117 }
+      clearOptionalGroup()
+      assertThat(hasOptionalGroup()).isFalse()
+
+      optionalNestedMessage = TestAllTypesLiteKt.nestedMessage { bb = 118 }
+      clearOptionalNestedMessage()
+      assertThat(hasOptionalNestedMessage()).isFalse()
+
+      optionalNestedEnum = NestedEnum.BAZ
+      clearOptionalNestedEnum()
+      assertThat(hasOptionalNestedEnum()).isFalse()
+
+      defaultInt32 = 401
+      clearDefaultInt32()
+      assertThat(hasDefaultInt32()).isFalse()
+
+      oneofUint32 = 601
+      clearOneofUint32()
+      assertThat(hasOneofUint32()).isFalse()
+    }
+  }
+
+  @Test
+  fun testCopy() {
+    val message = testAllTypesLite {
+      optionalInt32 = 101
+      optionalString = "115"
+    }
+    val modifiedMessage = message.copy {
+      optionalInt32 = 201
+    }
+
+    assertThat(message).isEqualTo(
+      TestAllTypesLite.newBuilder()
+        .setOptionalInt32(101)
+        .setOptionalString("115")
+        .build()
+    )
+    assertThat(modifiedMessage).isEqualTo(
+      TestAllTypesLite.newBuilder()
+        .setOptionalInt32(201)
+        .setOptionalString("115")
+        .build()
+    )
+  }
+
+  @Test
+  fun testOneof() {
+    val message = testAllTypesLite {
+      oneofString = "foo"
+      assertThat(oneofFieldCase)
+        .isEqualTo(TestAllTypesLite.OneofFieldCase.ONEOF_STRING)
+      assertThat(oneofString).isEqualTo("foo")
+      clearOneofField()
+      assertThat(hasOneofUint32()).isFalse()
+      assertThat(oneofFieldCase)
+        .isEqualTo(TestAllTypesLite.OneofFieldCase.ONEOFFIELD_NOT_SET)
+      oneofUint32 = 5
+    }
+
+    assertThat(message.getOneofFieldCase())
+      .isEqualTo(TestAllTypesLite.OneofFieldCase.ONEOF_UINT32)
+    assertThat(message.getOneofUint32()).isEqualTo(5)
+  }
+
+  @Test
+  fun testExtensionsSet() {
+    assertThat(
+      testAllExtensionsLite {
+        this[UnittestLite.optionalInt32ExtensionLite] = 101
+        this[UnittestLite.optionalInt64ExtensionLite] = 102L
+        this[UnittestLite.optionalUint32ExtensionLite] = 103
+        this[UnittestLite.optionalUint64ExtensionLite] = 104L
+        this[UnittestLite.optionalSint32ExtensionLite] = 105
+        this[UnittestLite.optionalSint64ExtensionLite] = 106L
+        this[UnittestLite.optionalFixed32ExtensionLite] = 107
+        this[UnittestLite.optionalFixed64ExtensionLite] = 108L
+        this[UnittestLite.optionalSfixed32ExtensionLite] = 109
+        this[UnittestLite.optionalSfixed64ExtensionLite] = 110L
+        this[UnittestLite.optionalFloatExtensionLite] = 111F
+        this[UnittestLite.optionalDoubleExtensionLite] = 112.0
+        this[UnittestLite.optionalBoolExtensionLite] = true
+        this[UnittestLite.optionalStringExtensionLite] = "115"
+        this[UnittestLite.optionalBytesExtensionLite] = toBytes("116")
+        this[UnittestLite.optionalGroupExtensionLite] = optionalGroupExtensionLite { a = 117 }
+        this[UnittestLite.optionalNestedMessageExtensionLite] =
+          TestAllTypesLiteKt.nestedMessage { bb = 118 }
+        this[UnittestLite.optionalForeignMessageExtensionLite] = foreignMessageLite { c = 119 }
+        this[UnittestLite.optionalImportMessageExtensionLite] =
+          ImportMessageLite.newBuilder().setD(120).build()
+        this[UnittestLite.optionalPublicImportMessageExtensionLite] =
+          PublicImportMessageLite.newBuilder().setE(126).build()
+        this[UnittestLite.optionalLazyMessageExtensionLite] =
+          TestAllTypesLiteKt.nestedMessage { bb = 127 }
+        this[UnittestLite.optionalNestedEnumExtensionLite] = NestedEnum.BAZ
+        this[UnittestLite.optionalForeignEnumExtensionLite] = ForeignEnumLite.FOREIGN_LITE_BAZ
+        this[UnittestLite.optionalImportEnumExtensionLite] = ImportEnumLite.IMPORT_LITE_BAZ
+        this[UnittestLite.optionalStringPieceExtensionLite] = "124"
+        this[UnittestLite.optionalCordExtensionLite] = "125"
+        this[UnittestLite.repeatedInt32ExtensionLite].add(201)
+        this[UnittestLite.repeatedInt64ExtensionLite].add(202L)
+        this[UnittestLite.repeatedUint32ExtensionLite].add(203)
+        this[UnittestLite.repeatedUint64ExtensionLite].add(204L)
+        this[UnittestLite.repeatedSint32ExtensionLite].add(205)
+        this[UnittestLite.repeatedSint64ExtensionLite].add(206L)
+        this[UnittestLite.repeatedFixed32ExtensionLite].add(207)
+        this[UnittestLite.repeatedFixed64ExtensionLite].add(208L)
+        this[UnittestLite.repeatedSfixed32ExtensionLite].add(209)
+        this[UnittestLite.repeatedSfixed64ExtensionLite].add(210L)
+        this[UnittestLite.repeatedFloatExtensionLite].add(211F)
+        this[UnittestLite.repeatedDoubleExtensionLite].add(212.0)
+        this[UnittestLite.repeatedBoolExtensionLite].add(true)
+        this[UnittestLite.repeatedStringExtensionLite].add("215")
+        this[UnittestLite.repeatedBytesExtensionLite].add(toBytes("216"))
+        this[UnittestLite.repeatedGroupExtensionLite].add(repeatedGroupExtensionLite { a = 217 })
+        this[UnittestLite.repeatedNestedMessageExtensionLite].add(
+          TestAllTypesLiteKt.nestedMessage { bb = 218 }
+        )
+        this[UnittestLite.repeatedForeignMessageExtensionLite].add(foreignMessageLite { c = 219 })
+        this[UnittestLite.repeatedImportMessageExtensionLite].add(
+          ImportMessageLite.newBuilder().setD(220).build()
+        )
+        this[UnittestLite.repeatedLazyMessageExtensionLite].add(
+          TestAllTypesLiteKt.nestedMessage { bb = 227 }
+        )
+        this[UnittestLite.repeatedNestedEnumExtensionLite].add(NestedEnum.BAR)
+        this[UnittestLite.repeatedForeignEnumExtensionLite].add(ForeignEnumLite.FOREIGN_LITE_BAR)
+        this[UnittestLite.repeatedImportEnumExtensionLite].add(ImportEnumLite.IMPORT_LITE_BAR)
+        this[UnittestLite.repeatedStringPieceExtensionLite].add("224")
+        this[UnittestLite.repeatedCordExtensionLite].add("225")
+        this[UnittestLite.repeatedInt32ExtensionLite].add(301)
+        this[UnittestLite.repeatedInt64ExtensionLite].add(302L)
+        this[UnittestLite.repeatedUint32ExtensionLite].add(303)
+        this[UnittestLite.repeatedUint64ExtensionLite].add(304L)
+        this[UnittestLite.repeatedSint32ExtensionLite].add(305)
+        this[UnittestLite.repeatedSint64ExtensionLite].add(306L)
+        this[UnittestLite.repeatedFixed32ExtensionLite].add(307)
+        this[UnittestLite.repeatedFixed64ExtensionLite].add(308L)
+        this[UnittestLite.repeatedSfixed32ExtensionLite].add(309)
+        this[UnittestLite.repeatedSfixed64ExtensionLite].add(310L)
+        this[UnittestLite.repeatedFloatExtensionLite].add(311F)
+        this[UnittestLite.repeatedDoubleExtensionLite].add(312.0)
+        this[UnittestLite.repeatedBoolExtensionLite].add(false)
+        this[UnittestLite.repeatedStringExtensionLite].add("315")
+        this[UnittestLite.repeatedBytesExtensionLite].add(toBytes("316"))
+        this[UnittestLite.repeatedGroupExtensionLite].add(repeatedGroupExtensionLite { a = 317 })
+        this[UnittestLite.repeatedNestedMessageExtensionLite].add(
+          TestAllTypesLiteKt.nestedMessage { bb = 318 }
+	)
+        this[UnittestLite.repeatedForeignMessageExtensionLite].add(foreignMessageLite { c = 319 })
+        this[UnittestLite.repeatedImportMessageExtensionLite].add(
+          ImportMessageLite.newBuilder().setD(320).build()
+	)
+        this[UnittestLite.repeatedLazyMessageExtensionLite].add(
+          TestAllTypesLiteKt.nestedMessage { bb = 327 }
+	)
+        this[UnittestLite.repeatedNestedEnumExtensionLite].add(NestedEnum.BAZ)
+        this[UnittestLite.repeatedForeignEnumExtensionLite].add(ForeignEnumLite.FOREIGN_LITE_BAZ)
+        this[UnittestLite.repeatedImportEnumExtensionLite].add(ImportEnumLite.IMPORT_LITE_BAZ)
+        this[UnittestLite.repeatedStringPieceExtensionLite].add("324")
+        this[UnittestLite.repeatedCordExtensionLite].add("325")
+        this[UnittestLite.defaultInt32ExtensionLite] = 401
+        this[UnittestLite.defaultInt64ExtensionLite] = 402L
+        this[UnittestLite.defaultUint32ExtensionLite] = 403
+        this[UnittestLite.defaultUint64ExtensionLite] = 404L
+        this[UnittestLite.defaultSint32ExtensionLite] = 405
+        this[UnittestLite.defaultSint64ExtensionLite] = 406L
+        this[UnittestLite.defaultFixed32ExtensionLite] = 407
+        this[UnittestLite.defaultFixed64ExtensionLite] = 408L
+        this[UnittestLite.defaultSfixed32ExtensionLite] = 409
+        this[UnittestLite.defaultSfixed64ExtensionLite] = 410L
+        this[UnittestLite.defaultFloatExtensionLite] = 411F
+        this[UnittestLite.defaultDoubleExtensionLite] = 412.0
+        this[UnittestLite.defaultBoolExtensionLite] = false
+        this[UnittestLite.defaultStringExtensionLite] = "415"
+        this[UnittestLite.defaultBytesExtensionLite] = toBytes("416")
+        this[UnittestLite.defaultNestedEnumExtensionLite] = NestedEnum.FOO
+        this[UnittestLite.defaultForeignEnumExtensionLite] = ForeignEnumLite.FOREIGN_LITE_FOO
+        this[UnittestLite.defaultImportEnumExtensionLite] = ImportEnumLite.IMPORT_LITE_FOO
+        this[UnittestLite.defaultStringPieceExtensionLite] = "424"
+        this[UnittestLite.defaultCordExtensionLite] = "425"
+        this[UnittestLite.oneofUint32ExtensionLite] = 601
+        this[UnittestLite.oneofNestedMessageExtensionLite] =
+          TestAllTypesLiteKt.nestedMessage { bb = 602 }
+        this[UnittestLite.oneofStringExtensionLite] = "603"
+        this[UnittestLite.oneofBytesExtensionLite] = toBytes("604")
+      }
+    ).isEqualTo(
+      TestUtilLite.getAllLiteExtensionsSet()
+    )
+  }
+
+  @Test
+  fun testExtensionGetters() {
+    testAllExtensionsLite {
+      this[UnittestLite.optionalInt32ExtensionLite] = 101
+      assertThat(this[UnittestLite.optionalInt32ExtensionLite]).isEqualTo(101)
+      this[UnittestLite.optionalStringExtensionLite] = "115"
+      assertThat(this[UnittestLite.optionalStringExtensionLite]).isEqualTo("115")
+      this[UnittestLite.optionalGroupExtensionLite] = optionalGroupExtensionLite { a = 117 }
+      assertThat(this[UnittestLite.optionalGroupExtensionLite])
+        .isEqualTo(optionalGroupExtensionLite { a = 117 })
+      this[UnittestLite.optionalNestedMessageExtensionLite] =
+        TestAllTypesLiteKt.nestedMessage { bb = 118 }
+      assertThat(this[UnittestLite.optionalNestedMessageExtensionLite])
+        .isEqualTo(TestAllTypesLiteKt.nestedMessage { bb = 118 })
+      this[UnittestLite.optionalNestedEnumExtensionLite] = NestedEnum.BAZ
+      assertThat(this[UnittestLite.optionalNestedEnumExtensionLite]).isEqualTo(NestedEnum.BAZ)
+      this[UnittestLite.defaultInt32ExtensionLite] = 401
+      assertThat(this[UnittestLite.defaultInt32ExtensionLite]).isEqualTo(401)
+      this[UnittestLite.oneofUint32ExtensionLite] = 601
+      assertThat(this[UnittestLite.oneofUint32ExtensionLite]).isEqualTo(601)
+    }
+  }
+
+  @Test
+  fun testRepeatedExtensionGettersAndSetters() {
+    testAllExtensionsLite {
+      this[UnittestLite.repeatedInt32ExtensionLite].addAll(listOf(1, 2))
+      assertThat(this[UnittestLite.repeatedInt32ExtensionLite]).isEqualTo(listOf(1, 2))
+      this[UnittestLite.repeatedInt32ExtensionLite].addAll(listOf(3, 4))
+      assertThat(this[UnittestLite.repeatedInt32ExtensionLite]).isEqualTo(listOf(1, 2, 3, 4))
+      this[UnittestLite.repeatedInt32ExtensionLite][0] = 5
+      assertThat(this[UnittestLite.repeatedInt32ExtensionLite]).isEqualTo(listOf(5, 2, 3, 4))
+
+      this[UnittestLite.repeatedStringExtensionLite].addAll(listOf("1", "2"))
+      assertThat(this[UnittestLite.repeatedStringExtensionLite]).isEqualTo(listOf("1", "2"))
+      this[UnittestLite.repeatedStringExtensionLite].addAll(listOf("3", "4"))
+      assertThat(this[UnittestLite.repeatedStringExtensionLite])
+        .isEqualTo(listOf("1", "2", "3", "4"))
+      this[UnittestLite.repeatedStringExtensionLite][0] = "5"
+      assertThat(this[UnittestLite.repeatedStringExtensionLite])
+        .isEqualTo(listOf("5", "2", "3", "4"))
+
+      this[UnittestLite.repeatedGroupExtensionLite].addAll(
+        listOf(
+          repeatedGroupExtensionLite { a = 1 },
+          repeatedGroupExtensionLite { a = 2 }
+        )
+      )
+      assertThat(this[UnittestLite.repeatedGroupExtensionLite]).isEqualTo(
+        listOf(
+          repeatedGroupExtensionLite { a = 1 },
+          repeatedGroupExtensionLite { a = 2 }
+        )
+      )
+      this[UnittestLite.repeatedGroupExtensionLite].addAll(
+        listOf(
+          repeatedGroupExtensionLite { a = 3 },
+          repeatedGroupExtensionLite { a = 4 }
+        )
+      )
+      assertThat(this[UnittestLite.repeatedGroupExtensionLite]).isEqualTo(
+        listOf(
+          repeatedGroupExtensionLite { a = 1 },
+          repeatedGroupExtensionLite { a = 2 },
+          repeatedGroupExtensionLite { a = 3 },
+          repeatedGroupExtensionLite { a = 4 }
+        )
+      )
+      this[UnittestLite.repeatedGroupExtensionLite][0] = repeatedGroupExtensionLite { a = 5 }
+      assertThat(this[UnittestLite.repeatedGroupExtensionLite]).isEqualTo(
+        listOf(
+          repeatedGroupExtensionLite { a = 5 },
+          repeatedGroupExtensionLite { a = 2 },
+          repeatedGroupExtensionLite { a = 3 },
+          repeatedGroupExtensionLite { a = 4 }
+        )
+      )
+
+      this[UnittestLite.repeatedNestedMessageExtensionLite].addAll(
+        listOf(nestedMessage { bb = 1 }, nestedMessage { bb = 2 })
+      )
+      assertThat(this[UnittestLite.repeatedNestedMessageExtensionLite]).isEqualTo(
+        listOf(nestedMessage { bb = 1 }, nestedMessage { bb = 2 })
+      )
+      this[UnittestLite.repeatedNestedMessageExtensionLite].addAll(
+        listOf(nestedMessage { bb = 3 }, nestedMessage { bb = 4 })
+      )
+      assertThat(this[UnittestLite.repeatedNestedMessageExtensionLite]).isEqualTo(
+        listOf(
+          nestedMessage { bb = 1 },
+          nestedMessage { bb = 2 },
+          nestedMessage { bb = 3 },
+          nestedMessage { bb = 4 }
+        )
+      )
+      this[UnittestLite.repeatedNestedMessageExtensionLite][0] = nestedMessage { bb = 5 }
+      assertThat(this[UnittestLite.repeatedNestedMessageExtensionLite]).isEqualTo(
+        listOf(
+          nestedMessage { bb = 5 },
+          nestedMessage { bb = 2 },
+          nestedMessage { bb = 3 },
+          nestedMessage { bb = 4 }
+        )
+      )
+
+      this[UnittestLite.repeatedNestedEnumExtensionLite]
+        .addAll(listOf(NestedEnum.FOO, NestedEnum.BAR))
+      assertThat(this[UnittestLite.repeatedNestedEnumExtensionLite])
+        .isEqualTo(listOf(NestedEnum.FOO, NestedEnum.BAR))
+      this[UnittestLite.repeatedNestedEnumExtensionLite].addAll(listOf(NestedEnum.BAZ, NestedEnum.FOO))
+      assertThat(this[UnittestLite.repeatedNestedEnumExtensionLite]).isEqualTo(
+        listOf(NestedEnum.FOO, NestedEnum.BAR, NestedEnum.BAZ, NestedEnum.FOO)
+      )
+      this[UnittestLite.repeatedNestedEnumExtensionLite][0] = NestedEnum.BAR
+      assertThat(this[UnittestLite.repeatedNestedEnumExtensionLite]).isEqualTo(
+        listOf(NestedEnum.BAR, NestedEnum.BAR, NestedEnum.BAZ, NestedEnum.FOO)
+      )
+    }
+  }
+
+  @Test
+  fun testExtensionContains() {
+    testAllExtensionsLite {
+      this[UnittestLite.optionalInt32ExtensionLite] = 101
+      assertThat(contains(UnittestLite.optionalInt32ExtensionLite)).isTrue()
+      assertThat(contains(UnittestLite.optionalStringExtensionLite)).isFalse()
+      this[UnittestLite.optionalGroupExtensionLite] = optionalGroupExtensionLite { a = 117 }
+      assertThat(contains(UnittestLite.optionalGroupExtensionLite)).isTrue()
+      assertThat(contains(UnittestLite.optionalNestedMessageExtensionLite)).isFalse()
+      this[UnittestLite.optionalNestedEnumExtensionLite] = NestedEnum.BAZ
+      assertThat(contains(UnittestLite.optionalNestedEnumExtensionLite)).isTrue()
+      assertThat(contains(UnittestLite.defaultInt32ExtensionLite)).isFalse()
+      this[UnittestLite.oneofUint32ExtensionLite] = 601
+      assertThat(contains(UnittestLite.oneofUint32ExtensionLite)).isTrue()
+    }
+
+    testAllExtensionsLite {
+      assertThat(contains(UnittestLite.optionalInt32ExtensionLite)).isFalse()
+      this[UnittestLite.optionalStringExtensionLite] = "115"
+      assertThat(contains(UnittestLite.optionalStringExtensionLite)).isTrue()
+      assertThat(contains(UnittestLite.optionalGroupExtensionLite)).isFalse()
+      this[UnittestLite.optionalNestedMessageExtensionLite] =
+        TestAllTypesLiteKt.nestedMessage { bb = 118 }
+      assertThat(contains(UnittestLite.optionalNestedMessageExtensionLite)).isTrue()
+      assertThat(contains(UnittestLite.optionalNestedEnumExtensionLite)).isFalse()
+      this[UnittestLite.defaultInt32ExtensionLite] = 401
+      assertThat(contains(UnittestLite.defaultInt32ExtensionLite)).isTrue()
+      assertThat(contains(UnittestLite.oneofUint32ExtensionLite)).isFalse()
+    }
+  }
+
+  @Test
+  fun testExtensionClears() {
+    testAllExtensionsLite {
+      this[UnittestLite.optionalInt32ExtensionLite] = 101
+      clear(UnittestLite.optionalInt32ExtensionLite)
+      assertThat(contains(UnittestLite.optionalInt32ExtensionLite)).isFalse()
+
+      this[UnittestLite.optionalStringExtensionLite] = "115"
+      clear(UnittestLite.optionalStringExtensionLite)
+      assertThat(contains(UnittestLite.optionalStringExtensionLite)).isFalse()
+
+      this[UnittestLite.optionalGroupExtensionLite] = optionalGroupExtensionLite { a = 117 }
+      clear(UnittestLite.optionalGroupExtensionLite)
+      assertThat(contains(UnittestLite.optionalGroupExtensionLite)).isFalse()
+
+      this[UnittestLite.optionalNestedMessageExtensionLite] =
+        TestAllTypesLiteKt.nestedMessage { bb = 118 }
+      clear(UnittestLite.optionalNestedMessageExtensionLite)
+      assertThat(contains(UnittestLite.optionalNestedMessageExtensionLite)).isFalse()
+
+      this[UnittestLite.optionalNestedEnumExtensionLite] = NestedEnum.BAZ
+      clear(UnittestLite.optionalNestedEnumExtensionLite)
+      assertThat(contains(UnittestLite.optionalNestedEnumExtensionLite)).isFalse()
+
+      this[UnittestLite.defaultInt32ExtensionLite] = 401
+      clear(UnittestLite.defaultInt32ExtensionLite)
+      assertThat(contains(UnittestLite.defaultInt32ExtensionLite)).isFalse()
+
+      this[UnittestLite.oneofUint32ExtensionLite] = 601
+      clear(UnittestLite.oneofUint32ExtensionLite)
+      assertThat(contains(UnittestLite.oneofUint32ExtensionLite)).isFalse()
+    }
+  }
+
+  @Test
+  fun testEmptyMessages() {
+    assertThat(
+      testEmptyMessageLite {}
+    ).isEqualTo(
+      TestEmptyMessageLite.newBuilder().build()
+    )
+
+    assertThat(
+      testEmptyMessageWithExtensionsLite {}
+    ).isEqualTo(
+      TestEmptyMessageWithExtensionsLite.newBuilder().build()
+    )
+  }
+
+  @Test
+  fun testMapSetters() {
+    assertThat(
+      testMapLite {
+        mapInt32Int32[1] = 2
+        mapInt64Int64[1L] = 2L
+        mapUint32Uint32[1] = 2
+        mapUint64Uint64[1L] = 2L
+        mapSint32Sint32[1] = 2
+        mapSint64Sint64[1L] = 2L
+        mapFixed32Fixed32[1] = 2
+        mapFixed64Fixed64[1L] = 2L
+        mapSfixed32Sfixed32[1] = 2
+        mapSfixed64Sfixed64[1L] = 2L
+        mapInt32Float[1] = 2F
+        mapInt32Double[1] = 2.0
+        mapBoolBool[true] = true
+        mapStringString["1"] = "2"
+        mapInt32Bytes[1] = toBytes("2")
+        mapInt32Enum[1] = MapEnumLite.MAP_ENUM_FOO_LITE
+        mapInt32ForeignMessage[1] = foreignMessageLite { c = 1 }
+      }
+    ).isEqualTo(
+      TestMapLite.newBuilder()
+        .putMapInt32Int32(1, 2)
+        .putMapInt64Int64(1L, 2L)
+        .putMapUint32Uint32(1, 2)
+        .putMapUint64Uint64(1L, 2L)
+        .putMapSint32Sint32(1, 2)
+        .putMapSint64Sint64(1L, 2L)
+        .putMapFixed32Fixed32(1, 2)
+        .putMapFixed64Fixed64(1L, 2L)
+        .putMapSfixed32Sfixed32(1, 2)
+        .putMapSfixed64Sfixed64(1L, 2L)
+        .putMapInt32Float(1, 2F)
+        .putMapInt32Double(1, 2.0)
+        .putMapBoolBool(true, true)
+        .putMapStringString("1", "2")
+        .putMapInt32Bytes(1, toBytes("2"))
+        .putMapInt32Enum(1, MapEnumLite.MAP_ENUM_FOO_LITE)
+        .putMapInt32ForeignMessage(1, foreignMessageLite { c = 1 })
+        .build()
+    )
+  }
+
+  @Test
+  fun testMapGettersAndSetters() {
+    testMapLite {
+      mapInt32Int32.put(1, 2)
+      assertThat(mapInt32Int32).isEqualTo(mapOf(1 to 2))
+      mapInt32Int32[3] = 4
+      assertThat(mapInt32Int32).isEqualTo(mapOf(1 to 2, 3 to 4))
+      mapInt32Int32.putAll(mapOf(5 to 6, 7 to 8))
+      assertThat(mapInt32Int32).isEqualTo(mapOf(1 to 2, 3 to 4, 5 to 6, 7 to 8))
+
+      mapStringString.put("1", "2")
+      assertThat(mapStringString).isEqualTo(mapOf("1" to "2"))
+      mapStringString["3"] = "4"
+      assertThat(mapStringString).isEqualTo(mapOf("1" to "2", "3" to "4"))
+      mapStringString.putAll(mapOf("5" to "6", "7" to "8"))
+      assertThat(mapStringString).isEqualTo(mapOf("1" to "2", "3" to "4", "5" to "6", "7" to "8"))
+
+      mapInt32Enum.put(1, MapEnumLite.MAP_ENUM_FOO_LITE)
+      assertThat(mapInt32Enum).isEqualTo(mapOf(1 to MapEnumLite.MAP_ENUM_FOO_LITE))
+      mapInt32Enum[2] = MapEnumLite.MAP_ENUM_BAR_LITE
+      assertThat(mapInt32Enum).isEqualTo(
+        mapOf(1 to MapEnumLite.MAP_ENUM_FOO_LITE, 2 to MapEnumLite.MAP_ENUM_BAR_LITE)
+      )
+      mapInt32Enum.putAll(
+        mapOf(3 to MapEnumLite.MAP_ENUM_BAZ_LITE, 4 to MapEnumLite.MAP_ENUM_FOO_LITE)
+      )
+      assertThat(mapInt32Enum).isEqualTo(
+        mapOf(
+          1 to MapEnumLite.MAP_ENUM_FOO_LITE,
+          2 to MapEnumLite.MAP_ENUM_BAR_LITE,
+          3 to MapEnumLite.MAP_ENUM_BAZ_LITE,
+          4 to MapEnumLite.MAP_ENUM_FOO_LITE
+        )
+      )
+
+      mapInt32ForeignMessage.put(1, foreignMessageLite { c = 1 })
+      assertThat(mapInt32ForeignMessage).isEqualTo(mapOf(1 to foreignMessageLite { c = 1 }))
+      mapInt32ForeignMessage[2] = foreignMessageLite { c = 2 }
+      assertThat(mapInt32ForeignMessage).isEqualTo(
+        mapOf(1 to foreignMessageLite { c = 1 }, 2 to foreignMessageLite { c = 2 })
+      )
+      mapInt32ForeignMessage.putAll(
+        mapOf(3 to foreignMessageLite { c = 3 }, 4 to foreignMessageLite { c = 4 })
+      )
+      assertThat(mapInt32ForeignMessage).isEqualTo(
+        mapOf(
+          1 to foreignMessageLite { c = 1 },
+          2 to foreignMessageLite { c = 2 },
+          3 to foreignMessageLite { c = 3 },
+          4 to foreignMessageLite { c = 4 }
+        )
+      )
+    }
+  }
+
+  @Test
+  fun testMapRemove() {
+    testMapLite {
+      mapInt32Int32.putAll(mapOf(1 to 2, 3 to 4))
+      mapInt32Int32.remove(1)
+      assertThat(mapInt32Int32).isEqualTo(mapOf(3 to 4))
+
+      mapStringString.putAll(mapOf("1" to "2", "3" to "4"))
+      mapStringString.remove("1")
+      assertThat(mapStringString).isEqualTo(mapOf("3" to "4"))
+
+      mapInt32Enum.putAll(
+        mapOf(1 to MapEnumLite.MAP_ENUM_FOO_LITE, 2 to MapEnumLite.MAP_ENUM_BAR_LITE)
+      )
+      mapInt32Enum.remove(1)
+      assertThat(mapInt32Enum).isEqualTo(mapOf(2 to MapEnumLite.MAP_ENUM_BAR_LITE))
+
+      mapInt32ForeignMessage.putAll(
+        mapOf(1 to foreignMessageLite { c = 1 }, 2 to foreignMessageLite { c = 2 })
+      )
+      mapInt32ForeignMessage.remove(1)
+      assertThat(mapInt32ForeignMessage).isEqualTo(mapOf(2 to foreignMessageLite { c = 2 }))
+    }
+  }
+
+  @Test
+  fun testMapClear() {
+    testMapLite {
+      mapInt32Int32.putAll(mapOf(1 to 2, 3 to 4))
+      mapInt32Int32.clear()
+      assertThat(mapInt32Int32.isEmpty()).isTrue()
+
+      mapStringString.putAll(mapOf("1" to "2", "3" to "4"))
+      mapStringString.clear()
+      assertThat(mapStringString.isEmpty()).isTrue()
+
+      mapInt32Enum.putAll(
+        mapOf(1 to MapEnumLite.MAP_ENUM_FOO_LITE, 2 to MapEnumLite.MAP_ENUM_BAR_LITE)
+      )
+      mapInt32Enum.clear()
+      assertThat(mapInt32Enum.isEmpty()).isTrue()
+
+      mapInt32ForeignMessage.putAll(
+        mapOf(1 to foreignMessageLite { c = 1 }, 2 to foreignMessageLite { c = 2 })
+      )
+      mapInt32ForeignMessage.clear()
+      assertThat(mapInt32ForeignMessage.isEmpty()).isTrue()
+    }
+  }
+
+  @Test
+  fun testEvilNames() {
+    assertThat(
+      evilNamesProto2 {
+        initialized = true
+        hasFoo = true
+        bar = "foo"
+        isInitialized = true
+        fooBar = "foo"
+        aLLCAPS += "foo"
+        aLLCAPSMAP[1] = true
+        hasUnderbarPrecedingNumeric1Foo = true
+        hasUnderbarPrecedingNumeric42Bar = true
+        hasUnderbarPrecedingNumeric123Foo42BarBaz = true
+        extension += "foo"
+        class_ += 1
+        int = 1.0
+        long = true
+        boolean = 1L
+        sealed = "foo"
+        interface_ = 1F
+        in_ = 1
+        object_ = "foo"
+        cachedSize_ = "foo"
+        serializedSize_ = true
+        by = "foo"
+      }
+    ).isEqualTo(
+      EvilNamesProto2.newBuilder()
+        .setInitialized(true)
+        .setHasFoo(true)
+        .setBar("foo")
+        .setIsInitialized(true)
+        .setFooBar("foo")
+        .addALLCAPS("foo")
+        .putALLCAPSMAP(1, true)
+        .setHasUnderbarPrecedingNumeric1Foo(true)
+        .setHasUnderbarPrecedingNumeric42Bar(true)
+        .setHasUnderbarPrecedingNumeric123Foo42BarBaz(true)
+        .addExtension("foo")
+        .addClass_(1)
+        .setInt(1.0)
+        .setLong(true)
+        .setBoolean(1L)
+        .setSealed("foo")
+        .setInterface(1F)
+        .setIn(1)
+        .setObject("foo")
+        .setCachedSize_("foo")
+        .setSerializedSize_(true)
+        .setBy("foo")
+        .build()
+    )
+
+    assertThat(interface_ {}).isEqualTo(Interface.newBuilder().build())
+  }
+
+  @Test
+  fun testHardKeywordGettersAndSetters() {
+    hardKeywordsAllTypes {
+      as_ = 1
+      assertThat(as_).isEqualTo(1)
+
+      in_ = "foo"
+      assertThat(in_).isEqualTo("foo")
+
+      break_ = HardKeywordsAllTypes.NestedEnum.FOO
+      assertThat(break_).isEqualTo(HardKeywordsAllTypes.NestedEnum.FOO)
+
+      do_ = HardKeywordsAllTypesKt.nestedMessage { while_ = 1 }
+      assertThat(do_).isEqualTo(HardKeywordsAllTypesKt.nestedMessage { while_ = 1 })
+
+      continue_[1] = 1
+      assertThat(continue_[1]).isEqualTo(1)
+
+      else_ += 1
+      assertThat(else_).isEqualTo(listOf(1))
+
+      for_ += "foo"
+      assertThat(for_).isEqualTo(listOf("foo"))
+
+      fun_ += HardKeywordsAllTypes.NestedEnum.FOO
+      assertThat(fun_).isEqualTo(listOf(HardKeywordsAllTypes.NestedEnum.FOO))
+
+      if_ += HardKeywordsAllTypesKt.nestedMessage { while_ = 1 }
+      assertThat(if_).isEqualTo(listOf(HardKeywordsAllTypesKt.nestedMessage { while_ = 1 }))
+    }
+  }
+
+  @Test
+  fun testHardKeywordHazzers() {
+    hardKeywordsAllTypes {
+      as_ = 1
+      assertThat(hasAs_()).isTrue()
+
+      in_ = "foo"
+      assertThat(hasIn_()).isTrue()
+
+      break_ = HardKeywordsAllTypes.NestedEnum.FOO
+      assertThat(hasBreak_()).isTrue()
+
+      do_ = HardKeywordsAllTypesKt.nestedMessage { while_ = 1 }
+      assertThat(hasDo_()).isTrue()
+    }
+  }
+
+  @Test
+  fun testHardKeywordClears() {
+    hardKeywordsAllTypes {
+      as_ = 1
+      clearAs_()
+      assertThat(hasAs_()).isFalse()
+
+      in_ = "foo"
+      clearIn_()
+      assertThat(hasIn_()).isFalse()
+
+      break_ = HardKeywordsAllTypes.NestedEnum.FOO
+      clearBreak_()
+      assertThat(hasBreak_()).isFalse()
+
+      do_ = HardKeywordsAllTypesKt.nestedMessage { while_ = 1 }
+      clearDo_()
+      assertThat(hasDo_()).isFalse()
+    }
+  }
+}
diff --git a/java/kotlin/generate-sources-build.xml b/java/kotlin/generate-sources-build.xml
new file mode 100644
index 0000000..4e7be23
--- /dev/null
+++ b/java/kotlin/generate-sources-build.xml
@@ -0,0 +1,20 @@
+<project name="generate-sources">
+    <echo message="Running protoc ..."/>
+    <mkdir dir="${generated.sources.dir}"/>
+    <exec executable="${protoc}">
+        <arg value="--java_out=${generated.sources.dir}"/>
+        <arg value="--proto_path=${protobuf.source.dir}"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/any.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/api.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/descriptor.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/duration.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/empty.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/field_mask.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/source_context.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/struct.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/timestamp.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/type.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/wrappers.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/compiler/plugin.proto"/>
+    </exec>
+</project>
diff --git a/java/kotlin/generate-test-sources-build.xml b/java/kotlin/generate-test-sources-build.xml
new file mode 100644
index 0000000..dca1a05
--- /dev/null
+++ b/java/kotlin/generate-test-sources-build.xml
@@ -0,0 +1,34 @@
+<project name="generate-test-sources">
+    <mkdir dir="${generated.testsources.dir}"/>
+    <exec executable="${protoc}">
+        <arg value="--java_out=${generated.testsources.dir}"/>
+        <arg value="--proto_path=${protobuf.source.dir}"/>
+        <arg value="--proto_path=${test.proto.dir}"/>
+        <arg value="--experimental_allow_proto3_optional"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/map_proto2_unittest.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/unittest.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/unittest_import.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/unittest_import_lite.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/unittest_import_public.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/unittest_import_public_lite.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/unittest_lite.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/unittest_proto3.proto"/>
+        <arg value="${test.proto.dir}/com/google/protobuf/evil_names_proto2.proto"/>
+        <arg value="${test.proto.dir}/com/google/protobuf/evil_names_proto3.proto"/>
+        <arg value="${test.proto.dir}/com/google/protobuf/example_extensible_message.proto"/>
+        <arg value="${test.proto.dir}/com/google/protobuf/multiple_files_proto3.proto"/>
+    </exec>
+    <exec executable="${protoc}">
+      <arg value="--kotlin_out=${generated.testsources.dir}"/>
+      <arg value="--proto_path=${protobuf.source.dir}"/>
+      <arg value="--proto_path=${test.proto.dir}"/>
+      <arg value="--experimental_allow_proto3_optional"/>
+      <arg value="${protobuf.source.dir}/google/protobuf/map_proto2_unittest.proto"/>
+      <arg value="${protobuf.source.dir}/google/protobuf/unittest.proto"/>
+      <arg value="${protobuf.source.dir}/google/protobuf/unittest_proto3.proto"/>
+      <arg value="${test.proto.dir}/com/google/protobuf/evil_names_proto2.proto"/>
+      <arg value="${test.proto.dir}/com/google/protobuf/evil_names_proto3.proto"/>
+      <arg value="${test.proto.dir}/com/google/protobuf/multiple_files_proto3.proto"/>
+    </exec>
+
+</project>
diff --git a/java/kotlin/pom.xml b/java/kotlin/pom.xml
new file mode 100644
index 0000000..8de8aca
--- /dev/null
+++ b/java/kotlin/pom.xml
@@ -0,0 +1,251 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>com.google.protobuf</groupId>
+    <artifactId>protobuf-parent</artifactId>
+    <version>3.15.6</version>
+  </parent>
+
+  <artifactId>protobuf-kotlin</artifactId>
+  <packaging>bundle</packaging>
+
+  <name>Protocol Buffers [Core]</name>
+  <description>
+    Core Protocol Buffers library. Protocol Buffers are a way of encoding structured data in an
+    efficient yet extensible format.
+  </description>
+
+  <properties>
+    <kotlin.version>1.4.31</kotlin.version>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.easymock</groupId>
+      <artifactId>easymock</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.easymock</groupId>
+      <artifactId>easymockclassextension</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava-testlib</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.truth</groupId>
+      <artifactId>truth</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.jetbrains.kotlin</groupId>
+      <artifactId>kotlin-stdlib</artifactId>
+      <version>${kotlin.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.jetbrains.kotlin</groupId>
+      <artifactId>kotlin-test</artifactId>
+      <version>${kotlin.version}</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <!-- Include core protos in the bundle as resources -->
+    <resources>
+      <resource>
+        <directory>${protobuf.source.dir}</directory>
+        <includes>
+          <include>google/protobuf/any.proto</include>
+          <include>google/protobuf/api.proto</include>
+          <include>google/protobuf/descriptor.proto</include>
+          <include>google/protobuf/duration.proto</include>
+          <include>google/protobuf/empty.proto</include>
+          <include>google/protobuf/field_mask.proto</include>
+          <include>google/protobuf/source_context.proto</include>
+          <include>google/protobuf/struct.proto</include>
+          <include>google/protobuf/timestamp.proto</include>
+          <include>google/protobuf/type.proto</include>
+          <include>google/protobuf/wrappers.proto</include>
+          <include>google/protobuf/compiler/plugin.proto</include>
+        </includes>
+      </resource>
+    </resources>
+    <testResources>
+      <testResource>
+        <directory>${protobuf.source.dir}</directory>
+        <includes>
+          <include>google/protobuf/testdata/golden_message_oneof_implemented</include>
+          <include>google/protobuf/testdata/golden_packed_fields_message</include>
+        </includes>
+      </testResource>
+    </testResources>
+
+    <plugins>
+      <plugin>
+        <artifactId>maven-resources-plugin</artifactId>
+        <version>3.1.0</version>
+        <executions>
+          <execution>
+            <id>copy-source-files</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>copy-resources</goal>
+            </goals>
+            <configuration>
+              <outputDirectory>${generated.sources.dir}/com/google/protobuf</outputDirectory>
+              <resources>
+                <resource>
+                  <directory>${basedir}/../core/src/main/java/com/google/protobuf</directory>
+                </resource>
+              </resources>
+            </configuration>
+          </execution>
+          <execution>
+            <id>copy-test-source-files</id>
+            <phase>generate-test-sources</phase>
+            <goals>
+              <goal>copy-resources</goal>
+            </goals>
+            <configuration>
+              <outputDirectory>${generated.testsources.dir}/com/google/protobuf</outputDirectory>
+              <resources>
+                <resource>
+                  <directory>${basedir}/../core/src/test/java/com/google/protobuf</directory>
+                  <includes>
+                    <include>TestUtil.java</include>
+                    <include>TestUtilLite.java</include>
+                  </includes>
+                </resource>
+              </resources>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <!-- Use Antrun plugin to generate sources with protoc -->
+      <plugin>
+        <artifactId>maven-antrun-plugin</artifactId>
+        <executions>
+          <!-- Generate core protos -->
+          <execution>
+            <id>generate-sources</id>
+            <phase>generate-sources</phase>
+            <configuration>
+              <target>
+                <ant antfile="generate-sources-build.xml"/>
+              </target>
+            </configuration>
+            <goals>
+              <goal>run</goal>
+            </goals>
+          </execution>
+
+          <!-- Generate the test protos -->
+          <execution>
+            <id>generate-test-sources</id>
+            <phase>generate-test-sources</phase>
+            <configuration>
+              <target>
+                <ant antfile="generate-test-sources-build.xml"/>
+              </target>
+            </configuration>
+            <goals>
+              <goal>run</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+      <!-- Add the generated sources to the build -->
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>build-helper-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>add-generated-sources</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>add-source</goal>
+            </goals>
+            <configuration>
+              <sources>
+                <source>${generated.sources.dir}</source>
+              </sources>
+            </configuration>
+          </execution>
+          <execution>
+            <id>add-generated-test-sources</id>
+            <phase>generate-test-sources</phase>
+            <goals>
+              <goal>add-test-source</goal>
+            </goals>
+            <configuration>
+              <sources>
+                <source>${generated.testsources.dir}</source>
+              </sources>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.jetbrains.kotlin</groupId>
+        <artifactId>kotlin-maven-plugin</artifactId>
+        <version>${kotlin.version}</version>
+        <extensions>true</extensions>
+        <executions>
+          <execution>
+            <id>compile</id>
+            <goals> <goal>compile</goal> </goals>
+            <configuration>
+              <sourceDirs>
+                <sourceDir>${generated.sources.dir}</sourceDir>
+                <sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
+              </sourceDirs>
+            </configuration>
+          </execution>
+          <execution>
+            <id>test-compile</id>
+            <goals> <goal>test-compile</goal> </goals>
+            <configuration>
+              <sourceDirs>
+                <sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
+                <sourceDir>${generated.testsources.dir}</sourceDir>
+              </sourceDirs>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <!-- OSGI bundle configuration -->
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Automatic-Module-Name>com.google.protobuf</Automatic-Module-Name> <!-- Java9+ Jigsaw module name -->
+            <Bundle-DocURL>https://developers.google.com/protocol-buffers/</Bundle-DocURL>
+            <Bundle-SymbolicName>com.google.protobuf</Bundle-SymbolicName>
+            <Export-Package>com.google.protobuf;version=${project.version}</Export-Package>
+            <Import-Package>sun.misc;resolution:=optional,*</Import-Package>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
diff --git a/java/kotlin/src/main/kotlin/com/google/protobuf/DslList.kt b/java/kotlin/src/main/kotlin/com/google/protobuf/DslList.kt
new file mode 100644
index 0000000..54a64af
--- /dev/null
+++ b/java/kotlin/src/main/kotlin/com/google/protobuf/DslList.kt
@@ -0,0 +1,27 @@
+package com.google.protobuf.kotlin
+
+/**
+ * A simple wrapper around a [List] with an extra generic parameter that can be used to disambiguate
+ * extension methods.
+ *
+ * <p>This class is used by Kotlin protocol buffer extensions, and its constructor is public only
+ * because generated message code is in a different compilation unit.  Others should not use this
+ * class directly in any way.
+ */
+@Suppress("unused") // the unused type parameter
+class DslList<E, P : DslProxy> @OnlyForUseByGeneratedProtoCode constructor(
+  private val delegate: List<E>
+) : List<E> by delegate {
+  override fun iterator(): Iterator<E> = UnmodifiableIterator(delegate.iterator())
+
+  override fun listIterator(): ListIterator<E> = UnmodifiableListIterator(delegate.listIterator())
+
+  override fun listIterator(index: Int): ListIterator<E> =
+    UnmodifiableListIterator(delegate.listIterator(index))
+
+  override fun equals(other: Any?): Boolean = delegate == other
+
+  override fun hashCode(): Int = delegate.hashCode()
+
+  override fun toString(): String = delegate.toString()
+}
diff --git a/java/kotlin/src/main/kotlin/com/google/protobuf/DslMap.kt b/java/kotlin/src/main/kotlin/com/google/protobuf/DslMap.kt
new file mode 100644
index 0000000..9949fb4
--- /dev/null
+++ b/java/kotlin/src/main/kotlin/com/google/protobuf/DslMap.kt
@@ -0,0 +1,30 @@
+package com.google.protobuf.kotlin
+
+/**
+ * A simple wrapper around a [Map] with an extra generic parameter that can be used to disambiguate
+ * extension methods.
+ *
+ * <p>This class is used by Kotlin protocol buffer extensions, and its constructor is public only
+ * because generated message code is in a different compilation unit.  Others should not use this
+ * class directly in any way.
+ */
+@Suppress("unused") // the unused type parameter
+class DslMap<K, V, P : DslProxy> @OnlyForUseByGeneratedProtoCode constructor(
+  private val delegate: Map<K, V>
+) : Map<K, V> by delegate {
+  // We allocate the wrappers on calls to get, not with lazy {...}, because lazy allocates
+  // a few objects up front, and any kind of query operation on this object should be rare.
+
+  override val entries: Set<Map.Entry<K, V>>
+    get() = UnmodifiableMapEntries(delegate.entries)
+  override val keys: Set<K>
+    get() = UnmodifiableSet(delegate.keys)
+  override val values: Collection<V>
+    get() = UnmodifiableCollection(delegate.values)
+
+  override fun equals(other: Any?): Boolean = delegate == other
+
+  override fun hashCode(): Int = delegate.hashCode()
+
+  override fun toString(): String = delegate.toString()
+}
diff --git a/java/kotlin/src/main/kotlin/com/google/protobuf/DslProxy.kt b/java/kotlin/src/main/kotlin/com/google/protobuf/DslProxy.kt
new file mode 100644
index 0000000..7d14e95
--- /dev/null
+++ b/java/kotlin/src/main/kotlin/com/google/protobuf/DslProxy.kt
@@ -0,0 +1,12 @@
+package com.google.protobuf.kotlin
+
+/**
+ * A type meaningful only for its existence, never intended to be instantiated.  For example,
+ * a `DslList<Int, FooProxy>` can be given different extension methods than a
+ * `DslList<Int, BarProxy>`.
+ */
+abstract class DslProxy @OnlyForUseByGeneratedProtoCode protected constructor() {
+  init {
+    throw UnsupportedOperationException("A DslProxy should never be instantiated")
+  }
+}
diff --git a/java/kotlin/src/main/kotlin/com/google/protobuf/ExtendableMessageExtensions.kt b/java/kotlin/src/main/kotlin/com/google/protobuf/ExtendableMessageExtensions.kt
new file mode 100644
index 0000000..d1f04a8
--- /dev/null
+++ b/java/kotlin/src/main/kotlin/com/google/protobuf/ExtendableMessageExtensions.kt
@@ -0,0 +1,26 @@
+package com.google.protobuf.kotlin
+
+import com.google.protobuf.ExtensionLite
+import com.google.protobuf.GeneratedMessageV3
+
+/** Sets the current value of the proto extension in this builder.*/
+operator fun <
+  M : GeneratedMessageV3.ExtendableMessage<M>,
+  B : GeneratedMessageV3.ExtendableBuilder<M, B>,
+  T
+  > B.set(extension: ExtensionLite<M, T>, value: T) {
+  setExtension(extension, value)
+}
+
+/** Gets the current value of the proto extension. */
+operator fun <
+  M : GeneratedMessageV3.ExtendableMessage<M>,
+  MorBT : GeneratedMessageV3.ExtendableMessageOrBuilder<M>,
+  T
+  > MorBT.get(extension: ExtensionLite<M, T>): T = getExtension(extension)
+
+/** Returns true if the specified extension is set on this builder. */
+operator fun <
+  M : GeneratedMessageV3.ExtendableMessage<M>,
+  MorBT : GeneratedMessageV3.ExtendableMessageOrBuilder<M>
+  > MorBT.contains(extension: ExtensionLite<M, *>): Boolean = hasExtension(extension)
diff --git a/java/kotlin/src/main/kotlin/com/google/protobuf/ExtendableMessageLiteExtensions.kt b/java/kotlin/src/main/kotlin/com/google/protobuf/ExtendableMessageLiteExtensions.kt
new file mode 100644
index 0000000..8bd8ed3
--- /dev/null
+++ b/java/kotlin/src/main/kotlin/com/google/protobuf/ExtendableMessageLiteExtensions.kt
@@ -0,0 +1,28 @@
+package com.google.protobuf.kotlin
+
+import com.google.protobuf.ExtensionLite
+import com.google.protobuf.GeneratedMessageLite
+
+/** Gets the value of the proto extension. */
+operator fun <
+  M : GeneratedMessageLite.ExtendableMessage<M, *>,
+  MOrBT : GeneratedMessageLite.ExtendableMessageOrBuilder<M, *>,
+  T
+  > MOrBT.get(extension: ExtensionLite<M, T>): T = getExtension(extension)
+
+/** Sets the current value of the proto extension in this builder. */
+operator fun <
+  M : GeneratedMessageLite.ExtendableMessage<M, B>,
+  B : GeneratedMessageLite.ExtendableBuilder<M, B>,
+  T
+  > B.set(extension: ExtensionLite<M, T>, value: T) {
+  setExtension(extension, value)
+}
+
+/** Returns true if the specified extension is set. */
+operator fun <
+  M : GeneratedMessageLite.ExtendableMessage<M, *>,
+  MorBT : GeneratedMessageLite.ExtendableMessageOrBuilder<M, *>
+  > MorBT.contains(
+  extension: ExtensionLite<M, *>
+): Boolean = hasExtension(extension)
diff --git a/java/kotlin/src/main/kotlin/com/google/protobuf/ExtensionList.kt b/java/kotlin/src/main/kotlin/com/google/protobuf/ExtensionList.kt
new file mode 100644
index 0000000..03764e1
--- /dev/null
+++ b/java/kotlin/src/main/kotlin/com/google/protobuf/ExtensionList.kt
@@ -0,0 +1,27 @@
+package com.google.protobuf.kotlin
+
+import com.google.protobuf.ExtensionLite
+import com.google.protobuf.MessageLite
+
+/**
+ * Implementation for ExtensionList and ExtensionListLite.  Like [DslList], represents an
+ * unmodifiable view of a repeated proto field -- in this case, an extension field -- but
+ * supports querying the extension.
+ */
+class ExtensionList<E, M : MessageLite> @OnlyForUseByGeneratedProtoCode constructor(
+  val extension: ExtensionLite<M, List<E>>,
+  @JvmField private val delegate: List<E>
+) : List<E> by delegate {
+  override fun iterator(): Iterator<E> = UnmodifiableIterator(delegate.iterator())
+
+  override fun listIterator(): ListIterator<E> = UnmodifiableListIterator(delegate.listIterator())
+
+  override fun listIterator(index: Int): ListIterator<E> =
+    UnmodifiableListIterator(delegate.listIterator(index))
+
+  override fun equals(other: Any?): Boolean = delegate == other
+
+  override fun hashCode(): Int = delegate.hashCode()
+
+  override fun toString(): String = delegate.toString()
+}
diff --git a/java/kotlin/src/main/kotlin/com/google/protobuf/OnlyForUseByGeneratedProtoCode.kt b/java/kotlin/src/main/kotlin/com/google/protobuf/OnlyForUseByGeneratedProtoCode.kt
new file mode 100644
index 0000000..368912e
--- /dev/null
+++ b/java/kotlin/src/main/kotlin/com/google/protobuf/OnlyForUseByGeneratedProtoCode.kt
@@ -0,0 +1,18 @@
+package com.google.protobuf.kotlin
+
+/**
+ * Opt-in annotation to make it difficult to accidentally use APIs only intended for use by proto
+ * generated code.  See https://kotlinlang.org/docs/reference/opt-in-requirements.html for details
+ * on how this API works.
+ */
+@RequiresOptIn(
+  message =
+    """
+    This API is only intended for use by generated protobuf code, the code generator, and their own
+    tests.  If this does not describe your code, you should not be using this API.
+  """,
+  level = RequiresOptIn.Level.ERROR
+)
+@Retention(AnnotationRetention.BINARY)
+@Target(AnnotationTarget.CONSTRUCTOR, AnnotationTarget.ANNOTATION_CLASS)
+annotation class OnlyForUseByGeneratedProtoCode
diff --git a/java/kotlin/src/main/kotlin/com/google/protobuf/ProtoDslMarker.kt b/java/kotlin/src/main/kotlin/com/google/protobuf/ProtoDslMarker.kt
new file mode 100644
index 0000000..c51ba35
--- /dev/null
+++ b/java/kotlin/src/main/kotlin/com/google/protobuf/ProtoDslMarker.kt
@@ -0,0 +1,10 @@
+package com.google.protobuf.kotlin
+
+/**
+ * Indicates an API that is part of a DSL to generate protocol buffer messages.
+ */
+@DslMarker
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.BINARY)
+@OnlyForUseByGeneratedProtoCode
+annotation class ProtoDslMarker
diff --git a/java/kotlin/src/main/kotlin/com/google/protobuf/UnmodifiableCollections.kt b/java/kotlin/src/main/kotlin/com/google/protobuf/UnmodifiableCollections.kt
new file mode 100644
index 0000000..6af93e1
--- /dev/null
+++ b/java/kotlin/src/main/kotlin/com/google/protobuf/UnmodifiableCollections.kt
@@ -0,0 +1,39 @@
+package com.google.protobuf.kotlin
+
+/** Wraps an [Iterator] and makes it unmodifiable even from Java. */
+internal class UnmodifiableIterator<E>(delegate: Iterator<E>) : Iterator<E> by delegate
+
+/** Wraps a [ListIterator] and makes it unmodifiable even from Java. */
+internal class UnmodifiableListIterator<E>(
+  delegate: ListIterator<E>
+) : ListIterator<E> by delegate
+
+/** Wraps a [Collection] and makes it unmodifiable even from Java. */
+internal open class UnmodifiableCollection<E>(
+  private val delegate: Collection<E>
+) : Collection<E> by delegate {
+  override fun iterator(): Iterator<E> = UnmodifiableIterator(delegate.iterator())
+}
+
+/** Wraps a [Set] and makes it unmodifiable even from Java. */
+internal class UnmodifiableSet<E>(
+  delegate: Collection<E>
+) : UnmodifiableCollection<E>(delegate), Set<E>
+
+/** Wraps a [Map.Entry] and makes it unmodifiable even from Java. */
+internal class UnmodifiableMapEntry<K, V>(delegate: Map.Entry<K, V>) : Map.Entry<K, V> by delegate
+
+/** Wraps a [Set] of map entries and makes it unmodifiable even from Java. */
+internal class UnmodifiableMapEntries<K, V>(
+  private val delegate: Set<Map.Entry<K, V>>
+) : UnmodifiableCollection<Map.Entry<K, V>>(delegate), Set<Map.Entry<K, V>> {
+
+  // Is this overkill? Probably.
+
+  override fun iterator(): Iterator<Map.Entry<K, V>> {
+    val itr = delegate.iterator()
+    return object : Iterator<Map.Entry<K, V>> by itr {
+      override fun next(): Map.Entry<K, V> = UnmodifiableMapEntry(itr.next())
+    }
+  }
+}
diff --git a/java/kotlin/src/test/kotlin/com/google/protobuf/DslListTest.kt b/java/kotlin/src/test/kotlin/com/google/protobuf/DslListTest.kt
new file mode 100644
index 0000000..e5e6017
--- /dev/null
+++ b/java/kotlin/src/test/kotlin/com/google/protobuf/DslListTest.kt
@@ -0,0 +1,98 @@
+package com.google.protobuf.kotlin
+
+import com.google.common.testing.EqualsTester
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/** Tests for [DslList]. */
+@RunWith(JUnit4::class)
+@OptIn(OnlyForUseByGeneratedProtoCode::class)
+class DslListTest {
+  class DummyProxy private constructor() : DslProxy()
+
+  @Test
+  fun matchesList() {
+    assertThat(DslList<Int, DummyProxy>(listOf(1, 2, 3))).containsExactly(1, 2, 3).inOrder()
+  }
+
+  @Test
+  fun reflectsChangesInList() {
+    val mutableList = mutableListOf(1, 2, 3)
+    val dslList = DslList<Int, DummyProxy>(mutableList)
+    mutableList.add(4)
+    assertThat(dslList).containsExactly(1, 2, 3, 4).inOrder()
+  }
+
+  @Test
+  fun dslListIsNotMutable() {
+    val dslList = DslList<Int, DummyProxy>(mutableListOf(1, 2, 3))
+    assertThat(dslList is MutableList<*>).isFalse()
+  }
+
+  @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")
+  @Test
+  fun dslListIsNotEvenSecretlyMutable() {
+    val dslList = DslList<Int, DummyProxy>(mutableListOf(1, 2, 3))
+    val dslListAsJavaUtil = dslList as java.util.List<Int>
+    assertFailsWith<UnsupportedOperationException> {
+      dslListAsJavaUtil.add(4)
+    }
+  }
+
+  @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")
+  @Test
+  fun dslList_IteratorIsNotEvenSecretlyMutable() {
+    val dslList = DslList<Int, DummyProxy>(mutableListOf(1, 2, 3))
+    val iterator = dslList.iterator() as java.util.Iterator<Int>
+    iterator.next()
+
+    assertFailsWith<UnsupportedOperationException> {
+      iterator.remove()
+    }
+  }
+
+  @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")
+  @Test
+  fun dslList_ListIteratorIsNotEvenSecretlyMutable() {
+    val dslList = DslList<Int, DummyProxy>(mutableListOf(1, 2, 3))
+    val iterator = dslList.listIterator() as java.util.ListIterator<Int>
+    iterator.next()
+
+    assertFailsWith<UnsupportedOperationException> {
+      iterator.remove()
+    }
+  }
+
+  @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")
+  @Test
+  fun dslList_ListIteratorIndexIsNotEvenSecretlyMutable() {
+    val dslList = DslList<Int, DummyProxy>(mutableListOf(1, 2, 3))
+    val iterator = dslList.listIterator(1) as java.util.ListIterator<Int>
+    iterator.next()
+
+    assertFailsWith<UnsupportedOperationException> {
+      iterator.remove()
+    }
+  }
+
+  @Test
+  fun expectedToString() {
+    assertThat(DslList<Int, DummyProxy>(listOf(1, 2)).toString()).isEqualTo("[1, 2]")
+  }
+
+  @Test
+  fun equality() {
+    EqualsTester()
+      .addEqualityGroup(DslList<Int, DummyProxy>(listOf(1, 2)), listOf(1, 2))
+      .addEqualityGroup(DslList<Int, DummyProxy>(listOf(2, 2)), listOf(2, 2))
+      .addEqualityGroup(
+        DslList<Int, DummyProxy>(emptyList()),
+        DslList<String, DummyProxy>(emptyList()),
+        emptyList<Int>()
+      )
+      .testEquals()
+  }
+}
diff --git a/java/kotlin/src/test/kotlin/com/google/protobuf/DslMapTest.kt b/java/kotlin/src/test/kotlin/com/google/protobuf/DslMapTest.kt
new file mode 100644
index 0000000..470e42e
--- /dev/null
+++ b/java/kotlin/src/test/kotlin/com/google/protobuf/DslMapTest.kt
@@ -0,0 +1,164 @@
+package com.google.protobuf.kotlin
+
+import com.google.common.testing.EqualsTester
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+@OptIn(OnlyForUseByGeneratedProtoCode::class)
+class DslMapTest {
+  class DummyProxy private constructor() : DslProxy()
+
+  @Test
+  fun matchesMap() {
+    assertThat(DslMap<Int, Int, DummyProxy>(mapOf(1 to -1, 2 to -2)))
+      .containsExactly(1, -1, 2, -2)
+  }
+
+  @Test
+  fun reflectsChangesInMap() {
+    val mutableMap = mutableMapOf(1 to -1, 2 to -2)
+    val dslMap = DslMap<Int, Int, DummyProxy>(mutableMap)
+    mutableMap[3] = -3
+    assertThat(dslMap).containsExactly(1, -1, 2, -2, 3, -3).inOrder()
+  }
+
+  @Test
+  fun dslMapIsNotMutable() {
+    val dslMap = DslMap<Int, Int, DummyProxy>(mutableMapOf(1 to -1))
+    assertThat(dslMap is MutableMap<*, *>).isFalse()
+  }
+
+  @Test
+  fun dslMapKeysAreNotMutable() {
+    val dslMap = DslMap<Int, Int, DummyProxy>(mutableMapOf(1 to -1))
+    assertThat(dslMap.keys is MutableSet<*>).isFalse()
+  }
+
+  @Test
+  fun dslMapValuesAreNotMutable() {
+    val dslMap = DslMap<Int, Int, DummyProxy>(mutableMapOf(1 to -1))
+    assertThat(dslMap.values is MutableSet<*>).isFalse()
+  }
+
+  @Test
+  fun dslMapEntriesAreNotMutable() {
+    val dslMap = DslMap<Int, Int, DummyProxy>(mutableMapOf(1 to -1))
+    assertThat(dslMap.entries is MutableSet<*>).isFalse()
+  }
+
+  @Test
+  fun dslMapEntryObjectsAreNotMutable() {
+    val dslMap = DslMap<Int, Int, DummyProxy>(mutableMapOf(1 to -1))
+    assertThat(dslMap.entries.single() is MutableMap.MutableEntry<*, *>).isFalse()
+  }
+
+  @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")
+  @Test
+  fun dslMapIsNotEvenSecretlyMutable() {
+    val dslMap = DslMap<Int, Int, DummyProxy>(mutableMapOf(1 to -1))
+    val dslMapAsJavaUtilMap = dslMap as java.util.Map<Int, Int>
+    assertFailsWith<UnsupportedOperationException> {
+      dslMapAsJavaUtilMap.put(2, -2)
+    }
+  }
+
+  @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")
+  @Test
+  fun dslMapKeysAreNotEvenSecretlyMutable() {
+    val dslMap = DslMap<Int, Int, DummyProxy>(mutableMapOf(1 to -1))
+    val dslMapKeysAsJavaUtilSet = dslMap.keys as java.util.Set<Int>
+    assertFailsWith<UnsupportedOperationException> {
+      dslMapKeysAsJavaUtilSet.remove(1)
+    }
+  }
+
+  @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")
+  @Test
+  fun dslMapKeysIteratorIsNotEvenSecretlyMutable() {
+    val dslMap = DslMap<Int, Int, DummyProxy>(mutableMapOf(1 to -1))
+    val dslMapKeysAsJavaUtilSet = dslMap.keys as java.util.Set<Int>
+    val itr = dslMapKeysAsJavaUtilSet.iterator()
+    itr.next()
+    assertFailsWith<UnsupportedOperationException> {
+      itr.remove()
+    }
+  }
+
+  @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")
+  @Test
+  fun dslMapValuesAreNotEvenSecretlyMutable() {
+    val dslMap = DslMap<Int, Int, DummyProxy>(mutableMapOf(1 to -1))
+    val dslMapValuesAsJavaUtilCollection = dslMap.values as java.util.Collection<Int>
+    assertFailsWith<UnsupportedOperationException> {
+      dslMapValuesAsJavaUtilCollection.remove(1)
+    }
+  }
+
+  @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")
+  @Test
+  fun dslMapValuesIteratorIsNotEvenSecretlyMutable() {
+    val dslMap = DslMap<Int, Int, DummyProxy>(mutableMapOf(1 to -1))
+    val dslMapValuesAsJavaUtilCollection = dslMap.values as java.util.Collection<Int>
+    val itr = dslMapValuesAsJavaUtilCollection.iterator()
+    itr.next()
+    assertFailsWith<UnsupportedOperationException> {
+      itr.remove()
+    }
+  }
+
+  @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")
+  @Test
+  fun dslMapEntriesAreNotEvenSecretlyMutable() {
+    val dslMap = DslMap<Int, Int, DummyProxy>(mutableMapOf(1 to -1))
+    val dslMapEntriesAsJavaUtilSet = dslMap.entries as java.util.Set<Map.Entry<Int, Int>>
+    val entry = dslMap.entries.single()
+    assertFailsWith<UnsupportedOperationException> {
+      dslMapEntriesAsJavaUtilSet.remove(entry)
+    }
+  }
+
+  @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")
+  @Test
+  fun dslMapEntriesIteratorIsNotEvenSecretlyMutable() {
+    val dslMap = DslMap<Int, Int, DummyProxy>(mutableMapOf(1 to -1))
+    val dslMapEntriesAsJavaUtilSet = dslMap.entries as java.util.Set<Map.Entry<Int, Int>>
+    val itr = dslMapEntriesAsJavaUtilSet.iterator()
+    itr.next()
+    assertFailsWith<UnsupportedOperationException> {
+      itr.remove()
+    }
+  }
+
+  @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")
+  @Test
+  fun dslMapEntryObjectsAreNotEvenSecretlyMutable() {
+    val dslMap = DslMap<Int, Int, DummyProxy>(mutableMapOf(1 to -1))
+    val dslMapEntryAsJavaUtilMapEntry = dslMap.entries.single() as java.util.Map.Entry<Int, Int>
+    assertFailsWith<UnsupportedOperationException> {
+      dslMapEntryAsJavaUtilMapEntry.value = 2
+    }
+  }
+
+  @Test
+  fun expectedToString() {
+    assertThat(DslMap<Int, Int, DummyProxy>(mapOf(1 to 2, 2 to 3)).toString())
+      .isEqualTo("{1=2, 2=3}")
+  }
+
+  @Test
+  fun equality() {
+    EqualsTester()
+      .addEqualityGroup(DslMap<Int, Int, DummyProxy>(mapOf(1 to 2, 2 to 3)), mapOf(1 to 2, 2 to 3))
+      .addEqualityGroup(DslMap<Int, Int, DummyProxy>(mapOf(1 to 3, 2 to 3)), mapOf(1 to 3, 2 to 3))
+      .addEqualityGroup(
+        DslMap<Int, Int, DummyProxy>(emptyMap()),
+        DslMap<String, String, DummyProxy>(emptyMap()),
+        emptyMap<Int, Int>()
+      )
+      .testEquals()
+  }
+}
diff --git a/java/kotlin/src/test/kotlin/com/google/protobuf/ExtendableMessageExtensionsTest.kt b/java/kotlin/src/test/kotlin/com/google/protobuf/ExtendableMessageExtensionsTest.kt
new file mode 100644
index 0000000..c8816eb
--- /dev/null
+++ b/java/kotlin/src/test/kotlin/com/google/protobuf/ExtendableMessageExtensionsTest.kt
@@ -0,0 +1,60 @@
+package com.google.protobuf.kotlin
+
+import com.google.common.truth.Truth.assertThat
+import example_extensible_message.ExampleExtensibleMessage
+import example_extensible_message.ExampleExtensibleMessageOuterClass as TestProto
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ExtendableMessageExtensionsTest {
+  @Test
+  fun setOnBuilder() {
+    val builder = ExampleExtensibleMessage.newBuilder()
+    builder[TestProto.int32Extension] = 5
+    assertThat(builder.build().getExtension(TestProto.int32Extension)).isEqualTo(5)
+  }
+
+  @Test
+  fun getOnBuilder() {
+    val builder = ExampleExtensibleMessage.newBuilder()
+      .setExtension(TestProto.int32Extension, 6)
+    assertThat(builder[TestProto.int32Extension]).isEqualTo(6)
+  }
+
+  @Test
+  fun getOnMessage() {
+    val message = ExampleExtensibleMessage.newBuilder()
+      .setExtension(TestProto.int32Extension, 6)
+      .build()
+    assertThat(message[TestProto.int32Extension]).isEqualTo(6)
+  }
+
+  @Test
+  fun containsPositiveOnMessage() {
+    val message = ExampleExtensibleMessage.newBuilder()
+      .setExtension(TestProto.int32Extension, 6)
+      .build()
+    assertThat(TestProto.int32Extension in message).isTrue()
+  }
+
+  @Test
+  fun containsPositiveOnBuilder() {
+    val builder = ExampleExtensibleMessage.newBuilder()
+      .setExtension(TestProto.int32Extension, 6)
+    assertThat(TestProto.int32Extension in builder).isTrue()
+  }
+
+  @Test
+  fun containsNegativeOnMessage() {
+    val message = ExampleExtensibleMessage.newBuilder().build()
+    assertThat(TestProto.int32Extension in message).isFalse()
+  }
+
+  @Test
+  fun containsNegativeOnBuilder() {
+    val builder = ExampleExtensibleMessage.newBuilder()
+    assertThat(TestProto.int32Extension in builder).isFalse()
+  }
+}
diff --git a/java/kotlin/src/test/kotlin/com/google/protobuf/ExtensionListTest.kt b/java/kotlin/src/test/kotlin/com/google/protobuf/ExtensionListTest.kt
new file mode 100644
index 0000000..e805e1e
--- /dev/null
+++ b/java/kotlin/src/test/kotlin/com/google/protobuf/ExtensionListTest.kt
@@ -0,0 +1,125 @@
+package com.google.protobuf.kotlin
+
+import com.google.common.testing.EqualsTester
+import com.google.common.truth.Truth.assertThat
+import example_extensible_message.ExampleExtensibleMessage
+import example_extensible_message.ExampleExtensibleMessageOuterClass as TestProto
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/** Tests for [DslList]. */
+@RunWith(JUnit4::class)
+@OptIn(OnlyForUseByGeneratedProtoCode::class)
+class ExtensionListTest {
+  class DummyProxy private constructor() : DslProxy()
+
+  @Test
+  fun matchesList() {
+    assertThat(
+      ExtensionList<Int, ExampleExtensibleMessage>(
+        TestProto.repeatedExtension, listOf(1, 2, 3)
+      )
+    ).containsExactly(1, 2, 3).inOrder()
+  }
+
+  @Test
+  fun reflectsChangesInList() {
+    val mutableList = mutableListOf(1, 2, 3)
+    val extensionList = ExtensionList<Int, ExampleExtensibleMessage>(
+      TestProto.repeatedExtension, mutableList
+    )
+    mutableList.add(4)
+    assertThat(extensionList).containsExactly(1, 2, 3, 4).inOrder()
+  }
+
+  @Test
+  fun extensionListIsNotMutable() {
+    val extensionList = ExtensionList<Int, ExampleExtensibleMessage>(
+      TestProto.repeatedExtension, mutableListOf(1, 2, 3)
+    )
+    assertThat(extensionList is MutableList<*>).isFalse()
+  }
+
+  @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")
+  @Test
+  fun extensionListIsNotEvenSecretlyMutable() {
+    val extensionList = ExtensionList<Int, ExampleExtensibleMessage>(
+      TestProto.repeatedExtension, mutableListOf(1, 2, 3)
+    )
+    val extensionListAsJavaUtil = extensionList as java.util.List<Int>
+    assertFailsWith<UnsupportedOperationException> {
+      extensionListAsJavaUtil.add(4)
+    }
+  }
+
+  @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")
+  @Test
+  fun extensionList_IteratorIsNotEvenSecretlyMutable() {
+    val extensionList = ExtensionList<Int, ExampleExtensibleMessage>(
+      TestProto.repeatedExtension, mutableListOf(1, 2, 3)
+    )
+    val iterator = extensionList.iterator() as java.util.Iterator<Int>
+    iterator.next()
+
+    assertFailsWith<UnsupportedOperationException> {
+      iterator.remove()
+    }
+  }
+
+  @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")
+  @Test
+  fun extensionList_ListIteratorIsNotEvenSecretlyMutable() {
+    val extensionList = ExtensionList<Int, ExampleExtensibleMessage>(
+      TestProto.repeatedExtension, mutableListOf(1, 2, 3)
+    )
+    val iterator = extensionList.listIterator() as java.util.ListIterator<Int>
+    iterator.next()
+
+    assertFailsWith<UnsupportedOperationException> {
+      iterator.remove()
+    }
+  }
+
+  @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")
+  @Test
+  fun extensionList_ListIteratorIndexIsNotEvenSecretlyMutable() {
+    val extensionList = ExtensionList<Int, ExampleExtensibleMessage>(
+      TestProto.repeatedExtension, mutableListOf(1, 2, 3)
+    )
+    val iterator = extensionList.listIterator(1) as java.util.ListIterator<Int>
+    iterator.next()
+
+    assertFailsWith<UnsupportedOperationException> {
+      iterator.remove()
+    }
+  }
+
+  @Test
+  fun expectedToString() {
+    assertThat(
+      ExtensionList<Int, ExampleExtensibleMessage>(TestProto.repeatedExtension, listOf(1, 2))
+        .toString()
+    ).isEqualTo("[1, 2]")
+  }
+
+  @Test
+  fun equality() {
+    EqualsTester()
+      .addEqualityGroup(
+        ExtensionList<Int, ExampleExtensibleMessage>(TestProto.repeatedExtension, listOf(1, 2)),
+        ExtensionList<Int, ExampleExtensibleMessage>(TestProto.differentExtension, listOf(1, 2)),
+        listOf(1, 2)
+      )
+      .addEqualityGroup(
+        ExtensionList<Int, ExampleExtensibleMessage>(TestProto.repeatedExtension, listOf(2, 2)),
+        listOf(2, 2)
+      )
+      .addEqualityGroup(
+        ExtensionList<Int, ExampleExtensibleMessage>(TestProto.repeatedExtension, emptyList()),
+        emptyList<Int>()
+      )
+      .testEquals()
+  }
+}
diff --git a/java/kotlin/src/test/kotlin/com/google/protobuf/Proto2Test.kt b/java/kotlin/src/test/kotlin/com/google/protobuf/Proto2Test.kt
new file mode 100644
index 0000000..72b9792
--- /dev/null
+++ b/java/kotlin/src/test/kotlin/com/google/protobuf/Proto2Test.kt
@@ -0,0 +1,891 @@
+package com.google.protobuf.kotlin
+
+import com.google.common.truth.Truth.assertThat
+import com.google.protobuf.TestUtil
+import com.google.protobuf.TestUtil.toBytes
+import evil_names_proto2.EvilNamesProto2OuterClass.EvilNamesProto2
+import evil_names_proto2.EvilNamesProto2OuterClass.HardKeywordsAllTypes
+import evil_names_proto2.EvilNamesProto2OuterClass.Interface
+import evil_names_proto2.HardKeywordsAllTypesKt
+import evil_names_proto2.evilNamesProto2
+import evil_names_proto2.hardKeywordsAllTypes
+import evil_names_proto2.interface_
+import com.google.protobuf.test.UnittestImport.ImportEnum
+import com.google.protobuf.test.UnittestImport.ImportMessage
+import com.google.protobuf.test.UnittestImportPublic.PublicImportMessage
+import protobuf_unittest.MapProto2Unittest.Proto2MapEnum
+import protobuf_unittest.MapProto2Unittest.TestEnumMap
+import protobuf_unittest.MapProto2Unittest.TestIntIntMap
+import protobuf_unittest.MapProto2Unittest.TestMaps
+import protobuf_unittest.TestAllTypesKt
+import protobuf_unittest.TestAllTypesKt.nestedMessage
+import protobuf_unittest.UnittestProto
+import protobuf_unittest.UnittestProto.ForeignEnum
+import protobuf_unittest.UnittestProto.TestAllTypes
+import protobuf_unittest.UnittestProto.TestAllTypes.NestedEnum
+import protobuf_unittest.UnittestProto.TestEmptyMessage
+import protobuf_unittest.UnittestProto.TestEmptyMessageWithExtensions
+import protobuf_unittest.copy
+import protobuf_unittest.foreignMessage
+import protobuf_unittest.optionalGroupExtension
+import protobuf_unittest.repeatedGroupExtension
+import protobuf_unittest.testAllExtensions
+import protobuf_unittest.testAllTypes
+import protobuf_unittest.testEmptyMessage
+import protobuf_unittest.testEmptyMessageWithExtensions
+import protobuf_unittest.testEnumMap
+import protobuf_unittest.testIntIntMap
+import protobuf_unittest.testMaps
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class Proto2Test {
+  @Test
+  fun testSetters() {
+    assertThat(
+      testAllTypes {
+        optionalInt32 = 101
+        optionalInt64 = 102
+        optionalUint32 = 103
+        optionalUint64 = 104
+        optionalSint32 = 105
+        optionalSint64 = 106
+        optionalFixed32 = 107
+        optionalFixed64 = 108
+        optionalSfixed32 = 109
+        optionalSfixed64 = 110
+        optionalFloat = 111.0f
+        optionalDouble = 112.0
+        optionalBool = true
+        optionalString = "115"
+        optionalBytes = toBytes("116")
+        optionalGroup =
+          TestAllTypesKt.optionalGroup { a = 117 }
+        optionalNestedMessage = nestedMessage { bb = 118 }
+        optionalForeignMessage = foreignMessage { c = 119 }
+        optionalImportMessage =
+          ImportMessage.newBuilder().setD(120).build()
+        optionalPublicImportMessage =
+          PublicImportMessage.newBuilder().setE(126).build()
+        optionalLazyMessage = nestedMessage { bb = 127 }
+        optionalNestedEnum = NestedEnum.BAZ
+        optionalForeignEnum = ForeignEnum.FOREIGN_BAZ
+        optionalImportEnum = ImportEnum.IMPORT_BAZ
+        optionalStringPiece = "124"
+        optionalCord = "125"
+        repeatedInt32.add(201)
+        repeatedInt64.add(202)
+        repeatedUint32.add(203)
+        repeatedUint64.add(204)
+        repeatedSint32.add(205)
+        repeatedSint64.add(206)
+        repeatedFixed32.add(207)
+        repeatedFixed64.add(208)
+        repeatedSfixed32.add(209)
+        repeatedSfixed64.add(210)
+        repeatedFloat.add(211f)
+        repeatedDouble.add(212.0)
+        repeatedBool.add(true)
+        repeatedString.add("215")
+        repeatedBytes.add(toBytes("216"))
+        repeatedGroup.add(TestAllTypesKt.repeatedGroup { a = 217 })
+        repeatedNestedMessage.add(nestedMessage { bb = 218 })
+        repeatedForeignMessage.add(foreignMessage { c = 219 })
+        repeatedImportMessage.add(
+          ImportMessage.newBuilder().setD(220).build()
+        )
+        repeatedLazyMessage.add(nestedMessage { bb = 227 })
+        repeatedNestedEnum.add(NestedEnum.BAR)
+        repeatedForeignEnum.add(ForeignEnum.FOREIGN_BAR)
+        repeatedImportEnum.add(ImportEnum.IMPORT_BAR)
+        repeatedStringPiece.add("224")
+        repeatedCord.add("225")
+        repeatedInt32 += 301
+        repeatedInt64 += 302
+        repeatedUint32 += 303
+        repeatedUint64 += 304
+        repeatedSint32 += 305
+        repeatedSint64 += 306
+        repeatedFixed32 += 307
+        repeatedFixed64 += 308
+        repeatedSfixed32 += 309
+        repeatedSfixed64 += 310
+        repeatedFloat += 311f
+        repeatedDouble += 312.0
+        repeatedBool += false
+        repeatedString += "315"
+        repeatedBytes += toBytes("316")
+        repeatedGroup += TestAllTypesKt.repeatedGroup { a = 317 }
+        repeatedNestedMessage += nestedMessage { bb = 318 }
+        repeatedForeignMessage += foreignMessage { c = 319 }
+        repeatedImportMessage +=
+          ImportMessage.newBuilder().setD(320).build()
+        repeatedLazyMessage +=
+          TestAllTypesKt.nestedMessage { bb = 327 }
+        repeatedNestedEnum += NestedEnum.BAZ
+        repeatedForeignEnum += ForeignEnum.FOREIGN_BAZ
+        repeatedImportEnum += ImportEnum.IMPORT_BAZ
+        repeatedStringPiece += "324"
+        repeatedCord += "325"
+        defaultInt32 = 401
+        defaultInt64 = 402
+        defaultUint32 = 403
+        defaultUint64 = 404
+        defaultSint32 = 405
+        defaultSint64 = 406
+        defaultFixed32 = 407
+        defaultFixed64 = 408
+        defaultSfixed32 = 409
+        defaultSfixed64 = 410
+        defaultFloat = 411f
+        defaultDouble = 412.0
+        defaultBool = false
+        defaultString = "415"
+        defaultBytes = toBytes("416")
+        defaultNestedEnum = NestedEnum.FOO
+        defaultForeignEnum = ForeignEnum.FOREIGN_FOO
+        defaultImportEnum = ImportEnum.IMPORT_FOO
+        defaultStringPiece = "424"
+        defaultCord = "425"
+        oneofUint32 = 601
+        oneofNestedMessage =
+          TestAllTypesKt.nestedMessage { bb = 602 }
+        oneofString = "603"
+        oneofBytes = toBytes("604")
+      }
+    ).isEqualTo(
+      TestUtil.getAllSetBuilder().build()
+    )
+  }
+
+  @Test
+  fun testGetters() {
+    testAllTypes {
+      optionalInt32 = 101
+      assertThat(optionalInt32).isEqualTo(101)
+      optionalString = "115"
+      assertThat(optionalString).isEqualTo("115")
+      optionalGroup = TestAllTypesKt.optionalGroup { a = 117 }
+      assertThat(optionalGroup).isEqualTo(TestAllTypesKt.optionalGroup { a = 117 })
+      optionalNestedMessage = TestAllTypesKt.nestedMessage { bb = 118 }
+      assertThat(optionalNestedMessage).isEqualTo(TestAllTypesKt.nestedMessage { bb = 118 })
+      optionalNestedEnum = NestedEnum.BAZ
+      assertThat(optionalNestedEnum).isEqualTo(NestedEnum.BAZ)
+      defaultInt32 = 401
+      assertThat(defaultInt32).isEqualTo(401)
+      oneofUint32 = 601
+      assertThat(oneofUint32).isEqualTo(601)
+    }
+  }
+
+  @Test
+  fun testDefaultGetters() {
+    testAllTypes {
+      assertThat(defaultInt32).isEqualTo(41)
+      assertThat(defaultString).isEqualTo("hello")
+      assertThat(defaultNestedEnum).isEqualTo(NestedEnum.BAR)
+      assertThat(defaultStringPiece).isEqualTo("abc")
+    }
+  }
+
+  @Test
+  fun testRepeatedGettersAndSetters() {
+    testAllTypes {
+      repeatedInt32.addAll(listOf(1, 2))
+      assertThat(repeatedInt32).isEqualTo(listOf(1, 2))
+      repeatedInt32 += listOf(3, 4)
+      assertThat(repeatedInt32).isEqualTo(listOf(1, 2, 3, 4))
+      repeatedInt32[0] = 5
+      assertThat(repeatedInt32).isEqualTo(listOf(5, 2, 3, 4))
+
+      repeatedString.addAll(listOf("1", "2"))
+      assertThat(repeatedString).isEqualTo(listOf("1", "2"))
+      repeatedString += listOf("3", "4")
+      assertThat(repeatedString).isEqualTo(listOf("1", "2", "3", "4"))
+      repeatedString[0] = "5"
+      assertThat(repeatedString).isEqualTo(listOf("5", "2", "3", "4"))
+
+      repeatedGroup.addAll(
+        listOf(
+          TestAllTypesKt.repeatedGroup { a = 1 },
+          TestAllTypesKt.repeatedGroup { a = 2 }
+        )
+      )
+      assertThat(repeatedGroup).isEqualTo(
+        listOf(
+          TestAllTypesKt.repeatedGroup { a = 1 },
+          TestAllTypesKt.repeatedGroup { a = 2 }
+        )
+      )
+      repeatedGroup +=
+        listOf(
+          TestAllTypesKt.repeatedGroup { a = 3 },
+          TestAllTypesKt.repeatedGroup { a = 4 }
+        )
+      assertThat(repeatedGroup).isEqualTo(
+        listOf(
+          TestAllTypesKt.repeatedGroup { a = 1 },
+          TestAllTypesKt.repeatedGroup { a = 2 },
+          TestAllTypesKt.repeatedGroup { a = 3 },
+          TestAllTypesKt.repeatedGroup { a = 4 }
+        )
+      )
+      repeatedGroup[0] = TestAllTypesKt.repeatedGroup { a = 5 }
+      assertThat(repeatedGroup).isEqualTo(
+        listOf(
+          TestAllTypesKt.repeatedGroup { a = 5 },
+          TestAllTypesKt.repeatedGroup { a = 2 },
+          TestAllTypesKt.repeatedGroup { a = 3 },
+          TestAllTypesKt.repeatedGroup { a = 4 }
+        )
+      )
+
+      repeatedNestedMessage.addAll(listOf(nestedMessage { bb = 1 }, nestedMessage { bb = 2 }))
+      assertThat(repeatedNestedMessage).isEqualTo(
+        listOf(
+          nestedMessage { bb = 1 },
+          nestedMessage { bb = 2 }
+        )
+      )
+      repeatedNestedMessage += listOf(nestedMessage { bb = 3 }, nestedMessage { bb = 4 })
+      assertThat(repeatedNestedMessage).isEqualTo(
+        listOf(
+          nestedMessage { bb = 1 },
+          nestedMessage { bb = 2 },
+          nestedMessage { bb = 3 },
+          nestedMessage { bb = 4 }
+        )
+      )
+      repeatedNestedMessage[0] = nestedMessage { bb = 5 }
+      assertThat(repeatedNestedMessage).isEqualTo(
+        listOf(
+          nestedMessage { bb = 5 },
+          nestedMessage { bb = 2 },
+          nestedMessage { bb = 3 },
+          nestedMessage { bb = 4 }
+        )
+      )
+
+      repeatedNestedEnum.addAll(listOf(NestedEnum.FOO, NestedEnum.BAR))
+      assertThat(repeatedNestedEnum).isEqualTo(listOf(NestedEnum.FOO, NestedEnum.BAR))
+      repeatedNestedEnum += listOf(NestedEnum.BAZ, NestedEnum.FOO)
+      assertThat(repeatedNestedEnum).isEqualTo(
+        listOf(NestedEnum.FOO, NestedEnum.BAR, NestedEnum.BAZ, NestedEnum.FOO)
+      )
+      repeatedNestedEnum[0] = NestedEnum.BAR
+      assertThat(repeatedNestedEnum).isEqualTo(
+        listOf(NestedEnum.BAR, NestedEnum.BAR, NestedEnum.BAZ, NestedEnum.FOO)
+      )
+    }
+  }
+
+  @Test
+  fun testHazzers() {
+    testAllTypes {
+      optionalInt32 = 101
+      assertThat(hasOptionalInt32()).isTrue()
+      assertThat(hasOptionalString()).isFalse()
+      optionalGroup = TestAllTypesKt.optionalGroup { a = 117 }
+      assertThat(hasOptionalGroup()).isTrue()
+      assertThat(hasOptionalNestedMessage()).isFalse()
+      optionalNestedEnum = NestedEnum.BAZ
+      assertThat(hasOptionalNestedEnum()).isTrue()
+      assertThat(hasDefaultInt32()).isFalse()
+      oneofUint32 = 601
+      assertThat(hasOneofUint32()).isTrue()
+    }
+
+    testAllTypes {
+      assertThat(hasOptionalInt32()).isFalse()
+      optionalString = "115"
+      assertThat(hasOptionalString()).isTrue()
+      assertThat(hasOptionalGroup()).isFalse()
+      optionalNestedMessage = TestAllTypesKt.nestedMessage { bb = 118 }
+      assertThat(hasOptionalNestedMessage()).isTrue()
+      assertThat(hasOptionalNestedEnum()).isFalse()
+      defaultInt32 = 401
+      assertThat(hasDefaultInt32()).isTrue()
+      assertThat(hasOneofUint32()).isFalse()
+    }
+  }
+
+  @Test
+  fun testClears() {
+    testAllTypes {
+      optionalInt32 = 101
+      clearOptionalInt32()
+      assertThat(hasOptionalInt32()).isFalse()
+
+      optionalString = "115"
+      clearOptionalString()
+      assertThat(hasOptionalString()).isFalse()
+
+      optionalGroup = TestAllTypesKt.optionalGroup { a = 117 }
+      clearOptionalGroup()
+      assertThat(hasOptionalGroup()).isFalse()
+
+      optionalNestedMessage = TestAllTypesKt.nestedMessage { bb = 118 }
+      clearOptionalNestedMessage()
+      assertThat(hasOptionalNestedMessage()).isFalse()
+
+      optionalNestedEnum = NestedEnum.BAZ
+      clearOptionalNestedEnum()
+      assertThat(hasOptionalNestedEnum()).isFalse()
+
+      defaultInt32 = 401
+      clearDefaultInt32()
+      assertThat(hasDefaultInt32()).isFalse()
+
+      oneofUint32 = 601
+      clearOneofUint32()
+      assertThat(hasOneofUint32()).isFalse()
+    }
+  }
+
+  @Test
+  fun testCopy() {
+    val message = testAllTypes {
+      optionalInt32 = 101
+      optionalString = "115"
+    }
+    val modifiedMessage = message.copy {
+      optionalInt32 = 201
+    }
+
+    assertThat(message).isEqualTo(
+      TestAllTypes.newBuilder()
+        .setOptionalInt32(101)
+        .setOptionalString("115")
+        .build()
+    )
+    assertThat(modifiedMessage).isEqualTo(
+      TestAllTypes.newBuilder()
+        .setOptionalInt32(201)
+        .setOptionalString("115")
+        .build()
+    )
+  }
+
+  @Test
+  fun testOneof() {
+    val message = testAllTypes {
+      oneofString = "foo"
+      assertThat(oneofFieldCase)
+        .isEqualTo(TestAllTypes.OneofFieldCase.ONEOF_STRING)
+      assertThat(oneofString).isEqualTo("foo")
+      clearOneofField()
+      assertThat(hasOneofUint32()).isFalse()
+      assertThat(oneofFieldCase)
+        .isEqualTo(TestAllTypes.OneofFieldCase.ONEOFFIELD_NOT_SET)
+      oneofUint32 = 5
+    }
+
+    assertThat(message.getOneofFieldCase())
+      .isEqualTo(TestAllTypes.OneofFieldCase.ONEOF_UINT32)
+    assertThat(message.getOneofUint32()).isEqualTo(5)
+  }
+
+  @Test
+  fun testExtensionsSet() {
+    assertThat(
+      testAllExtensions {
+        this[UnittestProto.optionalInt32Extension] = 101
+        this[UnittestProto.optionalInt64Extension] = 102L
+        this[UnittestProto.optionalUint32Extension] = 103
+        this[UnittestProto.optionalUint64Extension] = 104L
+        this[UnittestProto.optionalSint32Extension] = 105
+        this[UnittestProto.optionalSint64Extension] = 106L
+        this[UnittestProto.optionalFixed32Extension] = 107
+        this[UnittestProto.optionalFixed64Extension] = 108L
+        this[UnittestProto.optionalSfixed32Extension] = 109
+        this[UnittestProto.optionalSfixed64Extension] = 110L
+        this[UnittestProto.optionalFloatExtension] = 111F
+        this[UnittestProto.optionalDoubleExtension] = 112.0
+        this[UnittestProto.optionalBoolExtension] = true
+        this[UnittestProto.optionalStringExtension] = "115"
+        this[UnittestProto.optionalBytesExtension] = toBytes("116")
+        this[UnittestProto.optionalGroupExtension] = optionalGroupExtension { a = 117 }
+        this[UnittestProto.optionalNestedMessageExtension] =
+          TestAllTypesKt.nestedMessage { bb = 118 }
+        this[UnittestProto.optionalForeignMessageExtension] = foreignMessage { c = 119 }
+        this[UnittestProto.optionalImportMessageExtension] =
+          ImportMessage.newBuilder().setD(120).build()
+        this[UnittestProto.optionalPublicImportMessageExtension] =
+          PublicImportMessage.newBuilder().setE(126).build()
+        this[UnittestProto.optionalLazyMessageExtension] = TestAllTypesKt.nestedMessage { bb = 127 }
+        this[UnittestProto.optionalNestedEnumExtension] = NestedEnum.BAZ
+        this[UnittestProto.optionalForeignEnumExtension] = ForeignEnum.FOREIGN_BAZ
+        this[UnittestProto.optionalImportEnumExtension] = ImportEnum.IMPORT_BAZ
+        this[UnittestProto.optionalStringPieceExtension] = "124"
+        this[UnittestProto.optionalCordExtension] = "125"
+        this[UnittestProto.repeatedInt32Extension].add(201)
+        this[UnittestProto.repeatedInt64Extension].add(202L)
+        this[UnittestProto.repeatedUint32Extension].add(203)
+        this[UnittestProto.repeatedUint64Extension].add(204L)
+        this[UnittestProto.repeatedSint32Extension].add(205)
+        this[UnittestProto.repeatedSint64Extension].add(206L)
+        this[UnittestProto.repeatedFixed32Extension].add(207)
+        this[UnittestProto.repeatedFixed64Extension].add(208L)
+        this[UnittestProto.repeatedSfixed32Extension].add(209)
+        this[UnittestProto.repeatedSfixed64Extension].add(210L)
+        this[UnittestProto.repeatedFloatExtension].add(211F)
+        this[UnittestProto.repeatedDoubleExtension].add(212.0)
+        this[UnittestProto.repeatedBoolExtension].add(true)
+        this[UnittestProto.repeatedStringExtension].add("215")
+        this[UnittestProto.repeatedBytesExtension].add(toBytes("216"))
+        this[UnittestProto.repeatedGroupExtension].add(repeatedGroupExtension { a = 217 })
+        this[UnittestProto.repeatedNestedMessageExtension]
+          .add(TestAllTypesKt.nestedMessage { bb = 218 })
+        this[UnittestProto.repeatedForeignMessageExtension].add(foreignMessage { c = 219 })
+        this[UnittestProto.repeatedImportMessageExtension]
+          .add(ImportMessage.newBuilder().setD(220).build())
+        this[UnittestProto.repeatedLazyMessageExtension]
+          .add(TestAllTypesKt.nestedMessage { bb = 227 })
+        this[UnittestProto.repeatedNestedEnumExtension].add(NestedEnum.BAR)
+        this[UnittestProto.repeatedForeignEnumExtension].add(ForeignEnum.FOREIGN_BAR)
+        this[UnittestProto.repeatedImportEnumExtension].add(ImportEnum.IMPORT_BAR)
+        this[UnittestProto.repeatedStringPieceExtension].add("224")
+        this[UnittestProto.repeatedCordExtension].add("225")
+        this[UnittestProto.repeatedInt32Extension].add(301)
+        this[UnittestProto.repeatedInt64Extension].add(302L)
+        this[UnittestProto.repeatedUint32Extension].add(303)
+        this[UnittestProto.repeatedUint64Extension].add(304L)
+        this[UnittestProto.repeatedSint32Extension].add(305)
+        this[UnittestProto.repeatedSint64Extension].add(306L)
+        this[UnittestProto.repeatedFixed32Extension].add(307)
+        this[UnittestProto.repeatedFixed64Extension].add(308L)
+        this[UnittestProto.repeatedSfixed32Extension].add(309)
+        this[UnittestProto.repeatedSfixed64Extension].add(310L)
+        this[UnittestProto.repeatedFloatExtension].add(311F)
+        this[UnittestProto.repeatedDoubleExtension].add(312.0)
+        this[UnittestProto.repeatedBoolExtension].add(false)
+        this[UnittestProto.repeatedStringExtension].add("315")
+        this[UnittestProto.repeatedBytesExtension].add(toBytes("316"))
+        this[UnittestProto.repeatedGroupExtension].add(repeatedGroupExtension { a = 317 })
+        this[UnittestProto.repeatedNestedMessageExtension]
+          .add(TestAllTypesKt.nestedMessage { bb = 318 })
+        this[UnittestProto.repeatedForeignMessageExtension].add(foreignMessage { c = 319 })
+        this[UnittestProto.repeatedImportMessageExtension]
+          .add(ImportMessage.newBuilder().setD(320).build())
+        this[UnittestProto.repeatedLazyMessageExtension]
+          .add(TestAllTypesKt.nestedMessage { bb = 327 })
+        this[UnittestProto.repeatedNestedEnumExtension].add(NestedEnum.BAZ)
+        this[UnittestProto.repeatedForeignEnumExtension].add(ForeignEnum.FOREIGN_BAZ)
+        this[UnittestProto.repeatedImportEnumExtension].add(ImportEnum.IMPORT_BAZ)
+        this[UnittestProto.repeatedStringPieceExtension].add("324")
+        this[UnittestProto.repeatedCordExtension].add("325")
+        this[UnittestProto.defaultInt32Extension] = 401
+        this[UnittestProto.defaultInt64Extension] = 402L
+        this[UnittestProto.defaultUint32Extension] = 403
+        this[UnittestProto.defaultUint64Extension] = 404L
+        this[UnittestProto.defaultSint32Extension] = 405
+        this[UnittestProto.defaultSint64Extension] = 406L
+        this[UnittestProto.defaultFixed32Extension] = 407
+        this[UnittestProto.defaultFixed64Extension] = 408L
+        this[UnittestProto.defaultSfixed32Extension] = 409
+        this[UnittestProto.defaultSfixed64Extension] = 410L
+        this[UnittestProto.defaultFloatExtension] = 411F
+        this[UnittestProto.defaultDoubleExtension] = 412.0
+        this[UnittestProto.defaultBoolExtension] = false
+        this[UnittestProto.defaultStringExtension] = "415"
+        this[UnittestProto.defaultBytesExtension] = toBytes("416")
+        this[UnittestProto.defaultNestedEnumExtension] = NestedEnum.FOO
+        this[UnittestProto.defaultForeignEnumExtension] = ForeignEnum.FOREIGN_FOO
+        this[UnittestProto.defaultImportEnumExtension] = ImportEnum.IMPORT_FOO
+        this[UnittestProto.defaultStringPieceExtension] = "424"
+        this[UnittestProto.defaultCordExtension] = "425"
+        this[UnittestProto.oneofUint32Extension] = 601
+        this[UnittestProto.oneofNestedMessageExtension] = TestAllTypesKt.nestedMessage { bb = 602 }
+        this[UnittestProto.oneofStringExtension] = "603"
+        this[UnittestProto.oneofBytesExtension] = toBytes("604")
+      }
+    ).isEqualTo(
+      TestUtil.getAllExtensionsSet()
+    )
+  }
+
+  @Test
+  fun testExtensionGetters() {
+    testAllExtensions {
+      this[UnittestProto.optionalInt32Extension] = 101
+      assertThat(this[UnittestProto.optionalInt32Extension]).isEqualTo(101)
+      this[UnittestProto.optionalStringExtension] = "115"
+      assertThat(this[UnittestProto.optionalStringExtension]).isEqualTo("115")
+      this[UnittestProto.optionalGroupExtension] = optionalGroupExtension { a = 117 }
+      assertThat(this[UnittestProto.optionalGroupExtension])
+        .isEqualTo(optionalGroupExtension { a = 117 })
+      this[UnittestProto.optionalNestedMessageExtension] =
+        TestAllTypesKt.nestedMessage { bb = 118 }
+      assertThat(this[UnittestProto.optionalNestedMessageExtension])
+        .isEqualTo(TestAllTypesKt.nestedMessage { bb = 118 })
+      this[UnittestProto.optionalNestedEnumExtension] = NestedEnum.BAZ
+      assertThat(this[UnittestProto.optionalNestedEnumExtension]).isEqualTo(NestedEnum.BAZ)
+      this[UnittestProto.defaultInt32Extension] = 401
+      assertThat(this[UnittestProto.defaultInt32Extension]).isEqualTo(401)
+      this[UnittestProto.oneofUint32Extension] = 601
+      assertThat(this[UnittestProto.oneofUint32Extension]).isEqualTo(601)
+    }
+  }
+
+  @Test
+  fun testExtensionContains() {
+    testAllExtensions {
+      this[UnittestProto.optionalInt32Extension] = 101
+      assertThat(contains(UnittestProto.optionalInt32Extension)).isTrue()
+      assertThat(contains(UnittestProto.optionalStringExtension)).isFalse()
+      this[UnittestProto.optionalGroupExtension] = optionalGroupExtension { a = 117 }
+      assertThat(contains(UnittestProto.optionalGroupExtension)).isTrue()
+      assertThat(contains(UnittestProto.optionalNestedMessageExtension)).isFalse()
+      this[UnittestProto.optionalNestedEnumExtension] = NestedEnum.BAZ
+      assertThat(contains(UnittestProto.optionalNestedEnumExtension)).isTrue()
+      assertThat(contains(UnittestProto.defaultInt32Extension)).isFalse()
+      this[UnittestProto.oneofUint32Extension] = 601
+      assertThat(contains(UnittestProto.optionalInt32Extension)).isTrue()
+    }
+
+    testAllExtensions {
+      assertThat(contains(UnittestProto.optionalInt32Extension)).isFalse()
+      this[UnittestProto.optionalStringExtension] = "115"
+      assertThat(contains(UnittestProto.optionalStringExtension)).isTrue()
+      assertThat(contains(UnittestProto.optionalGroupExtension)).isFalse()
+      this[UnittestProto.optionalNestedMessageExtension] =
+        TestAllTypesKt.nestedMessage { bb = 118 }
+      assertThat(contains(UnittestProto.optionalNestedMessageExtension)).isTrue()
+      assertThat(contains(UnittestProto.optionalNestedEnumExtension)).isFalse()
+      this[UnittestProto.defaultInt32Extension] = 401
+      assertThat(contains(UnittestProto.defaultInt32Extension)).isTrue()
+      assertThat(contains(UnittestProto.oneofUint32Extension)).isFalse()
+    }
+  }
+
+  @Test
+  fun testExtensionClears() {
+    testAllExtensions {
+      this[UnittestProto.optionalInt32Extension] = 101
+      clear(UnittestProto.optionalInt32Extension)
+      assertThat(contains(UnittestProto.optionalInt32Extension)).isFalse()
+
+      this[UnittestProto.optionalStringExtension] = "115"
+      clear(UnittestProto.optionalStringExtension)
+      assertThat(contains(UnittestProto.optionalStringExtension)).isFalse()
+
+      this[UnittestProto.optionalGroupExtension] = optionalGroupExtension { a = 117 }
+      clear(UnittestProto.optionalGroupExtension)
+      assertThat(contains(UnittestProto.optionalGroupExtension)).isFalse()
+
+      this[UnittestProto.optionalNestedMessageExtension] =
+        TestAllTypesKt.nestedMessage { bb = 118 }
+      clear(UnittestProto.optionalNestedMessageExtension)
+      assertThat(contains(UnittestProto.optionalNestedMessageExtension)).isFalse()
+
+      this[UnittestProto.optionalNestedEnumExtension] = NestedEnum.BAZ
+      clear(UnittestProto.optionalNestedEnumExtension)
+      assertThat(contains(UnittestProto.optionalNestedEnumExtension)).isFalse()
+
+      this[UnittestProto.defaultInt32Extension] = 401
+      clear(UnittestProto.defaultInt32Extension)
+      assertThat(contains(UnittestProto.oneofUint32Extension)).isFalse()
+    }
+  }
+
+  @Test
+  fun testEmptyMessages() {
+    assertThat(
+      testEmptyMessage {}
+    ).isEqualTo(
+      TestEmptyMessage.newBuilder().build()
+    )
+
+    assertThat(
+      testEmptyMessageWithExtensions {}
+    ).isEqualTo(
+      TestEmptyMessageWithExtensions.newBuilder().build()
+    )
+  }
+
+  @Test
+  fun testMapSetters() {
+    val intMap = testIntIntMap { m[1] = 2 }
+    assertThat(intMap).isEqualTo(
+      TestIntIntMap.newBuilder().putM(1, 2).build()
+    )
+
+    assertThat(
+      testMaps {
+        mInt32[1] = intMap
+        mInt64[1L] = intMap
+        mUint32[1] = intMap
+        mUint64[1L] = intMap
+        mSint32[1] = intMap
+        mSint64[1L] = intMap
+        mFixed32[1] = intMap
+        mFixed64[1L] = intMap
+        mSfixed32[1] = intMap
+        mSfixed64[1] = intMap
+        mBool[true] = intMap
+        mString["1"] = intMap
+      }
+    ).isEqualTo(
+      TestMaps.newBuilder()
+        .putMInt32(1, intMap)
+        .putMInt64(1L, intMap)
+        .putMUint32(1, intMap)
+        .putMUint64(1L, intMap)
+        .putMSint32(1, intMap)
+        .putMSint64(1L, intMap)
+        .putMFixed32(1, intMap)
+        .putMFixed64(1L, intMap)
+        .putMSfixed32(1, intMap)
+        .putMSfixed64(1L, intMap)
+        .putMBool(true, intMap)
+        .putMString("1", intMap)
+        .build()
+    )
+
+    assertThat(
+      testEnumMap {
+        knownMapField[1] = Proto2MapEnum.PROTO2_MAP_ENUM_FOO
+      }
+    ).isEqualTo(
+      TestEnumMap.newBuilder()
+        .putKnownMapField(1, Proto2MapEnum.PROTO2_MAP_ENUM_FOO)
+        .build()
+    )
+  }
+
+  @Test
+  fun testMapGettersAndSetters() {
+    val intMap =
+      testIntIntMap {
+        m.put(1, 2)
+        assertThat(m).isEqualTo(mapOf(1 to 2))
+        m[3] = 4
+        assertThat(m).isEqualTo(mapOf(1 to 2, 3 to 4))
+        m.putAll(mapOf(5 to 6, 7 to 8))
+        assertThat(m).isEqualTo(mapOf(1 to 2, 3 to 4, 5 to 6, 7 to 8))
+      }
+
+    testMaps {
+      mInt32.put(1, intMap)
+      assertThat(mInt32).isEqualTo(mapOf(1 to intMap))
+      mInt32[2] = intMap
+      assertThat(mInt32).isEqualTo(mapOf(1 to intMap, 2 to intMap))
+      mInt32.putAll(mapOf(3 to intMap, 4 to intMap))
+      assertThat(mInt32).isEqualTo(mapOf(1 to intMap, 2 to intMap, 3 to intMap, 4 to intMap))
+
+      mString.put("1", intMap)
+      assertThat(mString).isEqualTo(mapOf("1" to intMap))
+      mString["2"] = intMap
+      assertThat(mString).isEqualTo(mapOf("1" to intMap, "2" to intMap))
+      mString.putAll(mapOf("3" to intMap, "4" to intMap))
+      assertThat(mString).isEqualTo(
+        mapOf("1" to intMap, "2" to intMap, "3" to intMap, "4" to intMap)
+      )
+    }
+
+    testEnumMap {
+      knownMapField.put(1, Proto2MapEnum.PROTO2_MAP_ENUM_FOO)
+      assertThat(knownMapField).isEqualTo(mapOf(1 to Proto2MapEnum.PROTO2_MAP_ENUM_FOO))
+      knownMapField[2] = Proto2MapEnum.PROTO2_MAP_ENUM_BAR
+      assertThat(knownMapField).isEqualTo(
+        mapOf(1 to Proto2MapEnum.PROTO2_MAP_ENUM_FOO, 2 to Proto2MapEnum.PROTO2_MAP_ENUM_BAR)
+      )
+      knownMapField.putAll(
+        mapOf(3 to Proto2MapEnum.PROTO2_MAP_ENUM_BAZ, 4 to Proto2MapEnum.PROTO2_MAP_ENUM_FOO)
+      )
+      assertThat(knownMapField).isEqualTo(
+        mapOf(
+          1 to Proto2MapEnum.PROTO2_MAP_ENUM_FOO,
+          2 to Proto2MapEnum.PROTO2_MAP_ENUM_BAR,
+          3 to Proto2MapEnum.PROTO2_MAP_ENUM_BAZ,
+          4 to Proto2MapEnum.PROTO2_MAP_ENUM_FOO
+        )
+      )
+    }
+  }
+
+  @Test
+  fun testMapRemove() {
+    val intMap =
+      testIntIntMap {
+        m.putAll(mapOf(1 to 2, 3 to 4))
+        m.remove(1)
+        assertThat(m).isEqualTo(mapOf(3 to 4))
+      }
+
+    testMaps {
+      mInt32.putAll(mapOf(1 to intMap, 2 to intMap))
+      mInt32.remove(1)
+      assertThat(mInt32).isEqualTo(mapOf(2 to intMap))
+
+      mString.putAll(mapOf("1" to intMap, "2" to intMap))
+      mString.remove("1")
+      assertThat(mString).isEqualTo(mapOf("2" to intMap))
+    }
+
+    testEnumMap {
+      knownMapField.putAll(
+        mapOf(1 to Proto2MapEnum.PROTO2_MAP_ENUM_FOO, 2 to Proto2MapEnum.PROTO2_MAP_ENUM_BAR)
+      )
+      knownMapField.remove(1)
+      assertThat(knownMapField).isEqualTo(mapOf(2 to Proto2MapEnum.PROTO2_MAP_ENUM_BAR))
+    }
+  }
+
+  @Test
+  fun testMapClear() {
+    val intMap =
+      testIntIntMap {
+        m.putAll(mapOf(1 to 2, 3 to 4))
+        m.clear()
+        assertThat(m.isEmpty()).isTrue()
+      }
+
+    testMaps {
+      mInt32.putAll(mapOf(1 to intMap, 2 to intMap))
+      mInt32.clear()
+      assertThat(mInt32.isEmpty()).isTrue()
+
+      mString.putAll(mapOf("1" to intMap, "2" to intMap))
+      mString.clear()
+      assertThat(mString.isEmpty()).isTrue()
+    }
+
+    testEnumMap {
+      knownMapField.putAll(
+        mapOf(1 to Proto2MapEnum.PROTO2_MAP_ENUM_FOO, 2 to Proto2MapEnum.PROTO2_MAP_ENUM_BAR)
+      )
+      knownMapField.clear()
+      assertThat(knownMapField.isEmpty()).isTrue()
+    }
+  }
+
+  @Test
+  fun testEvilNames() {
+    assertThat(
+      evilNamesProto2 {
+        initialized = true
+        hasFoo = true
+        bar = "foo"
+        isInitialized = true
+        fooBar = "foo"
+        aLLCAPS += "foo"
+        aLLCAPSMAP[1] = true
+        hasUnderbarPrecedingNumeric1Foo = true
+        hasUnderbarPrecedingNumeric42Bar = true
+        hasUnderbarPrecedingNumeric123Foo42BarBaz = true
+        extension += "foo"
+        class_ += 1
+        int = 1.0
+        long = true
+        boolean = 1L
+        sealed = "foo"
+        interface_ = 1F
+        in_ = 1
+        object_ = "foo"
+        cachedSize_ = "foo"
+        serializedSize_ = true
+        by = "foo"
+      }
+    ).isEqualTo(
+      EvilNamesProto2.newBuilder()
+        .setInitialized(true)
+        .setHasFoo(true)
+        .setBar("foo")
+        .setIsInitialized(true)
+        .setFooBar("foo")
+        .addALLCAPS("foo")
+        .putALLCAPSMAP(1, true)
+        .setHasUnderbarPrecedingNumeric1Foo(true)
+        .setHasUnderbarPrecedingNumeric42Bar(true)
+        .setHasUnderbarPrecedingNumeric123Foo42BarBaz(true)
+        .addExtension("foo")
+        .addClass_(1)
+        .setInt(1.0)
+        .setLong(true)
+        .setBoolean(1L)
+        .setSealed("foo")
+        .setInterface(1F)
+        .setIn(1)
+        .setObject("foo")
+        .setCachedSize_("foo")
+        .setSerializedSize_(true)
+        .setBy("foo")
+        .build()
+    )
+
+    assertThat(interface_ {}).isEqualTo(Interface.newBuilder().build())
+  }
+
+  @Test
+  fun testHardKeywordGettersAndSetters() {
+    hardKeywordsAllTypes {
+      as_ = 1
+      assertThat(as_).isEqualTo(1)
+
+      in_ = "foo"
+      assertThat(in_).isEqualTo("foo")
+
+      break_ = HardKeywordsAllTypes.NestedEnum.FOO
+      assertThat(break_).isEqualTo(HardKeywordsAllTypes.NestedEnum.FOO)
+
+      do_ = HardKeywordsAllTypesKt.nestedMessage { while_ = 1 }
+      assertThat(do_).isEqualTo(HardKeywordsAllTypesKt.nestedMessage { while_ = 1 })
+
+      continue_[1] = 1
+      assertThat(continue_[1]).isEqualTo(1)
+
+      else_ += 1
+      assertThat(else_).isEqualTo(listOf(1))
+
+      for_ += "foo"
+      assertThat(for_).isEqualTo(listOf("foo"))
+
+      fun_ += HardKeywordsAllTypes.NestedEnum.FOO
+      assertThat(fun_).isEqualTo(listOf(HardKeywordsAllTypes.NestedEnum.FOO))
+
+      if_ += HardKeywordsAllTypesKt.nestedMessage { while_ = 1 }
+      assertThat(if_).isEqualTo(listOf(HardKeywordsAllTypesKt.nestedMessage { while_ = 1 }))
+    }
+  }
+
+  @Test
+  fun testHardKeywordHazzers() {
+    hardKeywordsAllTypes {
+      as_ = 1
+      assertThat(hasAs_()).isTrue()
+
+      in_ = "foo"
+      assertThat(hasIn_()).isTrue()
+
+      break_ = HardKeywordsAllTypes.NestedEnum.FOO
+      assertThat(hasBreak_()).isTrue()
+
+      do_ = HardKeywordsAllTypesKt.nestedMessage { while_ = 1 }
+      assertThat(hasDo_()).isTrue()
+    }
+  }
+
+  @Test
+  fun testHardKeywordClears() {
+    hardKeywordsAllTypes {
+      as_ = 1
+      clearAs_()
+      assertThat(hasAs_()).isFalse()
+
+      in_ = "foo"
+      clearIn_()
+      assertThat(hasIn_()).isFalse()
+
+      break_ = HardKeywordsAllTypes.NestedEnum.FOO
+      clearBreak_()
+      assertThat(hasBreak_()).isFalse()
+
+      do_ = HardKeywordsAllTypesKt.nestedMessage { while_ = 1 }
+      clearDo_()
+      assertThat(hasDo_()).isFalse()
+    }
+  }
+}
diff --git a/java/kotlin/src/test/kotlin/com/google/protobuf/Proto3Test.kt b/java/kotlin/src/test/kotlin/com/google/protobuf/Proto3Test.kt
new file mode 100644
index 0000000..b3fd336
--- /dev/null
+++ b/java/kotlin/src/test/kotlin/com/google/protobuf/Proto3Test.kt
@@ -0,0 +1,340 @@
+package com.google.protobuf.kotlin
+
+import com.google.common.truth.Truth.assertThat
+import evil_names_proto3.EvilNamesProto3OuterClass.EvilNamesProto3
+import evil_names_proto3.EvilNamesProto3OuterClass.HardKeywordsAllTypes
+import evil_names_proto3.EvilNamesProto3OuterClass.Class
+import evil_names_proto3.HardKeywordsAllTypesKt
+import evil_names_proto3.class_
+import evil_names_proto3.evilNamesProto3
+import evil_names_proto3.hardKeywordsAllTypes
+import multiple_files_proto3.MultipleFilesMessageA
+import multiple_files_proto3.MultipleFilesMessageB
+import multiple_files_proto3.multipleFilesMessageA
+import multiple_files_proto3.multipleFilesMessageB
+import proto3_unittest.UnittestProto3
+import proto3_unittest.TestAllTypesKt
+import proto3_unittest.TestAllTypesKt.nestedMessage
+import proto3_unittest.UnittestProto3.TestAllTypes
+import proto3_unittest.UnittestProto3.TestAllTypes.NestedEnum
+import proto3_unittest.UnittestProto3.TestEmptyMessage
+import proto3_unittest.copy
+import proto3_unittest.testAllTypes
+import proto3_unittest.testEmptyMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class Proto3Test {
+  @Test
+  fun testGettersAndSetters() {
+    testAllTypes {
+      optionalInt32 = 101
+      assertThat(optionalInt32).isEqualTo(101)
+      optionalString = "115"
+      assertThat(optionalString).isEqualTo("115")
+      optionalNestedMessage = TestAllTypesKt.nestedMessage { bb = 118 }
+      assertThat(optionalNestedMessage).isEqualTo(TestAllTypesKt.nestedMessage { bb = 118 })
+      optionalNestedEnum = NestedEnum.BAZ
+      assertThat(optionalNestedEnum).isEqualTo(NestedEnum.BAZ)
+      oneofUint32 = 601
+      assertThat(oneofUint32).isEqualTo(601)
+    }
+  }
+
+  @Test
+  fun testRepeatedGettersAndSetters() {
+    testAllTypes {
+      repeatedInt32.addAll(listOf(1, 2))
+      assertThat(repeatedInt32).isEqualTo(listOf(1, 2))
+      repeatedInt32 += listOf(3, 4)
+      assertThat(repeatedInt32).isEqualTo(listOf(1, 2, 3, 4))
+      repeatedInt32[0] = 5
+      assertThat(repeatedInt32).isEqualTo(listOf(5, 2, 3, 4))
+
+      repeatedString.addAll(listOf("1", "2"))
+      assertThat(repeatedString).isEqualTo(listOf("1", "2"))
+      repeatedString += listOf("3", "4")
+      assertThat(repeatedString).isEqualTo(listOf("1", "2", "3", "4"))
+      repeatedString[0] = "5"
+      assertThat(repeatedString).isEqualTo(listOf("5", "2", "3", "4"))
+
+      repeatedNestedMessage.addAll(listOf(nestedMessage { bb = 1 }, nestedMessage { bb = 2 }))
+      assertThat(repeatedNestedMessage).isEqualTo(
+        listOf(
+          nestedMessage { bb = 1 },
+          nestedMessage { bb = 2 }
+        )
+      )
+      repeatedNestedMessage += listOf(nestedMessage { bb = 3 }, nestedMessage { bb = 4 })
+      assertThat(repeatedNestedMessage).isEqualTo(
+        listOf(
+          nestedMessage { bb = 1 },
+          nestedMessage { bb = 2 },
+          nestedMessage { bb = 3 },
+          nestedMessage { bb = 4 }
+        )
+      )
+      repeatedNestedMessage[0] = nestedMessage { bb = 5 }
+      assertThat(repeatedNestedMessage).isEqualTo(
+        listOf(
+          nestedMessage { bb = 5 },
+          nestedMessage { bb = 2 },
+          nestedMessage { bb = 3 },
+          nestedMessage { bb = 4 }
+        )
+      )
+
+      repeatedNestedEnum.addAll(listOf(NestedEnum.FOO, NestedEnum.BAR))
+      assertThat(repeatedNestedEnum).isEqualTo(listOf(NestedEnum.FOO, NestedEnum.BAR))
+      repeatedNestedEnum += listOf(NestedEnum.BAZ, NestedEnum.FOO)
+      assertThat(repeatedNestedEnum).isEqualTo(
+        listOf(NestedEnum.FOO, NestedEnum.BAR, NestedEnum.BAZ, NestedEnum.FOO)
+      )
+      repeatedNestedEnum[0] = NestedEnum.BAR
+      assertThat(repeatedNestedEnum).isEqualTo(
+        listOf(NestedEnum.BAR, NestedEnum.BAR, NestedEnum.BAZ, NestedEnum.FOO)
+      )
+    }
+  }
+
+  @Test
+  fun testClears() {
+    assertThat(
+      testAllTypes {
+        optionalInt32 = 101
+        clearOptionalInt32()
+
+        optionalString = "115"
+        clearOptionalString()
+
+        optionalNestedMessage = TestAllTypesKt.nestedMessage { bb = 118 }
+        clearOptionalNestedMessage()
+
+        optionalNestedEnum = NestedEnum.BAZ
+        clearOptionalNestedEnum()
+
+        oneofUint32 = 601
+        clearOneofUint32()
+      }
+    ).isEqualTo(
+      TestAllTypes.newBuilder().build()
+    )
+  }
+
+  @Test
+  fun testCopy() {
+    val message = testAllTypes {
+      optionalInt32 = 101
+      optionalString = "115"
+    }
+    val modifiedMessage = message.copy {
+      optionalInt32 = 201
+    }
+
+    assertThat(message).isEqualTo(
+      TestAllTypes.newBuilder()
+        .setOptionalInt32(101)
+        .setOptionalString("115")
+        .build()
+    )
+    assertThat(modifiedMessage).isEqualTo(
+      TestAllTypes.newBuilder()
+        .setOptionalInt32(201)
+        .setOptionalString("115")
+        .build()
+    )
+  }
+
+  @Test
+  fun testOneof() {
+    val message = testAllTypes {
+      oneofString = "foo"
+      assertThat(oneofFieldCase)
+        .isEqualTo(TestAllTypes.OneofFieldCase.ONEOF_STRING)
+      assertThat(oneofString).isEqualTo("foo")
+      clearOneofField()
+      assertThat(oneofFieldCase)
+        .isEqualTo(TestAllTypes.OneofFieldCase.ONEOFFIELD_NOT_SET)
+      oneofUint32 = 5
+    }
+
+    assertThat(message.getOneofFieldCase())
+      .isEqualTo(TestAllTypes.OneofFieldCase.ONEOF_UINT32)
+    assertThat(message.getOneofUint32()).isEqualTo(5)
+  }
+
+  @Test
+  fun testEmptyMessages() {
+    assertThat(
+      testEmptyMessage {}
+    ).isEqualTo(
+      TestEmptyMessage.newBuilder().build()
+    )
+  }
+
+  @Test
+  fun testEvilNames() {
+    assertThat(
+      evilNamesProto3 {
+        initialized = true
+        hasFoo = true
+        bar = "foo"
+        isInitialized = true
+        fooBar = "foo"
+        aLLCAPS += "foo"
+        aLLCAPSMAP[1] = true
+        hasUnderbarPrecedingNumeric1Foo = true
+        hasUnderbarPrecedingNumeric42Bar = true
+        hasUnderbarPrecedingNumeric123Foo42BarBaz = true
+        extension += "foo"
+        class_ = "foo"
+        int = 1.0
+        long = true
+        boolean = 1L
+        sealed = "foo"
+        interface_ = 1F
+        in_ = 1
+        object_ = "foo"
+        cachedSize_ = "foo"
+        serializedSize_ = true
+        value = "foo"
+        index = 1L
+        values += "foo"
+        newValues += "foo"
+        builder = true
+        k[1] = 1
+        v["foo"] = "foo"
+        key["foo"] = 1
+        map[1] = "foo"
+        pairs["foo"] = 1
+        LeadingUnderscore = "foo"
+        option = 1
+      }
+    ).isEqualTo(
+      EvilNamesProto3.newBuilder()
+        .setInitialized(true)
+        .setHasFoo(true)
+        .setBar("foo")
+        .setIsInitialized(true)
+        .setFooBar("foo")
+        .addALLCAPS("foo")
+        .putALLCAPSMAP(1, true)
+        .setHasUnderbarPrecedingNumeric1Foo(true)
+        .setHasUnderbarPrecedingNumeric42Bar(true)
+        .setHasUnderbarPrecedingNumeric123Foo42BarBaz(true)
+        .addExtension("foo")
+        .setClass_("foo")
+        .setInt(1.0)
+        .setLong(true)
+        .setBoolean(1L)
+        .setSealed("foo")
+        .setInterface(1F)
+        .setIn(1)
+        .setObject("foo")
+        .setCachedSize_("foo")
+        .setSerializedSize_(true)
+        .setValue("foo")
+        .setIndex(1L)
+        .addValues("foo")
+        .addNewValues("foo")
+        .setBuilder(true)
+        .putK(1, 1)
+        .putV("foo", "foo")
+        .putKey("foo", 1)
+        .putMap(1, "foo")
+        .putPairs("foo", 1)
+        .setLeadingUnderscore("foo")
+        .setOption(1)
+        .build()
+    )
+
+    assertThat(class_ {}).isEqualTo(Class.newBuilder().build())
+  }
+
+  @Test
+  fun testHardKeywordGettersAndSetters() {
+    hardKeywordsAllTypes {
+      as_ = 1
+      assertThat(as_).isEqualTo(1)
+
+      in_ = "foo"
+      assertThat(in_).isEqualTo("foo")
+
+      break_ = HardKeywordsAllTypes.NestedEnum.FOO
+      assertThat(break_).isEqualTo(HardKeywordsAllTypes.NestedEnum.FOO)
+
+      do_ = HardKeywordsAllTypesKt.nestedMessage { while_ = 1 }
+      assertThat(do_).isEqualTo(HardKeywordsAllTypesKt.nestedMessage { while_ = 1 })
+
+      continue_[1] = 1
+      assertThat(continue_[1]).isEqualTo(1)
+
+      else_ += 1
+      assertThat(else_).isEqualTo(listOf(1))
+
+      for_ += "foo"
+      assertThat(for_).isEqualTo(listOf("foo"))
+
+      fun_ += HardKeywordsAllTypes.NestedEnum.FOO
+      assertThat(fun_).isEqualTo(listOf(HardKeywordsAllTypes.NestedEnum.FOO))
+
+      if_ += HardKeywordsAllTypesKt.nestedMessage { while_ = 1 }
+      assertThat(if_).isEqualTo(listOf(HardKeywordsAllTypesKt.nestedMessage { while_ = 1 }))
+    }
+  }
+
+  @Test
+  fun testHardKeywordHazzers() {
+    hardKeywordsAllTypes {
+      as_ = 1
+      assertThat(hasAs_()).isTrue()
+
+      in_ = "foo"
+      assertThat(hasIn_()).isTrue()
+
+      break_ = HardKeywordsAllTypes.NestedEnum.FOO
+      assertThat(hasBreak_()).isTrue()
+
+      do_ = HardKeywordsAllTypesKt.nestedMessage { while_ = 1 }
+      assertThat(hasDo_()).isTrue()
+    }
+  }
+
+  @Test
+  fun testHardKeywordClears() {
+    hardKeywordsAllTypes {
+      as_ = 1
+      clearAs_()
+      assertThat(hasAs_()).isFalse()
+
+      in_ = "foo"
+      clearIn_()
+      assertThat(hasIn_()).isFalse()
+
+      break_ = HardKeywordsAllTypes.NestedEnum.FOO
+      clearBreak_()
+      assertThat(hasBreak_()).isFalse()
+
+      do_ = HardKeywordsAllTypesKt.nestedMessage { while_ = 1 }
+      clearDo_()
+      assertThat(hasDo_()).isFalse()
+    }
+  }
+
+  @Test
+  fun testMultipleFiles() {
+    assertThat(
+      multipleFilesMessageA {}
+    ).isEqualTo(
+      MultipleFilesMessageA.newBuilder().build()
+    )
+
+    assertThat(
+      multipleFilesMessageB {}
+    ).isEqualTo(
+      MultipleFilesMessageB.newBuilder().build()
+    )
+  }
+}
diff --git a/java/kotlin/src/test/proto/com/google/protobuf/evil_names_proto2.proto b/java/kotlin/src/test/proto/com/google/protobuf/evil_names_proto2.proto
new file mode 100644
index 0000000..70d813a
--- /dev/null
+++ b/java/kotlin/src/test/proto/com/google/protobuf/evil_names_proto2.proto
@@ -0,0 +1,62 @@
+syntax = "proto2";
+
+package evil_names_proto2;
+
+option java_package = "evil_names_proto2";
+
+message EvilNamesProto2 {
+  optional bool initialized = 1;
+  optional bool has_foo = 2;
+  optional string Bar = 3;
+  optional bool is_initialized = 4;
+
+  oneof camelCase {
+    string fooBar = 5;
+  }
+
+  repeated string ALL_CAPS = 7;
+  map<int32, bool> ALL_CAPS_MAP = 8;
+
+  optional bool has_underbar_preceding_numeric_1foo = 9;
+  optional bool has_underbar_preceding_numeric_42bar = 13;
+  optional bool has_underbar_preceding_numeric_123foo42bar_baz = 14;
+
+  extensions 100 to max;
+
+  repeated string extension = 12;
+  repeated int32 class = 15;
+  optional double int = 16;
+  optional bool long = 17;
+  optional int64 boolean = 18;
+  optional string sealed = 19;
+  optional float interface = 20;
+  optional int32 in = 21;
+  optional string object = 22;
+  optional string cached_size = 23;
+  optional bool serialized_size = 24;
+  optional string by = 25;
+}
+
+message HardKeywordsAllTypes {
+  message NestedMessage {
+    optional int32 while = 1;
+  }
+
+  enum NestedEnum {
+    FOO = 1;
+    BAR = 2;
+  }
+
+  optional int32 as = 1;
+  optional string in = 2;
+  optional NestedEnum break = 3;
+  map<int32, int32> continue = 4;
+  optional NestedMessage do = 5;
+
+  repeated int32 else = 6;
+  repeated string for = 7;
+  repeated NestedEnum fun = 8;
+  repeated NestedMessage if = 9;
+}
+
+message Interface {}
diff --git a/java/kotlin/src/test/proto/com/google/protobuf/evil_names_proto3.proto b/java/kotlin/src/test/proto/com/google/protobuf/evil_names_proto3.proto
new file mode 100644
index 0000000..1b40e4d
--- /dev/null
+++ b/java/kotlin/src/test/proto/com/google/protobuf/evil_names_proto3.proto
@@ -0,0 +1,76 @@
+syntax = "proto3";
+
+package evil_names_proto3;
+
+option java_package = "evil_names_proto3";
+
+message EvilNamesProto3 {
+  bool initialized = 1;
+  bool has_foo = 2;
+  string Bar = 3;
+  bool is_initialized = 4;
+
+  oneof camelCase {
+    string fooBar = 5;
+  }
+
+  repeated string ALL_CAPS = 7;
+  map<int32, bool> ALL_CAPS_MAP = 8;
+
+  bool has_underbar_preceding_numeric_1foo = 9;
+  bool has_underbar_preceding_numeric_42bar = 10;
+  bool has_underbar_preceding_numeric_123foo42bar_baz = 11;
+
+  repeated string extension = 12;
+
+  string class = 13;
+  double int = 14;
+  bool long = 15;
+  int64 boolean = 16;
+  string sealed = 17;
+  float interface = 18;
+  int32 in = 19;
+  string object = 20;
+  string cached_size = 21;
+  bool serialized_size = 22;
+  string value = 23;
+  int64 index = 24;
+  repeated string values = 25;
+  repeated string new_values = 26;
+  bool builder = 27;
+  map<int32, int32> k = 28;
+  map<string, string> v = 29;
+  map<string, int32> key = 30;
+  map<int32, string> map = 31;
+  map<string, int32> pairs = 32;
+
+  string _leading_underscore = 33;
+  oneof _leading_underscore_oneof {
+    int32 option = 34;
+  }
+}
+
+message HardKeywordsAllTypes {
+  message NestedMessage {
+    optional int32 while = 1;
+  }
+
+  enum NestedEnum {
+    ZERO = 0;
+    FOO = 1;
+    BAR = 2;
+  }
+
+  optional int32 as = 1;
+  optional string in = 2;
+  optional NestedEnum break = 3;
+  map<int32, int32> continue = 4;
+  optional NestedMessage do = 5;
+
+  repeated int32 else = 6;
+  repeated string for = 7;
+  repeated NestedEnum fun = 8;
+  repeated NestedMessage if = 9;
+}
+
+message Class {}
diff --git a/java/kotlin/src/test/proto/com/google/protobuf/example_extensible_message.proto b/java/kotlin/src/test/proto/com/google/protobuf/example_extensible_message.proto
new file mode 100644
index 0000000..14f18db
--- /dev/null
+++ b/java/kotlin/src/test/proto/com/google/protobuf/example_extensible_message.proto
@@ -0,0 +1,16 @@
+syntax = "proto2";
+
+package example_extensible_message;
+
+option java_package = "example_extensible_message";
+option java_multiple_files = true;
+
+message ExampleExtensibleMessage {
+  extensions 10 to 20;
+}
+
+extend ExampleExtensibleMessage {
+  repeated int32 repeated_extension = 10;
+  repeated int32 different_extension = 11;
+  optional int32 int32_extension = 12;
+}
diff --git a/java/kotlin/src/test/proto/com/google/protobuf/multiple_files_proto3.proto b/java/kotlin/src/test/proto/com/google/protobuf/multiple_files_proto3.proto
new file mode 100644
index 0000000..82211cc
--- /dev/null
+++ b/java/kotlin/src/test/proto/com/google/protobuf/multiple_files_proto3.proto
@@ -0,0 +1,12 @@
+syntax = "proto3";
+
+package multiple_files_proto3;
+
+option java_package = "multiple_files_proto3";
+option java_multiple_files = true;
+
+enum NestedEnum { FOO = 0; }
+
+message MultipleFilesMessageA {}
+
+message MultipleFilesMessageB {}
diff --git a/src/Makefile.am b/src/Makefile.am
index e67f37f..ccae399 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -150,6 +150,7 @@
   google/protobuf/compiler/csharp/csharp_generator.h             \
   google/protobuf/compiler/csharp/csharp_names.h                 \
   google/protobuf/compiler/java/java_generator.h                 \
+  google/protobuf/compiler/java/java_kotlin_generator.h          \
   google/protobuf/compiler/java/java_names.h                     \
   google/protobuf/compiler/js/js_generator.h                     \
   google/protobuf/compiler/js/well_known_types_embed.h           \
@@ -374,6 +375,7 @@
   google/protobuf/compiler/java/java_generator_factory.h       \
   google/protobuf/compiler/java/java_helpers.cc                \
   google/protobuf/compiler/java/java_helpers.h                 \
+  google/protobuf/compiler/java/java_kotlin_generator.cc       \
   google/protobuf/compiler/java/java_map_field.cc              \
   google/protobuf/compiler/java/java_map_field.h               \
   google/protobuf/compiler/java/java_map_field_lite.cc         \
diff --git a/src/google/protobuf/compiler/java/java_enum_field.cc b/src/google/protobuf/compiler/java/java_enum_field.cc
index 9a0799e..e68eb77 100644
--- a/src/google/protobuf/compiler/java/java_enum_field.cc
+++ b/src/google/protobuf/compiler/java/java_enum_field.cc
@@ -63,6 +63,7 @@
 
   (*variables)["type"] =
       name_resolver->GetImmutableClassName(descriptor->enum_type());
+  (*variables)["kt_type"] = (*variables)["type"];
   (*variables)["mutable_type"] =
       name_resolver->GetMutableClassName(descriptor->enum_type());
   (*variables)["default"] = ImmutableDefaultValue(descriptor, name_resolver);
@@ -76,6 +77,11 @@
   // by the proto compiler
   (*variables)["deprecation"] =
       descriptor->options().deprecated() ? "@java.lang.Deprecated " : "";
+  (*variables)["kt_deprecation"] =
+      descriptor->options().deprecated()
+          ? "@kotlin.Deprecated(message = \"Field " + (*variables)["name"] +
+                " is deprecated\") "
+          : "";
   (*variables)["on_changed"] = "onChanged();";
   // Use deprecated valueOf() method to be compatible with old generated code
   // for v2.5.0/v2.6.1.
@@ -270,6 +276,34 @@
   printer->Annotate("{", "}", descriptor_);
 }
 
+void ImmutableEnumFieldGenerator::GenerateKotlinDslMembers(
+    io::Printer* printer) const {
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(variables_,
+                 "$kt_deprecation$var $kt_name$: $kt_type$\n"
+                 "  @JvmName(\"${$get$kt_capitalized_name$$}$\")\n"
+                 "  get() = $kt_dsl_builder$.${$get$capitalized_name$$}$()\n"
+                 "  @JvmName(\"${$set$kt_capitalized_name$$}$\")\n"
+                 "  set(value) {\n"
+                 "    $kt_dsl_builder$.${$set$capitalized_name$$}$(value)\n"
+                 "  }\n");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "fun ${$clear$kt_capitalized_name$$}$() {\n"
+                 "  $kt_dsl_builder$.${$clear$capitalized_name$$}$()\n"
+                 "}\n");
+
+  if (HasHazzer(descriptor_)) {
+    WriteFieldAccessorDocComment(printer, descriptor_, HAZZER);
+    printer->Print(variables_,
+                   "fun ${$has$kt_capitalized_name$$}$(): kotlin.Boolean {\n"
+                   "  return $kt_dsl_builder$.${$has$capitalized_name$$}$()\n"
+                   "}\n");
+  }
+}
+
 void ImmutableEnumFieldGenerator::GenerateFieldBuilderInitializationCode(
     io::Printer* printer) const {
   // noop for enums
@@ -1037,6 +1071,98 @@
                  "}\n");
 }
 
+void RepeatedImmutableEnumFieldGenerator::GenerateKotlinDslMembers(
+    io::Printer* printer) const {
+  printer->Print(
+      variables_,
+      "/**\n"
+      " * An uninstantiable, behaviorless type to represent the field in\n"
+      " * generics.\n"
+      " */\n"
+      "@kotlin.OptIn"
+      "(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)\n"
+      "class ${$$kt_capitalized_name$Proxy$}$ private constructor()"
+      " : com.google.protobuf.kotlin.DslProxy()\n");
+
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(variables_,
+                 "$kt_deprecation$ val $kt_name$: "
+                 "com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
+                 "  @kotlin.jvm.JvmSynthetic\n"
+                 "  get() = com.google.protobuf.kotlin.DslList(\n"
+                 "    $kt_dsl_builder$.${$get$capitalized_name$List$}$()\n"
+                 "  )\n");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"add$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "add(value: $kt_type$) {\n"
+                 "  $kt_dsl_builder$.${$add$capitalized_name$$}$(value)\n"
+                 "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"plusAssign$kt_capitalized_name$\")\n"
+                 "inline operator fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "plusAssign(value: $kt_type$) {\n"
+                 "  add(value)\n"
+                 "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_MULTI_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"addAll$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "addAll(values: kotlin.collections.Iterable<$kt_type$>) {\n"
+                 "  $kt_dsl_builder$.${$addAll$capitalized_name$$}$(values)\n"
+                 "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_MULTI_ADDER,
+                               /* builder */ false);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.jvm.JvmName(\"plusAssignAll$kt_capitalized_name$\")\n"
+      "inline operator fun com.google.protobuf.kotlin.DslList"
+      "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+      "plusAssign(values: kotlin.collections.Iterable<$kt_type$>) {\n"
+      "  addAll(values)\n"
+      "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_INDEXED_SETTER,
+                               /* builder */ false);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.jvm.JvmName(\"set$kt_capitalized_name$\")\n"
+      "operator fun com.google.protobuf.kotlin.DslList"
+      "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+      "set(index: kotlin.Int, value: $kt_type$) {\n"
+      "  $kt_dsl_builder$.${$set$capitalized_name$$}$(index, value)\n"
+      "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"clear$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "clear() {\n"
+                 "  $kt_dsl_builder$.${$clear$capitalized_name$$}$()\n"
+                 "}");
+}
+
 std::string RepeatedImmutableEnumFieldGenerator::GetBoxedType() const {
   return name_resolver_->GetImmutableClassName(descriptor_->enum_type());
 }
diff --git a/src/google/protobuf/compiler/java/java_enum_field.h b/src/google/protobuf/compiler/java/java_enum_field.h
index 95c7db5..a8cd033 100644
--- a/src/google/protobuf/compiler/java/java_enum_field.h
+++ b/src/google/protobuf/compiler/java/java_enum_field.h
@@ -80,6 +80,7 @@
   void GenerateFieldBuilderInitializationCode(io::Printer* printer) const;
   void GenerateEqualsCode(io::Printer* printer) const;
   void GenerateHashCode(io::Printer* printer) const;
+  void GenerateKotlinDslMembers(io::Printer* printer) const;
 
   std::string GetBoxedType() const;
 
@@ -138,6 +139,7 @@
   void GenerateFieldBuilderInitializationCode(io::Printer* printer) const;
   void GenerateEqualsCode(io::Printer* printer) const;
   void GenerateHashCode(io::Printer* printer) const;
+  void GenerateKotlinDslMembers(io::Printer* printer) const;
 
   std::string GetBoxedType() const;
 
diff --git a/src/google/protobuf/compiler/java/java_enum_field_lite.cc b/src/google/protobuf/compiler/java/java_enum_field_lite.cc
index 8403f95..dd442fe 100644
--- a/src/google/protobuf/compiler/java/java_enum_field_lite.cc
+++ b/src/google/protobuf/compiler/java/java_enum_field_lite.cc
@@ -70,6 +70,7 @@
 
   (*variables)["type"] =
       name_resolver->GetImmutableClassName(descriptor->enum_type());
+  (*variables)["kt_type"] = (*variables)["type"];
   (*variables)["mutable_type"] =
       name_resolver->GetMutableClassName(descriptor->enum_type());
   (*variables)["default"] = ImmutableDefaultValue(descriptor, name_resolver);
@@ -83,6 +84,11 @@
   // by the proto compiler
   (*variables)["deprecation"] =
       descriptor->options().deprecated() ? "@java.lang.Deprecated " : "";
+  (*variables)["kt_deprecation"] =
+      descriptor->options().deprecated()
+          ? "@kotlin.Deprecated(message = \"Field " + (*variables)["name"] +
+                " is deprecated\") "
+          : "";
   (*variables)["required"] = descriptor->is_required() ? "true" : "false";
 
   if (HasHasbit(descriptor)) {
@@ -275,6 +281,34 @@
   printer->Annotate("{", "}", descriptor_);
 }
 
+void ImmutableEnumFieldLiteGenerator::GenerateKotlinDslMembers(
+    io::Printer* printer) const {
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(variables_,
+                 "$kt_deprecation$var $kt_name$: $kt_type$\n"
+                 "  @JvmName(\"${$get$kt_capitalized_name$$}$\")\n"
+                 "  get() = $kt_dsl_builder$.${$get$capitalized_name$$}$()\n"
+                 "  @JvmName(\"${$set$kt_capitalized_name$$}$\")\n"
+                 "  set(value) {\n"
+                 "    $kt_dsl_builder$.${$set$capitalized_name$$}$(value)\n"
+                 "  }\n");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "fun ${$clear$kt_capitalized_name$$}$() {\n"
+                 "  $kt_dsl_builder$.${$clear$capitalized_name$$}$()\n"
+                 "}\n");
+
+  if (HasHazzer(descriptor_)) {
+    WriteFieldAccessorDocComment(printer, descriptor_, HAZZER);
+    printer->Print(variables_,
+                   "fun ${$has$kt_capitalized_name$$}$(): kotlin.Boolean {\n"
+                   "  return $kt_dsl_builder$.${$has$capitalized_name$$}$()\n"
+                   "}\n");
+  }
+}
+
 void ImmutableEnumFieldLiteGenerator::GenerateInitializationCode(
     io::Printer* printer) const {
   if (!IsDefaultValueJavaDefault(descriptor_)) {
@@ -774,6 +808,98 @@
   printer->Print(variables_, "$name$_ = emptyIntList();\n");
 }
 
+void RepeatedImmutableEnumFieldLiteGenerator::GenerateKotlinDslMembers(
+    io::Printer* printer) const {
+  printer->Print(
+      variables_,
+      "/**\n"
+      " * An uninstantiable, behaviorless type to represent the field in\n"
+      " * generics.\n"
+      " */\n"
+      "@kotlin.OptIn"
+      "(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)\n"
+      "class ${$$kt_capitalized_name$Proxy$}$ private constructor()"
+      " : com.google.protobuf.kotlin.DslProxy()\n");
+
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(variables_,
+                 "$kt_deprecation$ val $kt_name$: "
+                 "com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
+                 "  @kotlin.jvm.JvmSynthetic\n"
+                 "  get() = com.google.protobuf.kotlin.DslList(\n"
+                 "    $kt_dsl_builder$.${$get$capitalized_name$List$}$()\n"
+                 "  )\n");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"add$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "add(value: $kt_type$) {\n"
+                 "  $kt_dsl_builder$.${$add$capitalized_name$$}$(value)\n"
+                 "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"plusAssign$kt_capitalized_name$\")\n"
+                 "inline operator fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "plusAssign(value: $kt_type$) {\n"
+                 "  add(value)\n"
+                 "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_MULTI_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"addAll$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "addAll(values: kotlin.collections.Iterable<$kt_type$>) {\n"
+                 "  $kt_dsl_builder$.${$addAll$capitalized_name$$}$(values)\n"
+                 "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_MULTI_ADDER,
+                               /* builder */ false);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.jvm.JvmName(\"plusAssignAll$kt_capitalized_name$\")\n"
+      "inline operator fun com.google.protobuf.kotlin.DslList"
+      "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+      "plusAssign(values: kotlin.collections.Iterable<$kt_type$>) {\n"
+      "  addAll(values)\n"
+      "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_INDEXED_SETTER,
+                               /* builder */ false);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.jvm.JvmName(\"set$kt_capitalized_name$\")\n"
+      "operator fun com.google.protobuf.kotlin.DslList"
+      "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+      "set(index: kotlin.Int, value: $kt_type$) {\n"
+      "  $kt_dsl_builder$.${$set$capitalized_name$$}$(index, value)\n"
+      "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"clear$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "clear() {\n"
+                 "  $kt_dsl_builder$.${$clear$capitalized_name$$}$()\n"
+                 "}");
+}
+
 std::string RepeatedImmutableEnumFieldLiteGenerator::GetBoxedType() const {
   return name_resolver_->GetImmutableClassName(descriptor_->enum_type());
 }
diff --git a/src/google/protobuf/compiler/java/java_enum_field_lite.h b/src/google/protobuf/compiler/java/java_enum_field_lite.h
index fbc1127..99249fe 100644
--- a/src/google/protobuf/compiler/java/java_enum_field_lite.h
+++ b/src/google/protobuf/compiler/java/java_enum_field_lite.h
@@ -73,6 +73,7 @@
   void GenerateInitializationCode(io::Printer* printer) const;
   void GenerateFieldInfo(io::Printer* printer,
                          std::vector<uint16_t>* output) const;
+  void GenerateKotlinDslMembers(io::Printer* printer) const;
 
   std::string GetBoxedType() const;
 
@@ -118,6 +119,7 @@
   void GenerateInitializationCode(io::Printer* printer) const;
   void GenerateFieldInfo(io::Printer* printer,
                          std::vector<uint16_t>* output) const;
+  void GenerateKotlinDslMembers(io::Printer* printer) const;
 
   std::string GetBoxedType() const;
 
diff --git a/src/google/protobuf/compiler/java/java_field.cc b/src/google/protobuf/compiler/java/java_field.cc
index 2f775a6..db5a332 100644
--- a/src/google/protobuf/compiler/java/java_field.cc
+++ b/src/google/protobuf/compiler/java/java_field.cc
@@ -258,6 +258,11 @@
   // empty string.
   (*variables)["{"] = "";
   (*variables)["}"] = "";
+  (*variables)["kt_name"] =
+      IsForbiddenKotlin(info->name) ? info->name + "_" : info->name;
+  (*variables)["kt_capitalized_name"] = IsForbiddenKotlin(info->name)
+                                            ? info->capitalized_name + "_"
+                                            : info->capitalized_name;
 }
 
 void SetCommonOneofVariables(const FieldDescriptor* descriptor,
diff --git a/src/google/protobuf/compiler/java/java_field.h b/src/google/protobuf/compiler/java/java_field.h
index df6c38d..a7c995c 100644
--- a/src/google/protobuf/compiler/java/java_field.h
+++ b/src/google/protobuf/compiler/java/java_field.h
@@ -84,6 +84,7 @@
   virtual void GenerateSerializedSizeCode(io::Printer* printer) const = 0;
   virtual void GenerateFieldBuilderInitializationCode(
       io::Printer* printer) const = 0;
+  virtual void GenerateKotlinDslMembers(io::Printer* printer) const = 0;
 
   virtual void GenerateEqualsCode(io::Printer* printer) const = 0;
   virtual void GenerateHashCode(io::Printer* printer) const = 0;
@@ -106,6 +107,7 @@
   virtual void GenerateInitializationCode(io::Printer* printer) const = 0;
   virtual void GenerateFieldInfo(io::Printer* printer,
                                  std::vector<uint16_t>* output) const = 0;
+  virtual void GenerateKotlinDslMembers(io::Printer* printer) const = 0;
 
   virtual std::string GetBoxedType() const = 0;
 
diff --git a/src/google/protobuf/compiler/java/java_file.cc b/src/google/protobuf/compiler/java/java_file.cc
index 5cdb00d..e8801a1 100644
--- a/src/google/protobuf/compiler/java/java_file.cc
+++ b/src/google/protobuf/compiler/java/java_file.cc
@@ -675,6 +675,53 @@
   }
 }
 
+std::string FileGenerator::GetKotlinClassname() {
+  return name_resolver_->GetFileClassName(file_, immutable_api_, true);
+}
+
+void FileGenerator::GenerateKotlinSiblings(
+    const std::string& package_dir, GeneratorContext* context,
+    std::vector<std::string>* file_list,
+    std::vector<std::string>* annotation_list) {
+  for (int i = 0; i < file_->message_type_count(); i++) {
+    const Descriptor* descriptor = file_->message_type(i);
+    MessageGenerator* generator = message_generators_[i].get();
+    auto open_file = [context](const string& filename) {
+      return std::unique_ptr<io::ZeroCopyOutputStream>(context->Open(filename));
+    };
+    std::string filename = package_dir + descriptor->name() + "Kt.kt";
+    file_list->push_back(filename);
+    std::string info_full_path = filename + ".pb.meta";
+    GeneratedCodeInfo annotations;
+    io::AnnotationProtoCollector<GeneratedCodeInfo> annotation_collector(
+        &annotations);
+    auto output = open_file(filename);
+    io::Printer printer(
+        output.get(), '$',
+        options_.annotate_code ? &annotation_collector : nullptr);
+
+    printer.Print(
+        "//Generated by the protocol buffer compiler. DO NOT EDIT!\n"
+        "// source: $filename$\n"
+        "\n",
+        "filename", descriptor->file()->name());
+    if (!java_package_.empty()) {
+      printer.Print(
+          "package $package$;\n"
+          "\n",
+          "package", java_package_);
+    }
+
+    generator->GenerateKotlinMembers(&printer);
+    generator->GenerateTopLevelKotlinMembers(&printer);
+
+    if (options_.annotate_code) {
+      auto info_output = open_file(info_full_path);
+      annotations.SerializeToZeroCopyStream(info_output.get());
+      annotation_list->push_back(info_full_path);
+    }
+  }
+}
 
 bool FileGenerator::ShouldIncludeDependency(const FileDescriptor* descriptor,
                                             bool immutable_api) {
diff --git a/src/google/protobuf/compiler/java/java_file.h b/src/google/protobuf/compiler/java/java_file.h
index 9f1f719..71ee3e8 100644
--- a/src/google/protobuf/compiler/java/java_file.h
+++ b/src/google/protobuf/compiler/java/java_file.h
@@ -78,6 +78,11 @@
 
   void Generate(io::Printer* printer);
 
+  std::string GetKotlinClassname();
+  void GenerateKotlinSiblings(const std::string& package_dir,
+                              GeneratorContext* generator_context,
+                              std::vector<std::string>* file_list,
+                              std::vector<std::string>* annotation_list);
 
   // If we aren't putting everything into one file, this will write all the
   // files other than the outer file (i.e. one for each message, enum, and
diff --git a/src/google/protobuf/compiler/java/java_helpers.cc b/src/google/protobuf/compiler/java/java_helpers.cc
index e32b6e3..1020043 100644
--- a/src/google/protobuf/compiler/java/java_helpers.cc
+++ b/src/google/protobuf/compiler/java/java_helpers.cc
@@ -91,6 +91,17 @@
         "transient",  "try",          "void",      "volatile",   "while",
     });
 
+// Names that should be avoided as field names in Kotlin.
+// All Kotlin hard keywords are in this list.
+const std::unordered_set<std::string>* kKotlinForbiddenNames =
+    new std::unordered_set<std::string>({
+        "as",    "as?",   "break", "class",  "continue",  "do",     "else",
+        "false", "for",   "fun",   "if",     "in",        "!in",    "interface",
+        "is",    "!is",   "null",  "object", "package",   "return", "super",
+        "this",  "throw", "true",  "try",    "typealias", "typeof", "val",
+        "var",   "when",  "while",
+    });
+
 bool IsForbidden(const std::string& field_name) {
   for (int i = 0; i < GOOGLE_ARRAYSIZE(kForbiddenWordList); ++i) {
     if (field_name == kForbiddenWordList[i]) {
@@ -156,6 +167,38 @@
       StrCat(enum_verifier_string, terminating_string).c_str());
 }
 
+std::string ToCamelCase(const std::string& input, bool lower_first) {
+  bool capitalize_next = !lower_first;
+  std::string result;
+  result.reserve(input.size());
+
+  for (char i : input) {
+    if (i == '_') {
+      capitalize_next = true;
+    } else if (capitalize_next) {
+      result.push_back(ToUpperCh(i));
+      capitalize_next = false;
+    } else {
+      result.push_back(i);
+    }
+  }
+
+  // Lower-case the first letter.
+  if (lower_first && !result.empty()) {
+    result[0] = ToLowerCh(result[0]);
+  }
+
+  return result;
+}
+
+char ToUpperCh(char ch) {
+  return (ch >= 'a' && ch <= 'z') ? (ch - 'a' + 'A') : ch;
+}
+
+char ToLowerCh(char ch) {
+  return (ch >= 'A' && ch <= 'Z') ? (ch - 'A' + 'a') : ch;
+}
+
 std::string UnderscoresToCamelCase(const std::string& input,
                                    bool cap_next_letter) {
   GOOGLE_CHECK(!input.empty());
@@ -217,6 +260,10 @@
   return name;
 }
 
+bool IsForbiddenKotlin(const std::string& field_name) {
+  return kKotlinForbiddenNames->find(field_name) !=
+         kKotlinForbiddenNames->end();
+}
 
 std::string UniqueFileScopeIdentifier(const Descriptor* descriptor) {
   return "static_" + StringReplace(descriptor->full_name(), ".", "_", true);
@@ -423,6 +470,35 @@
   return BoxedPrimitiveTypeName(GetJavaType(descriptor));
 }
 
+const char* KotlinTypeName(JavaType type) {
+  switch (type) {
+    case JAVATYPE_INT:
+      return "kotlin.Int";
+    case JAVATYPE_LONG:
+      return "kotlin.Long";
+    case JAVATYPE_FLOAT:
+      return "kotlin.Float";
+    case JAVATYPE_DOUBLE:
+      return "kotlin.Double";
+    case JAVATYPE_BOOLEAN:
+      return "kotlin.Boolean";
+    case JAVATYPE_STRING:
+      return "kotlin.String";
+    case JAVATYPE_BYTES:
+      return "com.google.protobuf.ByteString";
+    case JAVATYPE_ENUM:
+      return NULL;
+    case JAVATYPE_MESSAGE:
+      return NULL;
+
+      // No default because we want the compiler to complain if any new
+      // JavaTypes are added.
+  }
+
+  GOOGLE_LOG(FATAL) << "Can't get here.";
+  return NULL;
+}
+
 
 std::string GetOneofStoredType(const FieldDescriptor* field) {
   const JavaType javaType = GetJavaType(field);
diff --git a/src/google/protobuf/compiler/java/java_helpers.h b/src/google/protobuf/compiler/java/java_helpers.h
index 5ede13c..13dd903 100644
--- a/src/google/protobuf/compiler/java/java_helpers.h
+++ b/src/google/protobuf/compiler/java/java_helpers.h
@@ -53,6 +53,7 @@
 extern const char kThickSeparator[];
 extern const char kThinSeparator[];
 
+bool IsForbiddenKotlin(const std::string& field_name);
 
 // If annotation_file is non-empty, prints a javax.annotation.Generated
 // annotation to the given Printer. annotation_file will be referenced in the
@@ -75,6 +76,13 @@
 
 // Converts a name to camel-case. If cap_first_letter is true, capitalize the
 // first letter.
+std::string ToCamelCase(const std::string& input, bool lower_first);
+
+char ToUpperCh(char ch);
+char ToLowerCh(char ch);
+
+// Converts a name to camel-case. If cap_first_letter is true, capitalize the
+// first letter.
 std::string UnderscoresToCamelCase(const std::string& name,
                                    bool cap_first_letter);
 // Converts the field's name to camel-case, e.g. "foo_bar_baz" becomes
@@ -216,6 +224,9 @@
 // types.
 const char* BoxedPrimitiveTypeName(JavaType type);
 
+// Kotlin source does not distinguish between primitives and non-primitives,
+// but does use Kotlin-specific qualified types for them.
+const char* KotlinTypeName(JavaType type);
 
 // Get the name of the java enum constant representing this type. E.g.,
 // "INT32" for FieldDescriptor::TYPE_INT32. The enum constant's full
diff --git a/src/google/protobuf/compiler/java/java_kotlin_generator.cc b/src/google/protobuf/compiler/java/java_kotlin_generator.cc
new file mode 100644
index 0000000..5a5d198
--- /dev/null
+++ b/src/google/protobuf/compiler/java/java_kotlin_generator.cc
@@ -0,0 +1,130 @@
+#include <google/protobuf/compiler/java/java_kotlin_generator.h>
+
+#include <memory>
+
+#include <google/protobuf/compiler/java/java_file.h>
+#include <google/protobuf/compiler/java/java_helpers.h>
+#include <google/protobuf/compiler/java/java_options.h>
+
+#include <google/protobuf/stubs/strutil.h>
+
+namespace google {
+namespace protobuf {
+namespace compiler {
+namespace java {
+
+KotlinGenerator::KotlinGenerator() {}
+KotlinGenerator::~KotlinGenerator() {}
+
+uint64_t KotlinGenerator::GetSupportedFeatures() const {
+  return CodeGenerator::Feature::FEATURE_PROTO3_OPTIONAL;
+}
+
+bool KotlinGenerator::Generate(const FileDescriptor* file,
+                               const std::string& parameter,
+                               GeneratorContext* context,
+                               std::string* error) const {
+  // -----------------------------------------------------------------
+  // parse generator options
+
+  std::vector<std::pair<std::string, std::string> > options;
+  ParseGeneratorParameter(parameter, &options);
+  Options file_options;
+
+  for (auto& option : options) {
+    if (option.first == "output_list_file") {
+      file_options.output_list_file = option.second;
+    } else if (option.first == "immutable") {
+      file_options.generate_immutable_code = true;
+    } else if (option.first == "mutable") {
+      *error = "Mutable not supported by Kotlin generator";
+      return false;
+    } else if (option.first == "shared") {
+      file_options.generate_shared_code = true;
+    } else if (option.first == "lite") {
+      file_options.enforce_lite = true;
+    } else if (option.first == "annotate_code") {
+      file_options.annotate_code = true;
+    } else if (option.first == "annotation_list_file") {
+      file_options.annotation_list_file = option.second;
+    } else {
+      *error = "Unknown generator option: " + option.first;
+      return false;
+    }
+  }
+
+  // By default we generate immutable code and shared code for immutable API.
+  if (!file_options.generate_immutable_code &&
+      !file_options.generate_shared_code) {
+    file_options.generate_immutable_code = true;
+    file_options.generate_shared_code = true;
+  }
+
+  std::vector<std::string> all_files;
+  std::vector<std::string> all_annotations;
+
+  std::unique_ptr<FileGenerator> file_generator(new FileGenerator(file, file_options, /* immutable_api = */ true));
+
+  if (!file_generator->Validate(error)) {
+    return false;
+  }
+
+  auto open_file = [context](const string& filename) {
+    return std::unique_ptr<io::ZeroCopyOutputStream>(context->Open(filename));
+  };
+  std::string package_dir = JavaPackageToDir(file_generator->java_package());
+  std::string kotlin_filename = package_dir;
+  kotlin_filename += file_generator->GetKotlinClassname();
+  kotlin_filename += ".kt";
+  all_files.push_back(kotlin_filename);
+  std::string info_full_path = kotlin_filename + ".pb.meta";
+  if (file_options.annotate_code) {
+    all_annotations.push_back(info_full_path);
+  }
+
+  // Generate main kotlin file.
+  auto output = open_file(kotlin_filename);
+  GeneratedCodeInfo annotations;
+  io::AnnotationProtoCollector<GeneratedCodeInfo> annotation_collector(
+      &annotations);
+  io::Printer printer(
+      output.get(), '$',
+      file_options.annotate_code ? &annotation_collector : nullptr);
+
+  file_generator->GenerateKotlinSiblings(package_dir, context, &all_files,
+                                         &all_annotations);
+
+  if (file_options.annotate_code) {
+    auto info_output = open_file(info_full_path);
+    annotations.SerializeToZeroCopyStream(info_output.get());
+  }
+
+  // Generate output list if requested.
+  if (!file_options.output_list_file.empty()) {
+    // Generate output list.  This is just a simple text file placed in a
+    // deterministic location which lists the .kt files being generated.
+    auto srclist_raw_output = open_file(file_options.output_list_file);
+    io::Printer srclist_printer(srclist_raw_output.get(), '$');
+    for (auto& all_file : all_files) {
+      srclist_printer.Print("$filename$\n", "filename", all_file);
+    }
+  }
+
+  if (!file_options.annotation_list_file.empty()) {
+    // Generate output list.  This is just a simple text file placed in a
+    // deterministic location which lists the .kt files being generated.
+    auto annotation_list_raw_output =
+        open_file(file_options.annotation_list_file);
+    io::Printer annotation_list_printer(annotation_list_raw_output.get(), '$');
+    for (auto& all_annotation : all_annotations) {
+      annotation_list_printer.Print("$filename$\n", "filename", all_annotation);
+    }
+  }
+
+  return true;
+}
+
+}  // namespace java
+}  // namespace compiler
+} //namespace protobuf
+}  // namespace google
diff --git a/src/google/protobuf/compiler/java/java_kotlin_generator.h b/src/google/protobuf/compiler/java/java_kotlin_generator.h
new file mode 100644
index 0000000..db15353
--- /dev/null
+++ b/src/google/protobuf/compiler/java/java_kotlin_generator.h
@@ -0,0 +1,40 @@
+#ifndef GOOGLE_PROTOBUF_COMPILER_JAVA_KOTLIN_GENERATOR_H_
+#define GOOGLE_PROTOBUF_COMPILER_JAVA_KOTLIN_GENERATOR_H_
+
+#include <string>
+#include <google/protobuf/compiler/code_generator.h>
+
+#include <google/protobuf/port_def.inc>
+
+namespace google {
+namespace protobuf {
+namespace compiler {
+namespace java {
+
+// CodeGenerator implementation which generates Kotlin code.  If you create your
+// own protocol compiler binary and you want it to support Kotlin output, you
+// can do so by registering an instance of this CodeGenerator with the
+// CommandLineInterface in your main() function.
+class PROTOC_EXPORT KotlinGenerator : public CodeGenerator {
+ public:
+  KotlinGenerator();
+  ~KotlinGenerator() override;
+
+  // implements CodeGenerator ----------------------------------------
+  bool Generate(const FileDescriptor* file, const std::string& parameter,
+                GeneratorContext* context, std::string* error) const override;
+
+  uint64_t GetSupportedFeatures() const override;
+
+ private:
+  GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(KotlinGenerator);
+};
+
+}  // namespace java
+}  // namespace compiler
+}  // namespace protobuf
+}  // namespace google
+
+#include <google/protobuf/port_undef.inc>
+
+#endif  // GOOGLE_PROTOBUF_COMPILER_JAVA_KOTLIN_GENERATOR_H_
diff --git a/src/google/protobuf/compiler/java/java_map_field.cc b/src/google/protobuf/compiler/java/java_map_field.cc
index 5db199d..a4ec2ff 100644
--- a/src/google/protobuf/compiler/java/java_map_field.cc
+++ b/src/google/protobuf/compiler/java/java_map_field.cc
@@ -69,6 +69,17 @@
   }
 }
 
+std::string KotlinTypeName(const FieldDescriptor* field,
+                           ClassNameResolver* name_resolver) {
+  if (GetJavaType(field) == JAVATYPE_MESSAGE) {
+    return name_resolver->GetImmutableClassName(field->message_type());
+  } else if (GetJavaType(field) == JAVATYPE_ENUM) {
+    return name_resolver->GetImmutableClassName(field->enum_type());
+  } else {
+    return KotlinTypeName(GetJavaType(field));
+  }
+}
+
 std::string WireType(const FieldDescriptor* field) {
   return "com.google.protobuf.WireFormat.FieldType." +
          std::string(FieldTypeName(field->type()));
@@ -91,6 +102,8 @@
   (*variables)["key_type"] = TypeName(key, name_resolver, false);
   std::string boxed_key_type = TypeName(key, name_resolver, true);
   (*variables)["boxed_key_type"] = boxed_key_type;
+  (*variables)["kt_key_type"] = KotlinTypeName(key, name_resolver);
+  (*variables)["kt_value_type"] = KotlinTypeName(value, name_resolver);
   // Used for calling the serialization function.
   (*variables)["short_key_type"] =
       boxed_key_type.substr(boxed_key_type.rfind('.') + 1);
@@ -136,6 +149,11 @@
   // by the proto compiler
   (*variables)["deprecation"] =
       descriptor->options().deprecated() ? "@java.lang.Deprecated " : "";
+  (*variables)["kt_deprecation"] =
+      descriptor->options().deprecated()
+          ? "@kotlin.Deprecated(message = \"Field " + (*variables)["name"] +
+                " is deprecated\") "
+          : "";
   (*variables)["on_changed"] = "onChanged();";
 
   // For repeated fields, one bit is used for whether the array is immutable
@@ -651,6 +669,87 @@
   }
 }
 
+void ImmutableMapFieldGenerator::GenerateKotlinDslMembers(
+    io::Printer* printer) const {
+  printer->Print(
+      variables_,
+      "/**\n"
+      " * An uninstantiable, behaviorless type to represent the field in\n"
+      " * generics.\n"
+      " */\n"
+      "@kotlin.OptIn"
+      "(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)\n"
+      "class ${$$kt_capitalized_name$Proxy$}$ private constructor()"
+      " : com.google.protobuf.kotlin.DslProxy()\n");
+
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(
+      variables_,
+      "$kt_deprecation$ val $kt_name$: "
+      "com.google.protobuf.kotlin.DslMap"
+      "<$kt_key_type$, $kt_value_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
+      "  @kotlin.jvm.JvmSynthetic\n"
+      "  @JvmName(\"get$kt_capitalized_name$Map\")\n"
+      "  get() = com.google.protobuf.kotlin.DslMap(\n"
+      "    $kt_dsl_builder$.${$get$capitalized_name$Map$}$()\n"
+      "  )\n");
+
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(
+      variables_,
+      "@JvmName(\"put$kt_capitalized_name$\")\n"
+      "fun com.google.protobuf.kotlin.DslMap"
+      "<$kt_key_type$, $kt_value_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
+      "  .put(key: $kt_key_type$, value: $kt_value_type$) {\n"
+      "     $kt_dsl_builder$.${$put$capitalized_name$$}$(key, value)\n"
+      "   }\n");
+
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@JvmName(\"set$kt_capitalized_name$\")\n"
+      "inline operator fun com.google.protobuf.kotlin.DslMap"
+      "<$kt_key_type$, $kt_value_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
+      "  .set(key: $kt_key_type$, value: $kt_value_type$) {\n"
+      "     put(key, value)\n"
+      "   }\n");
+
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@JvmName(\"remove$kt_capitalized_name$\")\n"
+      "fun com.google.protobuf.kotlin.DslMap"
+      "<$kt_key_type$, $kt_value_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
+      "  .remove(key: $kt_key_type$) {\n"
+      "     $kt_dsl_builder$.${$remove$capitalized_name$$}$(key)\n"
+      "   }\n");
+
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@JvmName(\"putAll$kt_capitalized_name$\")\n"
+      "fun com.google.protobuf.kotlin.DslMap"
+      "<$kt_key_type$, $kt_value_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
+      "  .putAll(map: kotlin.collections.Map<$kt_key_type$, $kt_value_type$>) "
+      "{\n"
+      "     $kt_dsl_builder$.${$putAll$capitalized_name$$}$(map)\n"
+      "   }\n");
+
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@JvmName(\"clear$kt_capitalized_name$\")\n"
+      "fun com.google.protobuf.kotlin.DslMap"
+      "<$kt_key_type$, $kt_value_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
+      "  .clear() {\n"
+      "     $kt_dsl_builder$.${$clear$capitalized_name$$}$()\n"
+      "   }\n");
+}
+
 void ImmutableMapFieldGenerator::GenerateFieldBuilderInitializationCode(
     io::Printer* printer) const {
   // Nothing to initialize.
diff --git a/src/google/protobuf/compiler/java/java_map_field.h b/src/google/protobuf/compiler/java/java_map_field.h
index 2ff1f76..63b2577 100644
--- a/src/google/protobuf/compiler/java/java_map_field.h
+++ b/src/google/protobuf/compiler/java/java_map_field.h
@@ -62,6 +62,7 @@
   void GenerateFieldBuilderInitializationCode(io::Printer* printer) const;
   void GenerateEqualsCode(io::Printer* printer) const;
   void GenerateHashCode(io::Printer* printer) const;
+  void GenerateKotlinDslMembers(io::Printer* printer) const;
 
   std::string GetBoxedType() const;
 
diff --git a/src/google/protobuf/compiler/java/java_map_field_lite.cc b/src/google/protobuf/compiler/java/java_map_field_lite.cc
index 8db281d..023b4fc 100644
--- a/src/google/protobuf/compiler/java/java_map_field_lite.cc
+++ b/src/google/protobuf/compiler/java/java_map_field_lite.cc
@@ -71,6 +71,17 @@
   }
 }
 
+std::string KotlinTypeName(const FieldDescriptor* field,
+                           ClassNameResolver* name_resolver) {
+  if (GetJavaType(field) == JAVATYPE_MESSAGE) {
+    return name_resolver->GetImmutableClassName(field->message_type());
+  } else if (GetJavaType(field) == JAVATYPE_ENUM) {
+    return name_resolver->GetImmutableClassName(field->enum_type());
+  } else {
+    return KotlinTypeName(GetJavaType(field));
+  }
+}
+
 std::string WireType(const FieldDescriptor* field) {
   return "com.google.protobuf.WireFormat.FieldType." +
          std::string(FieldTypeName(field->type()));
@@ -92,6 +103,8 @@
 
   (*variables)["key_type"] = TypeName(key, name_resolver, false);
   (*variables)["boxed_key_type"] = TypeName(key, name_resolver, true);
+  (*variables)["kt_key_type"] = KotlinTypeName(key, name_resolver);
+  (*variables)["kt_value_type"] = KotlinTypeName(value, name_resolver);
   (*variables)["key_wire_type"] = WireType(key);
   (*variables)["key_default_value"] = DefaultValue(key, true, name_resolver);
   // We use `x.getClass()` as a null check because it generates less bytecode
@@ -137,6 +150,11 @@
   // by the proto compiler
   (*variables)["deprecation"] =
       descriptor->options().deprecated() ? "@java.lang.Deprecated " : "";
+  (*variables)["kt_deprecation"] =
+      descriptor->options().deprecated()
+          ? "@kotlin.Deprecated(message = \"Field " + (*variables)["name"] +
+                " is deprecated\") "
+          : "";
 
   (*variables)["default_entry"] =
       (*variables)["capitalized_name"] + "DefaultEntryHolder.defaultEntry";
@@ -794,6 +812,87 @@
   }
 }
 
+void ImmutableMapFieldLiteGenerator::GenerateKotlinDslMembers(
+    io::Printer* printer) const {
+  printer->Print(
+      variables_,
+      "/**\n"
+      " * An uninstantiable, behaviorless type to represent the field in\n"
+      " * generics.\n"
+      " */\n"
+      "@kotlin.OptIn"
+      "(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)\n"
+      "class ${$$kt_capitalized_name$Proxy$}$ private constructor()"
+      " : com.google.protobuf.kotlin.DslProxy()\n");
+
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(
+      variables_,
+      "$kt_deprecation$ val $kt_name$: "
+      "com.google.protobuf.kotlin.DslMap"
+      "<$kt_key_type$, $kt_value_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
+      "  @kotlin.jvm.JvmSynthetic\n"
+      "  @JvmName(\"get$kt_capitalized_name$Map\")\n"
+      "  get() = com.google.protobuf.kotlin.DslMap(\n"
+      "    $kt_dsl_builder$.${$get$capitalized_name$Map$}$()\n"
+      "  )\n");
+
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(
+      variables_,
+      "@JvmName(\"put$kt_capitalized_name$\")\n"
+      "fun com.google.protobuf.kotlin.DslMap"
+      "<$kt_key_type$, $kt_value_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
+      "  .put(key: $kt_key_type$, value: $kt_value_type$) {\n"
+      "     $kt_dsl_builder$.${$put$capitalized_name$$}$(key, value)\n"
+      "   }\n");
+
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@JvmName(\"set$kt_capitalized_name$\")\n"
+      "inline operator fun com.google.protobuf.kotlin.DslMap"
+      "<$kt_key_type$, $kt_value_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
+      "  .set(key: $kt_key_type$, value: $kt_value_type$) {\n"
+      "     put(key, value)\n"
+      "   }\n");
+
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@JvmName(\"remove$kt_capitalized_name$\")\n"
+      "fun com.google.protobuf.kotlin.DslMap"
+      "<$kt_key_type$, $kt_value_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
+      "  .remove(key: $kt_key_type$) {\n"
+      "     $kt_dsl_builder$.${$remove$capitalized_name$$}$(key)\n"
+      "   }\n");
+
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@JvmName(\"putAll$kt_capitalized_name$\")\n"
+      "fun com.google.protobuf.kotlin.DslMap"
+      "<$kt_key_type$, $kt_value_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
+      "  .putAll(map: kotlin.collections.Map<$kt_key_type$, $kt_value_type$>) "
+      "{\n"
+      "     $kt_dsl_builder$.${$putAll$capitalized_name$$}$(map)\n"
+      "   }\n");
+
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@JvmName(\"clear$kt_capitalized_name$\")\n"
+      "fun com.google.protobuf.kotlin.DslMap"
+      "<$kt_key_type$, $kt_value_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
+      "  .clear() {\n"
+      "     $kt_dsl_builder$.${$clear$capitalized_name$$}$()\n"
+      "   }\n");
+}
+
 void ImmutableMapFieldLiteGenerator::GenerateInitializationCode(
     io::Printer* printer) const {
   // Nothing to initialize.
diff --git a/src/google/protobuf/compiler/java/java_map_field_lite.h b/src/google/protobuf/compiler/java/java_map_field_lite.h
index 37aec06..62c162d 100644
--- a/src/google/protobuf/compiler/java/java_map_field_lite.h
+++ b/src/google/protobuf/compiler/java/java_map_field_lite.h
@@ -55,6 +55,7 @@
   void GenerateInitializationCode(io::Printer* printer) const;
   void GenerateFieldInfo(io::Printer* printer,
                          std::vector<uint16_t>* output) const;
+  void GenerateKotlinDslMembers(io::Printer* printer) const;
 
   std::string GetBoxedType() const;
 
diff --git a/src/google/protobuf/compiler/java/java_message.cc b/src/google/protobuf/compiler/java/java_message.cc
index f2df25f..5425ad9 100644
--- a/src/google/protobuf/compiler/java/java_message.cc
+++ b/src/google/protobuf/compiler/java/java_message.cc
@@ -1358,6 +1358,243 @@
   }
 }
 
+void ImmutableMessageGenerator::GenerateKotlinDsl(io::Printer* printer) const {
+  printer->Print(
+      "@kotlin.OptIn"
+      "(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)\n"
+      "@com.google.protobuf.kotlin.ProtoDslMarker\n");
+  printer->Print(
+      "class Dsl private constructor(\n"
+      "  @kotlin.jvm.JvmField private val _builder: $message$.Builder\n"
+      ") {\n"
+      "  companion object {\n"
+      "    @kotlin.jvm.JvmSynthetic\n"
+      "    @kotlin.PublishedApi\n"
+      "    internal fun _create(builder: $message$.Builder): Dsl = "
+      "Dsl(builder)\n"
+      "  }\n"
+      "\n"
+      "  @kotlin.jvm.JvmSynthetic\n"
+      "  @kotlin.PublishedApi\n"
+      "  internal fun _build(): $message$ = _builder.build()\n",
+      "message", name_resolver_->GetClassName(descriptor_, true));
+
+  printer->Indent();
+
+  for (int i = 0; i < descriptor_->field_count(); i++) {
+    printer->Print("\n");
+    field_generators_.get(descriptor_->field(i))
+        .GenerateKotlinDslMembers(printer);
+  }
+
+  for (auto oneof : oneofs_) {
+    printer->Print(
+        "val $oneof_name$Case: $message$.$oneof_capitalized_name$Case\n"
+        "  @JvmName(\"get$oneof_capitalized_name$Case\")\n"
+        "  get() = _builder.get$oneof_capitalized_name$Case()\n\n"
+        "fun clear$oneof_capitalized_name$() {\n"
+        "  _builder.clear$oneof_capitalized_name$()\n"
+        "}\n",
+        "oneof_name", context_->GetOneofGeneratorInfo(oneof)->name,
+        "oneof_capitalized_name",
+        context_->GetOneofGeneratorInfo(oneof)->capitalized_name, "message",
+        name_resolver_->GetClassName(descriptor_, true));
+  }
+
+  if (descriptor_->extension_range_count() > 0) {
+    GenerateKotlinExtensions(printer);
+  }
+
+  printer->Outdent();
+  printer->Print("}\n");
+}
+
+void ImmutableMessageGenerator::GenerateKotlinMembers(
+    io::Printer* printer) const {
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "inline fun $camelcase_name$(block: $message_kt$.Dsl.() -> Unit): "
+      "$message$ "
+      "=\n"
+      "  $message_kt$.Dsl._create($message$.newBuilder()).apply { block() "
+      "}._build()\n",
+      "camelcase_name", name_resolver_->GetKotlinFactoryName(descriptor_),
+      "message_kt", name_resolver_->GetKotlinExtensionsClassName(descriptor_),
+      "message", name_resolver_->GetClassName(descriptor_, true));
+
+  printer->Print("object $name$Kt {\n", "name", descriptor_->name());
+  printer->Indent();
+  GenerateKotlinDsl(printer);
+  for (int i = 0; i < descriptor_->nested_type_count(); i++) {
+    if (IsMapEntry(descriptor_->nested_type(i))) continue;
+    ImmutableMessageGenerator(descriptor_->nested_type(i), context_)
+        .GenerateKotlinMembers(printer);
+  }
+  printer->Outdent();
+  printer->Print("}\n");
+}
+
+void ImmutableMessageGenerator::GenerateTopLevelKotlinMembers(
+    io::Printer* printer) const {
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "inline fun $message$.copy(block: $message_kt$.Dsl.() -> Unit): "
+      "$message$ =\n"
+      "  $message_kt$.Dsl._create(this.toBuilder()).apply { block() "
+      "}._build()\n",
+      "message", name_resolver_->GetClassName(descriptor_, true), "message_kt",
+      name_resolver_->GetKotlinExtensionsClassName(descriptor_));
+
+  for (int i = 0; i < descriptor_->nested_type_count(); i++) {
+    if (IsMapEntry(descriptor_->nested_type(i))) continue;
+    ImmutableMessageGenerator(descriptor_->nested_type(i), context_)
+        .GenerateTopLevelKotlinMembers(printer);
+  }
+}
+
+void ImmutableMessageGenerator::GenerateKotlinExtensions(
+    io::Printer* printer) const {
+  std::string message_name = name_resolver_->GetClassName(descriptor_, true);
+
+  printer->Print(
+      "@Suppress(\"UNCHECKED_CAST\")\n"
+      "@kotlin.jvm.JvmSynthetic\n"
+      "operator fun <T> get(extension: "
+      "com.google.protobuf.ExtensionLite<$message$, T>): T {\n"
+      "  return if (extension.isRepeated) {\n"
+      "    get(extension as com.google.protobuf.ExtensionLite<$message$, "
+      "List<*>>) as T\n"
+      "  } else {\n"
+      "    _builder.getExtension(extension)\n"
+      "  }\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.OptIn"
+      "(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)\n"
+      "@kotlin.jvm.JvmName(\"-getRepeatedExtension\")\n"
+      "operator fun <E> get(\n"
+      "  extension: com.google.protobuf.ExtensionLite<$message$, List<E>>\n"
+      "): com.google.protobuf.kotlin.ExtensionList<E, $message$> {\n"
+      "  return com.google.protobuf.kotlin.ExtensionList(extension, "
+      "_builder.getExtension(extension))\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "operator fun contains(extension: "
+      "com.google.protobuf.ExtensionLite<$message$, *>): "
+      "Boolean {\n"
+      "  return _builder.hasExtension(extension)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "fun clear(extension: com.google.protobuf.ExtensionLite<$message$, *>) "
+      "{\n"
+      "  _builder.clearExtension(extension)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.PublishedApi\n"
+      "internal fun <T> setExtension(extension: "
+      "com.google.protobuf.ExtensionLite<$message$, T>, "
+      "value: T) {\n"
+      "  _builder.setExtension(extension, value)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "inline operator fun <T : Comparable<T>> set(\n"
+      "  extension: com.google.protobuf.ExtensionLite<$message$, T>,\n"
+      "  value: T\n"
+      ") {\n"
+      "  setExtension(extension, value)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "inline operator fun set(\n"
+      "  extension: com.google.protobuf.ExtensionLite<$message$, "
+      "com.google.protobuf.ByteString>,\n"
+      "  value: com.google.protobuf.ByteString\n"
+      ") {\n"
+      "  setExtension(extension, value)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "inline operator fun <T : com.google.protobuf.MessageLite> set(\n"
+      "  extension: com.google.protobuf.ExtensionLite<$message$, T>,\n"
+      "  value: T\n"
+      ") {\n"
+      "  setExtension(extension, value)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "fun <E> com.google.protobuf.kotlin.ExtensionList<E, "
+      "$message$>.add(value: E) {\n"
+      "  _builder.addExtension(this.extension, value)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "inline operator fun <E> com.google.protobuf.kotlin.ExtensionList<E, "
+      "$message$>.plusAssign"
+      "(value: E) {\n"
+      "  add(value)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "fun <E> com.google.protobuf.kotlin.ExtensionList<E, "
+      "$message$>.addAll(values: Iterable<E>) {\n"
+      "  for (value in values) {\n"
+      "    add(value)\n"
+      "  }\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "inline operator fun <E> com.google.protobuf.kotlin.ExtensionList<E, "
+      "$message$>.plusAssign(values: "
+      "Iterable<E>) {\n"
+      "  addAll(values)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "operator fun <E> com.google.protobuf.kotlin.ExtensionList<E, "
+      "$message$>.set(index: Int, value: "
+      "E) {\n"
+      "  _builder.setExtension(this.extension, index, value)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "inline fun com.google.protobuf.kotlin.ExtensionList<*, "
+      "$message$>.clear() {\n"
+      "  clear(extension)\n"
+      "}\n\n",
+      "message", message_name);
+}
+
 void ImmutableMessageGenerator::GenerateAnyMethods(io::Printer* printer) {
   printer->Print(
       "private static String getTypeUrl(\n"
diff --git a/src/google/protobuf/compiler/java/java_message.h b/src/google/protobuf/compiler/java/java_message.h
index 87b9df5..50d7180 100644
--- a/src/google/protobuf/compiler/java/java_message.h
+++ b/src/google/protobuf/compiler/java/java_message.h
@@ -85,6 +85,9 @@
   // Generate code to register all contained extensions with an
   // ExtensionRegistry.
   virtual void GenerateExtensionRegistrationCode(io::Printer* printer) = 0;
+  virtual void GenerateKotlinDsl(io::Printer* printer) const = 0;
+  virtual void GenerateKotlinMembers(io::Printer* printer) const = 0;
+  virtual void GenerateTopLevelKotlinMembers(io::Printer* printer) const = 0;
 
  protected:
   const Descriptor* descriptor_;
@@ -107,6 +110,9 @@
 
   // Returns an estimate of the number of bytes the printed code will compile to
   virtual int GenerateStaticVariableInitializers(io::Printer* printer);
+  void GenerateKotlinDsl(io::Printer* printer) const override;
+  void GenerateKotlinMembers(io::Printer* printer) const override;
+  void GenerateTopLevelKotlinMembers(io::Printer* printer) const override;
 
  private:
   void GenerateFieldAccessorTable(io::Printer* printer, int* bytecode_estimate);
@@ -128,6 +134,7 @@
   void GenerateEqualsAndHashCode(io::Printer* printer);
   void GenerateParser(io::Printer* printer);
   void GenerateParsingConstructor(io::Printer* printer);
+  void GenerateKotlinExtensions(io::Printer* printer) const;
   void GenerateAnyMethods(io::Printer* printer);
 
   Context* context_;
diff --git a/src/google/protobuf/compiler/java/java_message_field.cc b/src/google/protobuf/compiler/java/java_message_field.cc
index f657c17..a6d5dfe 100644
--- a/src/google/protobuf/compiler/java/java_message_field.cc
+++ b/src/google/protobuf/compiler/java/java_message_field.cc
@@ -60,6 +60,7 @@
 
   (*variables)["type"] =
       name_resolver->GetImmutableClassName(descriptor->message_type());
+  (*variables)["kt_type"] = (*variables)["type"];
   (*variables)["mutable_type"] =
       name_resolver->GetMutableClassName(descriptor->message_type());
   (*variables)["group_or_message"] =
@@ -69,6 +70,11 @@
   // by the proto compiler
   (*variables)["deprecation"] =
       descriptor->options().deprecated() ? "@java.lang.Deprecated " : "";
+  (*variables)["kt_deprecation"] =
+      descriptor->options().deprecated()
+          ? "@kotlin.Deprecated(message = \"Field " + (*variables)["name"] +
+                " is deprecated\") "
+          : "";
   (*variables)["on_changed"] = "onChanged();";
   (*variables)["ver"] = GeneratedCodeVersionSuffix();
   (*variables)["get_parser"] =
@@ -407,6 +413,32 @@
       "}\n");
 }
 
+void ImmutableMessageFieldGenerator::GenerateKotlinDslMembers(
+    io::Printer* printer) const {
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(variables_,
+                 "$kt_deprecation$var $kt_name$: $kt_type$\n"
+                 "  @JvmName(\"${$get$kt_capitalized_name$$}$\")\n"
+                 "  get() = $kt_dsl_builder$.${$get$capitalized_name$$}$()\n"
+                 "  @JvmName(\"${$set$kt_capitalized_name$$}$\")\n"
+                 "  set(value) {\n"
+                 "    $kt_dsl_builder$.${$set$capitalized_name$$}$(value)\n"
+                 "  }\n");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "fun ${$clear$kt_capitalized_name$$}$() {\n"
+                 "  $kt_dsl_builder$.${$clear$capitalized_name$$}$()\n"
+                 "}\n");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, HAZZER);
+  printer->Print(variables_,
+                 "fun ${$has$kt_capitalized_name$$}$(): kotlin.Boolean {\n"
+                 "  return $kt_dsl_builder$.${$has$capitalized_name$$}$()\n"
+                 "}\n");
+}
+
 void ImmutableMessageFieldGenerator::GenerateFieldBuilderInitializationCode(
     io::Printer* printer) const {
   if (HasHasbit(descriptor_)) {
@@ -1361,6 +1393,98 @@
   return name_resolver_->GetImmutableClassName(descriptor_->message_type());
 }
 
+void RepeatedImmutableMessageFieldGenerator::GenerateKotlinDslMembers(
+    io::Printer* printer) const {
+  printer->Print(
+      variables_,
+      "/**\n"
+      " * An uninstantiable, behaviorless type to represent the field in\n"
+      " * generics.\n"
+      " */\n"
+      "@kotlin.OptIn"
+      "(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)\n"
+      "class ${$$kt_capitalized_name$Proxy$}$ private constructor()"
+      " : com.google.protobuf.kotlin.DslProxy()\n");
+
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(variables_,
+                 "$kt_deprecation$ val $kt_name$: "
+                 "com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
+                 "  @kotlin.jvm.JvmSynthetic\n"
+                 "  get() = com.google.protobuf.kotlin.DslList(\n"
+                 "    $kt_dsl_builder$.${$get$capitalized_name$List$}$()\n"
+                 "  )\n");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"add$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "add(value: $kt_type$) {\n"
+                 "  $kt_dsl_builder$.${$add$capitalized_name$$}$(value)\n"
+                 "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"plusAssign$kt_capitalized_name$\")\n"
+                 "inline operator fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "plusAssign(value: $kt_type$) {\n"
+                 "  add(value)\n"
+                 "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_MULTI_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"addAll$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "addAll(values: kotlin.collections.Iterable<$kt_type$>) {\n"
+                 "  $kt_dsl_builder$.${$addAll$capitalized_name$$}$(values)\n"
+                 "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_MULTI_ADDER,
+                               /* builder */ false);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.jvm.JvmName(\"plusAssignAll$kt_capitalized_name$\")\n"
+      "inline operator fun com.google.protobuf.kotlin.DslList"
+      "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+      "plusAssign(values: kotlin.collections.Iterable<$kt_type$>) {\n"
+      "  addAll(values)\n"
+      "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_INDEXED_SETTER,
+                               /* builder */ false);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.jvm.JvmName(\"set$kt_capitalized_name$\")\n"
+      "operator fun com.google.protobuf.kotlin.DslList"
+      "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+      "set(index: kotlin.Int, value: $kt_type$) {\n"
+      "  $kt_dsl_builder$.${$set$capitalized_name$$}$(index, value)\n"
+      "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"clear$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "clear() {\n"
+                 "  $kt_dsl_builder$.${$clear$capitalized_name$$}$()\n"
+                 "}");
+}
+
 }  // namespace java
 }  // namespace compiler
 }  // namespace protobuf
diff --git a/src/google/protobuf/compiler/java/java_message_field.h b/src/google/protobuf/compiler/java/java_message_field.h
index 36fa492..08c5e91 100644
--- a/src/google/protobuf/compiler/java/java_message_field.h
+++ b/src/google/protobuf/compiler/java/java_message_field.h
@@ -81,6 +81,7 @@
   void GenerateFieldBuilderInitializationCode(io::Printer* printer) const;
   void GenerateEqualsCode(io::Printer* printer) const;
   void GenerateHashCode(io::Printer* printer) const;
+  void GenerateKotlinDslMembers(io::Printer* printer) const;
 
   std::string GetBoxedType() const;
 
@@ -146,6 +147,7 @@
   void GenerateFieldBuilderInitializationCode(io::Printer* printer) const;
   void GenerateEqualsCode(io::Printer* printer) const;
   void GenerateHashCode(io::Printer* printer) const;
+  void GenerateKotlinDslMembers(io::Printer* printer) const;
 
   std::string GetBoxedType() const;
 
diff --git a/src/google/protobuf/compiler/java/java_message_field_lite.cc b/src/google/protobuf/compiler/java/java_message_field_lite.cc
index 73f1bcf..adb91a3 100644
--- a/src/google/protobuf/compiler/java/java_message_field_lite.cc
+++ b/src/google/protobuf/compiler/java/java_message_field_lite.cc
@@ -61,6 +61,7 @@
 
   (*variables)["type"] =
       name_resolver->GetImmutableClassName(descriptor->message_type());
+  (*variables)["kt_type"] = (*variables)["type"];
   (*variables)["mutable_type"] =
       name_resolver->GetMutableClassName(descriptor->message_type());
   (*variables)["group_or_message"] =
@@ -70,6 +71,11 @@
   // by the proto compiler
   (*variables)["deprecation"] =
       descriptor->options().deprecated() ? "@java.lang.Deprecated " : "";
+  (*variables)["kt_deprecation"] =
+      descriptor->options().deprecated()
+          ? "@kotlin.Deprecated(message = \"Field " + (*variables)["name"] +
+                " is deprecated\") "
+          : "";
   (*variables)["required"] = descriptor->is_required() ? "true" : "false";
 
   if (HasHasbit(descriptor)) {
@@ -277,6 +283,32 @@
   printer->Annotate("{", "}", descriptor_);
 }
 
+void ImmutableMessageFieldLiteGenerator::GenerateKotlinDslMembers(
+    io::Printer* printer) const {
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(variables_,
+                 "$kt_deprecation$var $kt_name$: $kt_type$\n"
+                 "  @JvmName(\"${$get$kt_capitalized_name$$}$\")\n"
+                 "  get() = $kt_dsl_builder$.${$get$capitalized_name$$}$()\n"
+                 "  @JvmName(\"${$set$kt_capitalized_name$$}$\")\n"
+                 "  set(value) {\n"
+                 "    $kt_dsl_builder$.${$set$capitalized_name$$}$(value)\n"
+                 "  }\n");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "fun ${$clear$kt_capitalized_name$$}$() {\n"
+                 "  $kt_dsl_builder$.${$clear$capitalized_name$$}$()\n"
+                 "}\n");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, HAZZER);
+  printer->Print(variables_,
+                 "fun ${$has$kt_capitalized_name$$}$(): kotlin.Boolean {\n"
+                 "  return $kt_dsl_builder$.${$has$capitalized_name$$}$()\n"
+                 "}\n");
+}
+
 void ImmutableMessageFieldLiteGenerator::GenerateFieldInfo(
     io::Printer* printer, std::vector<uint16_t>* output) const {
   WriteIntToUtf16CharSequence(descriptor_->number(), output);
@@ -751,6 +783,98 @@
   return name_resolver_->GetImmutableClassName(descriptor_->message_type());
 }
 
+void RepeatedImmutableMessageFieldLiteGenerator::GenerateKotlinDslMembers(
+    io::Printer* printer) const {
+  printer->Print(
+      variables_,
+      "/**\n"
+      " * An uninstantiable, behaviorless type to represent the field in\n"
+      " * generics.\n"
+      " */\n"
+      "@kotlin.OptIn"
+      "(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)\n"
+      "class ${$$kt_capitalized_name$Proxy$}$ private constructor()"
+      " : com.google.protobuf.kotlin.DslProxy()\n");
+
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(variables_,
+                 "$kt_deprecation$ val $kt_name$: "
+                 "com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
+                 "  @kotlin.jvm.JvmSynthetic\n"
+                 "  get() = com.google.protobuf.kotlin.DslList(\n"
+                 "    $kt_dsl_builder$.${$get$capitalized_name$List$}$()\n"
+                 "  )\n");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"add$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "add(value: $kt_type$) {\n"
+                 "  $kt_dsl_builder$.${$add$capitalized_name$$}$(value)\n"
+                 "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"plusAssign$kt_capitalized_name$\")\n"
+                 "inline operator fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "plusAssign(value: $kt_type$) {\n"
+                 "  add(value)\n"
+                 "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_MULTI_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"addAll$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "addAll(values: kotlin.collections.Iterable<$kt_type$>) {\n"
+                 "  $kt_dsl_builder$.${$addAll$capitalized_name$$}$(values)\n"
+                 "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_MULTI_ADDER,
+                               /* builder */ false);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.jvm.JvmName(\"plusAssignAll$kt_capitalized_name$\")\n"
+      "inline operator fun com.google.protobuf.kotlin.DslList"
+      "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+      "plusAssign(values: kotlin.collections.Iterable<$kt_type$>) {\n"
+      "  addAll(values)\n"
+      "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_INDEXED_SETTER,
+                               /* builder */ false);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.jvm.JvmName(\"set$kt_capitalized_name$\")\n"
+      "operator fun com.google.protobuf.kotlin.DslList"
+      "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+      "set(index: kotlin.Int, value: $kt_type$) {\n"
+      "  $kt_dsl_builder$.${$set$capitalized_name$$}$(index, value)\n"
+      "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"clear$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "clear() {\n"
+                 "  $kt_dsl_builder$.${$clear$capitalized_name$$}$()\n"
+                 "}");
+}
+
 }  // namespace java
 }  // namespace compiler
 }  // namespace protobuf
diff --git a/src/google/protobuf/compiler/java/java_message_field_lite.h b/src/google/protobuf/compiler/java/java_message_field_lite.h
index 8b935e6..af0129c 100644
--- a/src/google/protobuf/compiler/java/java_message_field_lite.h
+++ b/src/google/protobuf/compiler/java/java_message_field_lite.h
@@ -73,6 +73,7 @@
   void GenerateInitializationCode(io::Printer* printer) const;
   void GenerateFieldInfo(io::Printer* printer,
                          std::vector<uint16_t>* output) const;
+  void GenerateKotlinDslMembers(io::Printer* printer) const;
 
   std::string GetBoxedType() const;
 
@@ -118,6 +119,7 @@
   void GenerateInitializationCode(io::Printer* printer) const;
   void GenerateFieldInfo(io::Printer* printer,
                          std::vector<uint16_t>* output) const;
+  void GenerateKotlinDslMembers(io::Printer* printer) const;
 
   std::string GetBoxedType() const;
 
diff --git a/src/google/protobuf/compiler/java/java_message_lite.cc b/src/google/protobuf/compiler/java/java_message_lite.cc
index cae9963..74ce6d6 100644
--- a/src/google/protobuf/compiler/java/java_message_lite.cc
+++ b/src/google/protobuf/compiler/java/java_message_lite.cc
@@ -724,6 +724,242 @@
   }
 }
 
+void ImmutableMessageLiteGenerator::GenerateKotlinDsl(
+    io::Printer* printer) const {
+  printer->Print(
+      "@kotlin.OptIn"
+      "(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)\n"
+      "@com.google.protobuf.kotlin.ProtoDslMarker\n");
+  printer->Print(
+      "class Dsl private constructor(\n"
+      "  @kotlin.jvm.JvmField private val _builder: $message$.Builder\n"
+      ") {\n"
+      "  companion object {\n"
+      "    @kotlin.jvm.JvmSynthetic\n"
+      "    @kotlin.PublishedApi\n"
+      "    internal fun _create(builder: $message$.Builder): Dsl = "
+      "Dsl(builder)\n"
+      "  }\n"
+      "\n"
+      "  @kotlin.jvm.JvmSynthetic\n"
+      "  @kotlin.PublishedApi\n"
+      "  internal fun _build(): $message$ = _builder.build()\n",
+      "message", name_resolver_->GetClassName(descriptor_, true));
+
+  printer->Indent();
+
+  for (int i = 0; i < descriptor_->field_count(); i++) {
+    printer->Print("\n");
+    field_generators_.get(descriptor_->field(i))
+        .GenerateKotlinDslMembers(printer);
+  }
+
+  for (auto oneof : oneofs_) {
+    printer->Print(
+        "val $oneof_name$Case: $message$.$oneof_capitalized_name$Case\n"
+        "  @JvmName(\"get$oneof_capitalized_name$Case\")\n"
+        "  get() = _builder.get$oneof_capitalized_name$Case()\n\n"
+        "fun clear$oneof_capitalized_name$() {\n"
+        "  _builder.clear$oneof_capitalized_name$()\n"
+        "}\n",
+        "oneof_name", context_->GetOneofGeneratorInfo(oneof)->name,
+        "oneof_capitalized_name",
+        context_->GetOneofGeneratorInfo(oneof)->capitalized_name, "message",
+        name_resolver_->GetClassName(descriptor_, true));
+  }
+
+  if (descriptor_->extension_range_count() > 0) {
+    GenerateKotlinExtensions(printer);
+  }
+
+  printer->Outdent();
+  printer->Print("}\n");
+}
+
+void ImmutableMessageLiteGenerator::GenerateKotlinMembers(
+    io::Printer* printer) const {
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "inline fun $camelcase_name$(block: $message_kt$.Dsl.() -> Unit): "
+      "$message$ =\n"
+      "  $message_kt$.Dsl._create($message$.newBuilder()).apply { block() "
+      "}._build()\n",
+      "camelcase_name", name_resolver_->GetKotlinFactoryName(descriptor_),
+      "message_kt", name_resolver_->GetKotlinExtensionsClassName(descriptor_),
+      "message", name_resolver_->GetClassName(descriptor_, true));
+
+  printer->Print("object $name$Kt {\n", "name", descriptor_->name());
+  printer->Indent();
+  GenerateKotlinDsl(printer);
+  for (int i = 0; i < descriptor_->nested_type_count(); i++) {
+    if (IsMapEntry(descriptor_->nested_type(i))) continue;
+    ImmutableMessageLiteGenerator(descriptor_->nested_type(i), context_)
+        .GenerateKotlinMembers(printer);
+  }
+  printer->Outdent();
+  printer->Print("}\n");
+}
+
+void ImmutableMessageLiteGenerator::GenerateTopLevelKotlinMembers(
+    io::Printer* printer) const {
+  printer->Print(
+      "inline fun $message$.copy(block: $message_kt$.Dsl.() -> Unit): "
+      "$message$ =\n"
+      "  $message_kt$.Dsl._create(this.toBuilder()).apply { block() "
+      "}._build()\n",
+      "message", name_resolver_->GetClassName(descriptor_, true), "message_kt",
+      name_resolver_->GetKotlinExtensionsClassName(descriptor_));
+
+  for (int i = 0; i < descriptor_->nested_type_count(); i++) {
+    if (IsMapEntry(descriptor_->nested_type(i))) continue;
+    ImmutableMessageLiteGenerator(descriptor_->nested_type(i), context_)
+        .GenerateTopLevelKotlinMembers(printer);
+  }
+}
+
+void ImmutableMessageLiteGenerator::GenerateKotlinExtensions(
+    io::Printer* printer) const {
+  std::string message_name = name_resolver_->GetClassName(descriptor_, true);
+
+  printer->Print(
+      "@Suppress(\"UNCHECKED_CAST\")\n"
+      "@kotlin.jvm.JvmSynthetic\n"
+      "operator fun <T> get(extension: "
+      "com.google.protobuf.ExtensionLite<$message$, T>): T {\n"
+      "  return if (extension.isRepeated) {\n"
+      "    get(extension as com.google.protobuf.ExtensionLite<$message$, "
+      "List<*>>) as T\n"
+      "  } else {\n"
+      "    _builder.getExtension(extension)\n"
+      "  }\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.OptIn"
+      "(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)\n"
+      "@kotlin.jvm.JvmName(\"-getRepeatedExtension\")\n"
+      "operator fun <E> get(\n"
+      "  extension: com.google.protobuf.ExtensionLite<$message$, List<E>>\n"
+      "): com.google.protobuf.kotlin.ExtensionList<E, $message$> {\n"
+      "  return com.google.protobuf.kotlin.ExtensionList(extension, "
+      "_builder.getExtension(extension))\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "operator fun contains(extension: "
+      "com.google.protobuf.ExtensionLite<$message$, *>): "
+      "Boolean {\n"
+      "  return _builder.hasExtension(extension)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "fun clear(extension: com.google.protobuf.ExtensionLite<$message$, *>) "
+      "{\n"
+      "  _builder.clearExtension(extension)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.PublishedApi\n"
+      "internal fun <T> setExtension(extension: "
+      "com.google.protobuf.ExtensionLite<$message$, T>, "
+      "value: T) {\n"
+      "  _builder.setExtension(extension, value)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "inline operator fun <T : Comparable<T>> set(\n"
+      "  extension: com.google.protobuf.ExtensionLite<$message$, T>,\n"
+      "  value: T\n"
+      ") {\n"
+      "  setExtension(extension, value)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "inline operator fun set(\n"
+      "  extension: com.google.protobuf.ExtensionLite<$message$, "
+      "com.google.protobuf.ByteString>,\n"
+      "  value: com.google.protobuf.ByteString\n"
+      ") {\n"
+      "  setExtension(extension, value)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "inline operator fun <T : com.google.protobuf.MessageLite> set(\n"
+      "  extension: com.google.protobuf.ExtensionLite<$message$, T>,\n"
+      "  value: T\n"
+      ") {\n"
+      "  setExtension(extension, value)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "fun <E> com.google.protobuf.kotlin.ExtensionList<E, "
+      "$message$>.add(value: E) {\n"
+      "  _builder.addExtension(this.extension, value)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "inline operator fun <E> com.google.protobuf.kotlin.ExtensionList<E, "
+      "$message$>.plusAssign"
+      "(value: E) {\n"
+      "  add(value)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "fun <E> com.google.protobuf.kotlin.ExtensionList<E, "
+      "$message$>.addAll(values: Iterable<E>) {\n"
+      "  for (value in values) {\n"
+      "    add(value)\n"
+      "  }\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "inline operator fun <E> com.google.protobuf.kotlin.ExtensionList<E, "
+      "$message$>.plusAssign(values: "
+      "Iterable<E>) {\n"
+      "  addAll(values)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "operator fun <E> com.google.protobuf.kotlin.ExtensionList<E, "
+      "$message$>.set(index: Int, value: "
+      "E) {\n"
+      "  _builder.setExtension(this.extension, index, value)\n"
+      "}\n\n",
+      "message", message_name);
+
+  printer->Print(
+      "@kotlin.jvm.JvmSynthetic\n"
+      "inline fun com.google.protobuf.kotlin.ExtensionList<*, "
+      "$message$>.clear() {\n"
+      "  clear(extension)\n"
+      "}\n\n",
+      "message", message_name);
+}
+
 }  // namespace java
 }  // namespace compiler
 }  // namespace protobuf
diff --git a/src/google/protobuf/compiler/java/java_message_lite.h b/src/google/protobuf/compiler/java/java_message_lite.h
index 5290b1e..4dfa291 100644
--- a/src/google/protobuf/compiler/java/java_message_lite.h
+++ b/src/google/protobuf/compiler/java/java_message_lite.h
@@ -56,6 +56,9 @@
   virtual void GenerateStaticVariables(io::Printer* printer,
                                        int* bytecode_estimate);
   virtual int GenerateStaticVariableInitializers(io::Printer* printer);
+  void GenerateKotlinDsl(io::Printer* printer) const override;
+  void GenerateKotlinMembers(io::Printer* printer) const override;
+  void GenerateTopLevelKotlinMembers(io::Printer* printer) const override;
 
  private:
   void GenerateParseFromMethods(io::Printer* printer);
@@ -66,6 +69,7 @@
   void GenerateParser(io::Printer* printer);
   void GenerateConstructor(io::Printer* printer);
   void GenerateDynamicMethodNewBuildMessageInfo(io::Printer* printer);
+  void GenerateKotlinExtensions(io::Printer* printer) const;
 
   Context* context_;
   ClassNameResolver* name_resolver_;
diff --git a/src/google/protobuf/compiler/java/java_name_resolver.cc b/src/google/protobuf/compiler/java/java_name_resolver.cc
index ed33dae..43c7db5 100644
--- a/src/google/protobuf/compiler/java/java_name_resolver.cc
+++ b/src/google/protobuf/compiler/java/java_name_resolver.cc
@@ -69,6 +69,16 @@
   return StripPackageName(descriptor->full_name(), descriptor->file());
 }
 
+std::string ClassNameWithoutPackageKotlin(const Descriptor* descriptor) {
+  std::string result = descriptor->name();
+  const Descriptor* temp = descriptor->containing_type();
+
+  while (temp) {
+    result = temp->name() + "Kt." + result;
+    temp = temp->containing_type();
+  }
+  return result;
+}
 
 // Get the name of an enum's Java class without package name prefix.
 std::string ClassNameWithoutPackage(const EnumDescriptor* descriptor,
@@ -316,6 +326,12 @@
          descriptor->name();
 }
 
+std::string ClassNameResolver::GetKotlinFactoryName(
+    const Descriptor* descriptor) {
+  std::string name = ToCamelCase(descriptor->name(), /* lower_first = */ true);
+  return IsForbiddenKotlin(name) ? name + "_" : name;
+}
+
 std::string ClassNameResolver::GetJavaImmutableClassName(
     const Descriptor* descriptor) {
   return GetJavaClassFullName(ClassNameWithoutPackage(descriptor, true),
@@ -328,6 +344,12 @@
                               descriptor->file(), true);
 }
 
+std::string ClassNameResolver::GetKotlinExtensionsClassName(
+    const Descriptor* descriptor) {
+  return GetClassFullName(ClassNameWithoutPackageKotlin(descriptor),
+                          descriptor->file(), true, true, true);
+}
+
 
 }  // namespace java
 }  // namespace compiler
diff --git a/src/google/protobuf/compiler/java/java_name_resolver.h b/src/google/protobuf/compiler/java/java_name_resolver.h
index 8461df9..9717d92 100644
--- a/src/google/protobuf/compiler/java/java_name_resolver.h
+++ b/src/google/protobuf/compiler/java/java_name_resolver.h
@@ -115,6 +115,8 @@
   //   com.package.OuterClass$OuterMessage$InnerMessage
   std::string GetJavaImmutableClassName(const Descriptor* descriptor);
   std::string GetJavaImmutableClassName(const EnumDescriptor* descriptor);
+  std::string GetKotlinFactoryName(const Descriptor* descriptor);
+  std::string GetKotlinExtensionsClassName(const Descriptor* descriptor);
  private:
   // Get the full name of a Java class by prepending the Java package name
   // or outer class name.
diff --git a/src/google/protobuf/compiler/java/java_primitive_field.cc b/src/google/protobuf/compiler/java/java_primitive_field.cc
index 65cc05a..2bc565c 100644
--- a/src/google/protobuf/compiler/java/java_primitive_field.cc
+++ b/src/google/protobuf/compiler/java/java_primitive_field.cc
@@ -68,6 +68,7 @@
 
   (*variables)["type"] = PrimitiveTypeName(javaType);
   (*variables)["boxed_type"] = BoxedPrimitiveTypeName(javaType);
+  (*variables)["kt_type"] = KotlinTypeName(javaType);
   (*variables)["field_type"] = (*variables)["type"];
 
   if (javaType == JAVATYPE_BOOLEAN || javaType == JAVATYPE_DOUBLE ||
@@ -129,6 +130,11 @@
   // by the proto compiler
   (*variables)["deprecation"] =
       descriptor->options().deprecated() ? "@java.lang.Deprecated " : "";
+  (*variables)["kt_deprecation"] =
+      descriptor->options().deprecated()
+          ? "@kotlin.Deprecated(message = \"Field " + (*variables)["name"] +
+                " is deprecated\") "
+          : "";
   int fixed_size = FixedSize(GetType(descriptor));
   if (fixed_size != -1) {
     (*variables)["fixed_size"] = StrCat(fixed_size);
@@ -298,6 +304,33 @@
                  "}\n");
 }
 
+void ImmutablePrimitiveFieldGenerator::GenerateKotlinDslMembers(
+    io::Printer* printer) const {
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(variables_,
+                 "$kt_deprecation$var $kt_name$: $kt_type$\n"
+                 "  @JvmName(\"${$get$kt_capitalized_name$$}$\")\n"
+                 "  get() = $kt_dsl_builder$.${$get$capitalized_name$$}$()\n"
+                 "  @JvmName(\"${$set$kt_capitalized_name$$}$\")\n"
+                 "  set(value) {\n"
+                 "    $kt_dsl_builder$.${$set$capitalized_name$$}$(value)\n"
+                 "  }\n");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "fun ${$clear$kt_capitalized_name$$}$() {\n"
+                 "  $kt_dsl_builder$.${$clear$capitalized_name$$}$()\n"
+                 "}\n");
+
+  if (HasHazzer(descriptor_)) {
+    WriteFieldAccessorDocComment(printer, descriptor_, HAZZER);
+    printer->Print(variables_,
+                   "fun ${$has$kt_capitalized_name$$}$(): kotlin.Boolean {\n"
+                   "  return $kt_dsl_builder$.${$has$capitalized_name$$}$()\n"
+                   "}\n");
+  }
+}
 
 void ImmutablePrimitiveFieldGenerator::GenerateFieldBuilderInitializationCode(
     io::Printer* printer) const {
@@ -793,6 +826,98 @@
   printer->Annotate("{", "}", descriptor_);
 }
 
+void RepeatedImmutablePrimitiveFieldGenerator::GenerateKotlinDslMembers(
+    io::Printer* printer) const {
+  printer->Print(
+      variables_,
+      "/**\n"
+      " * An uninstantiable, behaviorless type to represent the field in\n"
+      " * generics.\n"
+      " */\n"
+      "@kotlin.OptIn"
+      "(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)\n"
+      "class ${$$kt_capitalized_name$Proxy$}$ private constructor()"
+      " : com.google.protobuf.kotlin.DslProxy()\n");
+
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(variables_,
+                 "$kt_deprecation$ val $kt_name$: "
+                 "com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
+                 "  @kotlin.jvm.JvmSynthetic\n"
+                 "  get() = com.google.protobuf.kotlin.DslList(\n"
+                 "    $kt_dsl_builder$.${$get$capitalized_name$List$}$()\n"
+                 "  )\n");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"add$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "add(value: $kt_type$) {\n"
+                 "  $kt_dsl_builder$.${$add$capitalized_name$$}$(value)\n"
+                 "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"plusAssign$kt_capitalized_name$\")\n"
+                 "inline operator fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "plusAssign(value: $kt_type$) {\n"
+                 "  add(value)\n"
+                 "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_MULTI_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"addAll$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "addAll(values: kotlin.collections.Iterable<$kt_type$>) {\n"
+                 "  $kt_dsl_builder$.${$addAll$capitalized_name$$}$(values)\n"
+                 "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_MULTI_ADDER,
+                               /* builder */ false);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.jvm.JvmName(\"plusAssignAll$kt_capitalized_name$\")\n"
+      "inline operator fun com.google.protobuf.kotlin.DslList"
+      "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+      "plusAssign(values: kotlin.collections.Iterable<$kt_type$>) {\n"
+      "  addAll(values)\n"
+      "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_INDEXED_SETTER,
+                               /* builder */ false);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.jvm.JvmName(\"set$kt_capitalized_name$\")\n"
+      "operator fun com.google.protobuf.kotlin.DslList"
+      "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+      "set(index: kotlin.Int, value: $kt_type$) {\n"
+      "  $kt_dsl_builder$.${$set$capitalized_name$$}$(index, value)\n"
+      "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"clear$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "clear() {\n"
+                 "  $kt_dsl_builder$.${$clear$capitalized_name$$}$()\n"
+                 "}");
+}
+
 void RepeatedImmutablePrimitiveFieldGenerator::
     GenerateFieldBuilderInitializationCode(io::Printer* printer) const {
   // noop for primitives
diff --git a/src/google/protobuf/compiler/java/java_primitive_field.h b/src/google/protobuf/compiler/java/java_primitive_field.h
index db20750..56be916 100644
--- a/src/google/protobuf/compiler/java/java_primitive_field.h
+++ b/src/google/protobuf/compiler/java/java_primitive_field.h
@@ -81,6 +81,7 @@
   void GenerateFieldBuilderInitializationCode(io::Printer* printer) const;
   void GenerateEqualsCode(io::Printer* printer) const;
   void GenerateHashCode(io::Printer* printer) const;
+  void GenerateKotlinDslMembers(io::Printer* printer) const;
 
   std::string GetBoxedType() const;
 
@@ -139,6 +140,7 @@
   void GenerateFieldBuilderInitializationCode(io::Printer* printer) const;
   void GenerateEqualsCode(io::Printer* printer) const;
   void GenerateHashCode(io::Printer* printer) const;
+  void GenerateKotlinDslMembers(io::Printer* printer) const;
 
   std::string GetBoxedType() const;
 
diff --git a/src/google/protobuf/compiler/java/java_primitive_field_lite.cc b/src/google/protobuf/compiler/java/java_primitive_field_lite.cc
index 1932e99..cc589f3 100644
--- a/src/google/protobuf/compiler/java/java_primitive_field_lite.cc
+++ b/src/google/protobuf/compiler/java/java_primitive_field_lite.cc
@@ -74,6 +74,7 @@
   JavaType javaType = GetJavaType(descriptor);
   (*variables)["type"] = PrimitiveTypeName(javaType);
   (*variables)["boxed_type"] = BoxedPrimitiveTypeName(javaType);
+  (*variables)["kt_type"] = KotlinTypeName(javaType);
   (*variables)["field_type"] = (*variables)["type"];
   (*variables)["default"] = ImmutableDefaultValue(descriptor, name_resolver);
   (*variables)["capitalized_type"] =
@@ -137,6 +138,11 @@
   // by the proto compiler
   (*variables)["deprecation"] =
       descriptor->options().deprecated() ? "@java.lang.Deprecated " : "";
+  (*variables)["kt_deprecation"] =
+      descriptor->options().deprecated()
+          ? "@kotlin.Deprecated(message = \"Field " + (*variables)["name"] +
+                " is deprecated\") "
+          : "";
   int fixed_size = FixedSize(GetType(descriptor));
   if (fixed_size != -1) {
     (*variables)["fixed_size"] = StrCat(fixed_size);
@@ -303,6 +309,33 @@
   printer->Annotate("{", "}", descriptor_);
 }
 
+void ImmutablePrimitiveFieldLiteGenerator::GenerateKotlinDslMembers(
+    io::Printer* printer) const {
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(variables_,
+                 "$kt_deprecation$var $kt_name$: $kt_type$\n"
+                 "  @JvmName(\"${$get$kt_capitalized_name$$}$\")\n"
+                 "  get() = $kt_dsl_builder$.${$get$capitalized_name$$}$()\n"
+                 "  @JvmName(\"${$set$kt_capitalized_name$$}$\")\n"
+                 "  set(value) {\n"
+                 "    $kt_dsl_builder$.${$set$capitalized_name$$}$(value)\n"
+                 "  }\n");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "fun ${$clear$kt_capitalized_name$$}$() {\n"
+                 "  $kt_dsl_builder$.${$clear$capitalized_name$$}$()\n"
+                 "}\n");
+
+  if (HasHazzer(descriptor_)) {
+    WriteFieldAccessorDocComment(printer, descriptor_, HAZZER);
+    printer->Print(variables_,
+                   "fun ${$has$kt_capitalized_name$$}$(): kotlin.Boolean {\n"
+                   "  return $kt_dsl_builder$.${$has$capitalized_name$$}$()\n"
+                   "}\n");
+  }
+}
 
 void ImmutablePrimitiveFieldLiteGenerator::GenerateFieldInfo(
     io::Printer* printer, std::vector<uint16_t>* output) const {
@@ -615,6 +648,98 @@
   printer->Annotate("{", "}", descriptor_);
 }
 
+void RepeatedImmutablePrimitiveFieldLiteGenerator::GenerateKotlinDslMembers(
+    io::Printer* printer) const {
+  printer->Print(
+      variables_,
+      "/**\n"
+      " * An uninstantiable, behaviorless type to represent the field in\n"
+      " * generics.\n"
+      " */\n"
+      "@kotlin.OptIn"
+      "(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)\n"
+      "class ${$$kt_capitalized_name$Proxy$}$ private constructor()"
+      " : com.google.protobuf.kotlin.DslProxy()\n");
+
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(variables_,
+                 "$kt_deprecation$ val $kt_name$: "
+                 "com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
+                 "  @kotlin.jvm.JvmSynthetic\n"
+                 "  get() = com.google.protobuf.kotlin.DslList(\n"
+                 "    $kt_dsl_builder$.${$get$capitalized_name$List$}$()\n"
+                 "  )\n");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"add$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "add(value: $kt_type$) {\n"
+                 "  $kt_dsl_builder$.${$add$capitalized_name$$}$(value)\n"
+                 "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"plusAssign$kt_capitalized_name$\")\n"
+                 "inline operator fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "plusAssign(value: $kt_type$) {\n"
+                 "  add(value)\n"
+                 "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_MULTI_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"addAll$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "addAll(values: kotlin.collections.Iterable<$kt_type$>) {\n"
+                 "  $kt_dsl_builder$.${$addAll$capitalized_name$$}$(values)\n"
+                 "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_MULTI_ADDER,
+                               /* builder */ false);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.jvm.JvmName(\"plusAssignAll$kt_capitalized_name$\")\n"
+      "inline operator fun com.google.protobuf.kotlin.DslList"
+      "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+      "plusAssign(values: kotlin.collections.Iterable<$kt_type$>) {\n"
+      "  addAll(values)\n"
+      "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_INDEXED_SETTER,
+                               /* builder */ false);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.jvm.JvmName(\"set$kt_capitalized_name$\")\n"
+      "operator fun com.google.protobuf.kotlin.DslList"
+      "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+      "set(index: kotlin.Int, value: $kt_type$) {\n"
+      "  $kt_dsl_builder$.${$set$capitalized_name$$}$(index, value)\n"
+      "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"clear$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>."
+                 "clear() {\n"
+                 "  $kt_dsl_builder$.${$clear$capitalized_name$$}$()\n"
+                 "}");
+}
+
 void RepeatedImmutablePrimitiveFieldLiteGenerator::GenerateFieldInfo(
     io::Printer* printer, std::vector<uint16_t>* output) const {
   WriteIntToUtf16CharSequence(descriptor_->number(), output);
diff --git a/src/google/protobuf/compiler/java/java_primitive_field_lite.h b/src/google/protobuf/compiler/java/java_primitive_field_lite.h
index 22a2557..b9fcacd 100644
--- a/src/google/protobuf/compiler/java/java_primitive_field_lite.h
+++ b/src/google/protobuf/compiler/java/java_primitive_field_lite.h
@@ -73,6 +73,7 @@
   void GenerateInitializationCode(io::Printer* printer) const;
   void GenerateFieldInfo(io::Printer* printer,
                          std::vector<uint16_t>* output) const;
+  void GenerateKotlinDslMembers(io::Printer* printer) const;
 
   std::string GetBoxedType() const;
 
@@ -119,7 +120,8 @@
   void GenerateInitializationCode(io::Printer* printer) const;
   void GenerateFieldInfo(io::Printer* printer,
                          std::vector<uint16_t>* output) const;
-
+  void GenerateKotlinDslMembers(io::Printer* printer) const;
+      
   std::string GetBoxedType() const;
 
  private:
diff --git a/src/google/protobuf/compiler/java/java_string_field.cc b/src/google/protobuf/compiler/java/java_string_field.cc
index 2e9a9e7..8d72d95 100644
--- a/src/google/protobuf/compiler/java/java_string_field.cc
+++ b/src/google/protobuf/compiler/java/java_string_field.cc
@@ -90,6 +90,11 @@
   // by the proto compiler
   (*variables)["deprecation"] =
       descriptor->options().deprecated() ? "@java.lang.Deprecated " : "";
+  (*variables)["kt_deprecation"] =
+      descriptor->options().deprecated()
+          ? "@kotlin.Deprecated(message = \"Field " + (*variables)["name"] +
+                " is deprecated\") "
+          : "";
   (*variables)["on_changed"] = "onChanged();";
 
   if (HasHasbit(descriptor)) {
@@ -367,6 +372,34 @@
                  "}\n");
 }
 
+void ImmutableStringFieldGenerator::GenerateKotlinDslMembers(
+    io::Printer* printer) const {
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(variables_,
+                 "$kt_deprecation$var $kt_name$: kotlin.String\n"
+                 "  @JvmName(\"${$get$kt_capitalized_name$$}$\")\n"
+                 "  get() = $kt_dsl_builder$.${$get$capitalized_name$$}$()\n"
+                 "  @JvmName(\"${$set$kt_capitalized_name$$}$\")\n"
+                 "  set(value) {\n"
+                 "    $kt_dsl_builder$.${$set$capitalized_name$$}$(value)\n"
+                 "  }\n");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "fun ${$clear$kt_capitalized_name$$}$() {\n"
+                 "  $kt_dsl_builder$.${$clear$capitalized_name$$}$()\n"
+                 "}\n");
+
+  if (HasHazzer(descriptor_)) {
+    WriteFieldAccessorDocComment(printer, descriptor_, HAZZER);
+    printer->Print(variables_,
+                   "fun ${$has$kt_capitalized_name$$}$(): kotlin.Boolean {\n"
+                   "  return $kt_dsl_builder$.${$has$capitalized_name$$}$()\n"
+                   "}\n");
+  }
+}
+
 void ImmutableStringFieldGenerator::GenerateFieldBuilderInitializationCode(
     io::Printer* printer) const {
   // noop for primitives
@@ -916,6 +949,107 @@
                  "}\n");
 }
 
+void RepeatedImmutableStringFieldGenerator::GenerateKotlinDslMembers(
+    io::Printer* printer) const {
+  printer->Print(
+      variables_,
+      "/**\n"
+      " * An uninstantiable, behaviorless type to represent the field in\n"
+      " * generics.\n"
+      " */\n"
+      "@kotlin.OptIn"
+      "(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)\n"
+      "class ${$$kt_capitalized_name$Proxy$}$ private constructor()"
+      " : com.google.protobuf.kotlin.DslProxy()\n");
+
+  // property for List<String>
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_GETTER);
+  printer->Print(
+      variables_,
+      "val $kt_name$: "
+      "com.google.protobuf.kotlin.DslList"
+      "<kotlin.String, ${$$kt_capitalized_name$Proxy$}$>\n"
+      "  @kotlin.OptIn"
+      "(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)\n"
+      "  get() = com.google.protobuf.kotlin.DslList(\n"
+      "    $kt_dsl_builder$.${$get$capitalized_name$List$}$()\n"
+      "  )\n");
+
+  // List<String>.add(String)
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"add$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<kotlin.String, ${$$kt_capitalized_name$Proxy$}$>."
+                 "add(value: kotlin.String) {\n"
+                 "  $kt_dsl_builder$.${$add$capitalized_name$$}$(value)\n"
+                 "}\n");
+
+  // List<String> += String
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"plusAssign$kt_capitalized_name$\")\n"
+                 "operator fun com.google.protobuf.kotlin.DslList"
+                 "<kotlin.String, ${$$kt_capitalized_name$Proxy$}$>."
+                 "plusAssign(value: kotlin.String) {\n"
+                 "  $kt_dsl_builder$.${$add$capitalized_name$$}$(value)\n"
+                 "}\n");
+
+  // List<String>.addAll(Iterable<String>)
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_MULTI_ADDER,
+                               /* builder */ false);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.jvm.JvmName(\"addAll$kt_capitalized_name$\")\n"
+      "fun com.google.protobuf.kotlin.DslList"
+      "<kotlin.String, ${$$kt_capitalized_name$Proxy$}$>."
+      "addAll(values: kotlin.collections.Iterable<kotlin.String>) {\n"
+      "  $kt_dsl_builder$.${$addAll$capitalized_name$$}$(values)\n"
+      "}\n");
+
+  // List<String> += Iterable<String>
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_MULTI_ADDER,
+                               /* builder */ false);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.jvm.JvmName(\"plusAssignAll$kt_capitalized_name$\")\n"
+      "operator fun com.google.protobuf.kotlin.DslList"
+      "<kotlin.String, ${$$kt_capitalized_name$Proxy$}$>."
+      "plusAssign(values: kotlin.collections.Iterable<kotlin.String>) {\n"
+      "  $kt_dsl_builder$.${$addAll$capitalized_name$$}$(values)\n"
+      "}\n");
+
+  // List<String>[Int] = String
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_INDEXED_SETTER,
+                               /* builder */ false);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.jvm.JvmName(\"set$kt_capitalized_name$\")\n"
+      "operator fun com.google.protobuf.kotlin.DslList"
+      "<kotlin.String, ${$$kt_capitalized_name$Proxy$}$>."
+      "set(index: kotlin.Int, value: kotlin.String) {\n"
+      "  $kt_dsl_builder$.${$set$capitalized_name$$}$(index, value)\n"
+      "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"clear$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<kotlin.String, ${$$kt_capitalized_name$Proxy$}$>."
+                 "clear() {\n"
+                 "  $kt_dsl_builder$.${$clear$capitalized_name$$}$()\n"
+                 "}");
+}
+
 void RepeatedImmutableStringFieldGenerator::
     GenerateFieldBuilderInitializationCode(io::Printer* printer) const {
   // noop for primitives
diff --git a/src/google/protobuf/compiler/java/java_string_field.h b/src/google/protobuf/compiler/java/java_string_field.h
index 1c00ae8..6cc9126 100644
--- a/src/google/protobuf/compiler/java/java_string_field.h
+++ b/src/google/protobuf/compiler/java/java_string_field.h
@@ -81,6 +81,7 @@
   void GenerateFieldBuilderInitializationCode(io::Printer* printer) const;
   void GenerateEqualsCode(io::Printer* printer) const;
   void GenerateHashCode(io::Printer* printer) const;
+  void GenerateKotlinDslMembers(io::Printer* printer) const;
 
   std::string GetBoxedType() const;
 
@@ -137,6 +138,7 @@
   void GenerateFieldBuilderInitializationCode(io::Printer* printer) const;
   void GenerateEqualsCode(io::Printer* printer) const;
   void GenerateHashCode(io::Printer* printer) const;
+  void GenerateKotlinDslMembers(io::Printer* printer) const;
 
   std::string GetBoxedType() const;
 
diff --git a/src/google/protobuf/compiler/java/java_string_field_lite.cc b/src/google/protobuf/compiler/java/java_string_field_lite.cc
index 63702c1..f22fc4b 100644
--- a/src/google/protobuf/compiler/java/java_string_field_lite.cc
+++ b/src/google/protobuf/compiler/java/java_string_field_lite.cc
@@ -86,6 +86,11 @@
   // by the proto compiler
   (*variables)["deprecation"] =
       descriptor->options().deprecated() ? "@java.lang.Deprecated " : "";
+  (*variables)["kt_deprecation"] =
+      descriptor->options().deprecated()
+          ? "@kotlin.Deprecated(message = \"Field " + (*variables)["name"] +
+                " is deprecated\") "
+          : "";
   (*variables)["required"] = descriptor->is_required() ? "true" : "false";
 
   if (HasHasbit(descriptor)) {
@@ -304,6 +309,34 @@
   printer->Annotate("{", "}", descriptor_);
 }
 
+void ImmutableStringFieldLiteGenerator::GenerateKotlinDslMembers(
+    io::Printer* printer) const {
+  WriteFieldDocComment(printer, descriptor_);
+  printer->Print(variables_,
+                 "$kt_deprecation$var $kt_name$: kotlin.String\n"
+                 "  @JvmName(\"${$get$kt_capitalized_name$$}$\")\n"
+                 "  get() = $kt_dsl_builder$.${$get$capitalized_name$$}$()\n"
+                 "  @JvmName(\"${$set$kt_capitalized_name$$}$\")\n"
+                 "  set(value) {\n"
+                 "    $kt_dsl_builder$.${$set$capitalized_name$$}$(value)\n"
+                 "  }\n");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "fun ${$clear$kt_capitalized_name$$}$() {\n"
+                 "  $kt_dsl_builder$.${$clear$capitalized_name$$}$()\n"
+                 "}\n");
+
+  if (HasHazzer(descriptor_)) {
+    WriteFieldAccessorDocComment(printer, descriptor_, HAZZER);
+    printer->Print(variables_,
+                   "fun ${$has$kt_capitalized_name$$}$(): kotlin.Boolean {\n"
+                   "  return $kt_dsl_builder$.${$has$capitalized_name$$}$()\n"
+                   "}\n");
+  }
+}
+
 void ImmutableStringFieldLiteGenerator::GenerateFieldInfo(
     io::Printer* printer, std::vector<uint16_t>* output) const {
   WriteIntToUtf16CharSequence(descriptor_->number(), output);
@@ -704,6 +737,107 @@
   printer->Annotate("{", "}", descriptor_);
 }
 
+void RepeatedImmutableStringFieldLiteGenerator::GenerateKotlinDslMembers(
+    io::Printer* printer) const {
+  printer->Print(
+      variables_,
+      "/**\n"
+      " * An uninstantiable, behaviorless type to represent the field in\n"
+      " * generics.\n"
+      " */\n"
+      "@kotlin.OptIn"
+      "(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)\n"
+      "class ${$$kt_capitalized_name$Proxy$}$ private constructor()"
+      " : com.google.protobuf.kotlin.DslProxy()\n");
+
+  // property for List<String>
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_GETTER);
+  printer->Print(
+      variables_,
+      "val $kt_name$: "
+      "com.google.protobuf.kotlin.DslList"
+      "<kotlin.String, ${$$kt_capitalized_name$Proxy$}$>\n"
+      "  @kotlin.OptIn"
+      "(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)\n"
+      "  get() = com.google.protobuf.kotlin.DslList(\n"
+      "    $kt_dsl_builder$.${$get$capitalized_name$List$}$()\n"
+      "  )\n");
+
+  // List<String>.add(String)
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"add$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<kotlin.String, ${$$kt_capitalized_name$Proxy$}$>."
+                 "add(value: kotlin.String) {\n"
+                 "  $kt_dsl_builder$.${$add$capitalized_name$$}$(value)\n"
+                 "}\n");
+
+  // List<String> += String
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_ADDER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"plusAssign$kt_capitalized_name$\")\n"
+                 "inline operator fun com.google.protobuf.kotlin.DslList"
+                 "<kotlin.String, ${$$kt_capitalized_name$Proxy$}$>."
+                 "plusAssign(value: kotlin.String) {\n"
+                 "  add(value)\n"
+                 "}\n");
+
+  // List<String>.addAll(Iterable<String>)
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_MULTI_ADDER,
+                               /* builder */ false);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.jvm.JvmName(\"addAll$kt_capitalized_name$\")\n"
+      "fun com.google.protobuf.kotlin.DslList"
+      "<kotlin.String, ${$$kt_capitalized_name$Proxy$}$>."
+      "addAll(values: kotlin.collections.Iterable<kotlin.String>) {\n"
+      "  $kt_dsl_builder$.${$addAll$capitalized_name$$}$(values)\n"
+      "}\n");
+
+  // List<String> += Iterable<String>
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_MULTI_ADDER,
+                               /* builder */ false);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.jvm.JvmName(\"plusAssignAll$kt_capitalized_name$\")\n"
+      "inline operator fun com.google.protobuf.kotlin.DslList"
+      "<kotlin.String, ${$$kt_capitalized_name$Proxy$}$>."
+      "plusAssign(values: kotlin.collections.Iterable<kotlin.String>) {\n"
+      "  addAll(values)\n"
+      "}\n");
+
+  // List<String>[Int] = String
+  WriteFieldAccessorDocComment(printer, descriptor_, LIST_INDEXED_SETTER,
+                               /* builder */ false);
+  printer->Print(
+      variables_,
+      "@kotlin.jvm.JvmSynthetic\n"
+      "@kotlin.jvm.JvmName(\"set$kt_capitalized_name$\")\n"
+      "operator fun com.google.protobuf.kotlin.DslList"
+      "<kotlin.String, ${$$kt_capitalized_name$Proxy$}$>."
+      "set(index: kotlin.Int, value: kotlin.String) {\n"
+      "  $kt_dsl_builder$.${$set$capitalized_name$$}$(index, value)\n"
+      "}");
+
+  WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
+                               /* builder */ false);
+  printer->Print(variables_,
+                 "@kotlin.jvm.JvmSynthetic\n"
+                 "@kotlin.jvm.JvmName(\"clear$kt_capitalized_name$\")\n"
+                 "fun com.google.protobuf.kotlin.DslList"
+                 "<kotlin.String, ${$$kt_capitalized_name$Proxy$}$>."
+                 "clear() {\n"
+                 "  $kt_dsl_builder$.${$clear$capitalized_name$$}$()\n"
+                 "}");
+}
+
 void RepeatedImmutableStringFieldLiteGenerator::GenerateFieldInfo(
     io::Printer* printer, std::vector<uint16_t>* output) const {
   WriteIntToUtf16CharSequence(descriptor_->number(), output);
diff --git a/src/google/protobuf/compiler/java/java_string_field_lite.h b/src/google/protobuf/compiler/java/java_string_field_lite.h
index 194a19d..c95256f 100644
--- a/src/google/protobuf/compiler/java/java_string_field_lite.h
+++ b/src/google/protobuf/compiler/java/java_string_field_lite.h
@@ -74,6 +74,7 @@
   void GenerateInitializationCode(io::Printer* printer) const;
   void GenerateFieldInfo(io::Printer* printer,
                          std::vector<uint16_t>* output) const;
+  void GenerateKotlinDslMembers(io::Printer* printer) const;
 
   std::string GetBoxedType() const;
 
@@ -118,6 +119,7 @@
   void GenerateInitializationCode(io::Printer* printer) const;
   void GenerateFieldInfo(io::Printer* printer,
                          std::vector<uint16_t>* output) const;
+  void GenerateKotlinDslMembers(io::Printer* printer) const;
 
   std::string GetBoxedType() const;
 
diff --git a/src/google/protobuf/compiler/main.cc b/src/google/protobuf/compiler/main.cc
index 895b47d..7cb7a63 100644
--- a/src/google/protobuf/compiler/main.cc
+++ b/src/google/protobuf/compiler/main.cc
@@ -30,6 +30,7 @@
 
 #include <google/protobuf/compiler/cpp/cpp_generator.h>
 #include <google/protobuf/compiler/java/java_generator.h>
+#include <google/protobuf/compiler/java/java_kotlin_generator.h>
 #include <google/protobuf/compiler/js/js_generator.h>
 #include <google/protobuf/compiler/command_line_interface.h>
 #include <google/protobuf/compiler/python/python_generator.h>
@@ -64,6 +65,10 @@
   cli.RegisterGenerator("--java_out", "--java_opt", &java_generator,
                         "Generate Java source file.");
 
+  // Proto2 Kotlin
+  java::KotlinGenerator kt_generator;
+  cli.RegisterGenerator("--kotlin_out", "--kotlin_opt", &kt_generator,
+                        "Generate Kotlin file.");
 
 
   // Proto2 Python