Kotlin Generated Code Guide
Any differences between proto2 and proto3 generated code are highlighted—note that these differences are in the generated code as described in this document, not the base message classes/interfaces, which are the same in both versions. You should read the proto2 language guide and/or proto3 language guide before reading this document.
Compiler Invocation
The protocol buffer compiler produces Kotlin code that builds on top of Java
code. As a result, it must be invoked with two command-line flags, --java_out=
and --kotlin_out=
. The parameter to the --java_out=
option is the directory
where you want the compiler to write your Java output, and the same for the
--kotlin_out=
. For each .proto
file input, the compiler creates a wrapper
.java
file containing a Java class which represents the .proto
file itself.
Regardless of whether or not your .proto
file contains a line like the
following:
option java_multiple_files = true;
The compiler will create separate .kt
files for each of the classes and
factory methods which it will generate for each top-level message declared in
the .proto
file.
The Java package name for each file is the same as that used by the generated Java code as described in the Java generated code reference.
The output file is chosen by concatenating the parameter to --kotlin_out=
, the
package name (with periods [.] replaced with slashes [/]), and the suffix
Kt.kt
file name.
So, for example, let’s say you invoke the compiler as follows:
protoc --proto_path=src --java_out=build/gen/java --kotlin_out=build/gen/kotlin src/foo.proto
If foo.proto
’s Java package is com.example
and it contains a message named
Bar
, then the protocol buffer compiler will generate the file
build/gen/kotlin/com/example/BarKt.kt
. The protocol buffer compiler will
automatically create the build/gen/kotlin/com
and
build/gen/kotlin/com/example
directories if needed. However, it will not
create build/gen/kotlin
, build/gen
, or build
; they must already exist. You
can specify multiple .proto
files in a single invocation; all output files
will be generated at once.
Messages
Given a simple message declaration:
message FooBar {}
The protocol buffer compiler generates—in addition to the generated Java
code—an object called FooBarKt
, as well as two top-level functions,
having the following structure:
object FooBarKt {
class Dsl private constructor { ... }
}
inline fun fooBar(block: FooBarKt.Dsl.() -> Unit): FooBar
inline fun FooBar.copy(block: FooBarKt.Dsl.() -> Unit): FooBar
Nested Types
A message can be declared inside another message. For example:
message Foo {
message Bar { }
}
In this case, the compiler nests the BarKt
object and the bar
factory method
inside FooKt
, though the copy
method remains top-level:
object FooKt {
class Dsl { ... }
object BarKt {
class Dsl private constructor { ... }
}
inline fun bar(block: FooKt.BarKt.Dsl.() -> Unit): Foo.Bar
}
inline fun foo(block: FooKt.Dsl.() -> Unit): Foo
inline fun Foo.copy(block: FooKt.Dsl.() -> Unit): Foo
inline fun Foo.Bar.copy(block: FooKt.BarKt.Dsl.() -> Unit): Foo.Bar
Fields
In addition to the methods described in the previous section, the protocol
buffer compiler generates mutable properties in the DSL for each field defined
within the message in the .proto
file. (Kotlin already infers read-only
properties on the message object from the getters generated by Java.)
Note that properties always use camel-case naming, even if the field name in the
.proto
file uses lower-case with underscores
(as it should). The
case-conversion works as follows:
- For each underscore in the name, the underscore is removed, and the following letter is capitalized.
- If the name will have a prefix attached (for example, “clear”), the first letter is capitalized. Otherwise, it is lower-cased.
Thus, the field foo_bar_baz
becomes fooBarBaz
.
In a few special cases in which a field name conflicts with reserved words in
Kotlin or methods already defined in the protobuf library, an extra underscore
is appended. For instance, the clearer for a field named in
is clearIn_()
.
Singular Fields (proto2)
For any of these field definitions:
optional int32 foo = 1;
required int32 foo = 1;
The compiler will generate the following accessors in the DSL:
fun hasFoo(): Boolean
: Returnstrue
if the field is set.var foo: Int
: The current value of the field. If the field is not set, returns the default value.fun clearFoo()
: Clears the value of the field. After calling this,hasFoo()
will returnfalse
andgetFoo()
will return the default value.
For other simple field types, the corresponding Java type is chosen according to the scalar value types table. For message and enum types, the value type is replaced with the message or enum class. As the message type is still defined in Java, unsigned types in the message are represented using the standard corresponding signed types in the DSL, for compatibility with Java and older versions of Kotlin.
Embedded Message Fields
Note that there is no special handling of submessages. For example, if you have a field
optional Foo my_foo = 1;
you must write
myFoo = foo {
...
}
In general, this is because the compiler does not know whether Foo
has a
Kotlin DSL at all, or e.g. only has the Java APIs generated. This means that you
do not have to wait for messages you depend on to add Kotlin code generation.
Singular Fields (proto3)
For this field definition:
int32 foo = 1;
The compiler will generate the following property in the DSL:
var foo: Int
: Returns the current value of the field. If the field is not set, returns the default value for the field’s type.fun clearFoo()
: Clears the value of the field. After calling this,getFoo()
will return the default value for the field’s type.
For other simple field types, the corresponding Java type is chosen according to the scalar value types table. For message and enum types, the value type is replaced with the message or enum class. As the message type is still defined in Java, unsigned types in the message are represented using the standard corresponding signed types in the DSL, for compatibility with Java and older versions of Kotlin.
Embedded Message Fields
For message field types, an additional accessor method is generated in the DSL:
boolean hasFoo()
: Returnstrue
if the field has been set.
Note that there is no shortcut for setting a submessage based on a DSL. For example, if you have a field
Foo my_foo = 1;
you must write
myFoo = foo {
...
}
In general, this is because the compiler does not know whether Foo
has a
Kotlin DSL at all, or e.g. only has the Java APIs generated. This means that you
do not have to wait for messages you depend on to add Kotlin code generation.
Repeated Fields
For this field definition:
repeated string foo = 1;
The compiler will generate the following members in the DSL:
class FooProxy: DslProxy
, an unconstructable type used only in genericsval fooList: DslList<String, FooProxy>
, a read-only view of the list of current elements in the repeated fieldfun DslList<String, FooProxy>.add(value: String)
, an extension function allowing elements to be added to the repeated fieldoperator fun DslList<String, FooProxy>.plusAssign(value: String)
, an alias foradd
fun DslList<String, FooProxy>.addAll(values: Iterable<String>)
, an extension function allowing anIterable
of elements to be added to the repeated fieldoperator fun DslList<String, FooProxy>.plusAssign(values: Iterable<String>)
, an alias foraddAll
operator fun DslList<String, FooProxy>.set(index: Int, value: String)
, an extension function setting the value of the element at the given zero-based indefun DslList<String, FooProxy>.clear()
, an extension function clearing the contents of the repeated field
This unusual construction allows fooList
to "behave like" a mutable list
within the scope of the DSL, supporting only the methods supported by the
underlying builder, while preventing mutability from "escaping" the DSL, which
could cause confusing side effects.
For other simple field types, the corresponding Java type is chosen according to the scalar value types table. For message and enum types, the type is the message or enum class.
Oneof Fields
For this oneof field definition:
oneof oneof_name {
int32 foo = 1;
...
}
The compiler will generate the following accessor methods in the DSL:
val oneofNameCase: OneofNameCase
: gets which, if any, of theoneof_name
fields are set; see the Java code reference for the return typefun hasFoo(): Boolean
(proto2 only): Returnstrue
if the oneof case isFOO
.val foo: Int
: Returns the current value ofoneof_name
if the oneof case isFOO
. Otherwise, returns the default value of this field.
For other simple field types, the corresponding Java type is chosen according to the scalar value types table. For message and enum types, the value type is replaced with the message or enum class.
Map Fields
For this map field definition:
map<int32, int32> weight = 1;
The compiler will generate the following members in the DSL class:
class WeightProxy private constructor(): DslProxy()
, an unconstructable type used only in genericsval weight: DslMap<Int, Int, WeightProxy>
, a read-only view of the current entries in the map fieldfun DslMap<Int, Int, WeightProxy>.put(key: Int, value: Int)
: add the entry to this map fieldoperator fun DslMap<Int, Int, WeightProxy>.put(key: Int, value: Int)
: alias forput
using operator syntaxfun DslMap<Int, Int, WeightProxy>.remove(key: Int)
: removes the entry associated withkey
, if presentfun DslMap<Int, Int, WeightProxy>.putAll(map: Map<Int, Int>)
: adds all entries from the specified map to this map field, overwriting prior values for already present keysfun DslMap<Int, Int, WeightProxy>.clear()
: clears all entries from this map field
Extensions (proto2 only)
Given a message with an extension range:
message Foo {
extensions 100 to 199;
}
The protocol buffer compiler will add the following methods to FooKt.Dsl
:
operator fun <T> get(extension: ExtensionLite<Foo, T>): T
: gets the current value of the extension field in the DSLoperator fun <T> get(extension: ExtensionLite<Foo, List<T>>): ExtensionList<T, Foo>
: gets the current value of the repeated extension field in the DSL as a read-onlyList
operator fun <T : Comparable<T>> set(extension: ExtensionLite<Foo, T>)
: sets the current value of the extension field in the DSL (forComparable
field types)operator fun <T : MessageLite> set(extension: ExtensionLite<Foo, T>)
: sets the current value of the extension field in the DSL (for message field types)operator fun set(extension: ExtensionLite<Foo, ByteString>)
: sets the current value of the extension field in the DSL (forbytes
fields)operator fun contains(extension: ExtensionLite<Foo, *>): Boolean
: returns true if the extension field has a valuefun clear(extension: ExtensionLite<Foo, *>)
: clears the extension fieldfun <E> ExtensionList<Foo, E>.add(value: E)
: adds a value to the repeated extension fieldoperator fun <E> ExtensionList<Foo, E>.plusAssign(value: E)
: alias foradd
using operator syntaxoperator fun <E> ExtensionList<Foo, E>.addAll(values: Iterable<E>)
: adds multiple values to the repeated extension fieldoperator fun <E> ExtensionList<Foo, E>.plusAssign(values: Iterable<E>)
: alias foraddAll
using operator syntaxoperator fun <E> ExtensionList<Foo, E>.set(index: Int, value: E)
: sets the element of the repeated extension field at the specified indexinline fun ExtensionList<Foo, *>.clear()
: clears the elements of the repeated extension field
The generics here are complex, but the effect is that this[extension] = value
works for every extension type except repeated extensions, and repeated
extensions have "natural" list syntax that works similarly to
non-extension repeated fields.
Given an extension definition:
extend Foo {
optional int32 bar = 123;
}
Java generates the “extension identifier” bar
, which is used to “key”
extension operations above.