{"id":24116989,"url":"https://github.com/avro-kotlin/avro4k","last_synced_at":"2026-01-23T00:49:39.600Z","repository":{"id":37250609,"uuid":"194962508","full_name":"avro-kotlin/avro4k","owner":"avro-kotlin","description":"Avro format support for Kotlin","archived":false,"fork":false,"pushed_at":"2025-05-06T00:42:21.000Z","size":1666,"stargazers_count":210,"open_issues_count":26,"forks_count":39,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-05-13T06:49:37.878Z","etag":null,"topics":["avro","kotlin","schema","serialization"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/avro-kotlin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-07-03T02:05:59.000Z","updated_at":"2025-04-09T21:55:30.000Z","dependencies_parsed_at":"2025-04-12T02:00:16.879Z","dependency_job_id":"f0a02141-7334-45ac-af82-f44ebd719b5f","html_url":"https://github.com/avro-kotlin/avro4k","commit_stats":null,"previous_names":["sksamuel/avro4k"],"tags_count":32,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avro-kotlin%2Favro4k","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avro-kotlin%2Favro4k/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avro-kotlin%2Favro4k/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avro-kotlin%2Favro4k/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/avro-kotlin","download_url":"https://codeload.github.com/avro-kotlin/avro4k/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254471061,"owners_count":22076585,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["avro","kotlin","schema","serialization"],"created_at":"2025-01-11T07:23:14.295Z","updated_at":"2026-01-23T00:49:39.592Z","avatar_url":"https://github.com/avro-kotlin.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"![build-main](https://github.com/avro-kotlin/avro4k/workflows/build-main/badge.svg)\n[![Download](https://img.shields.io/maven-central/v/com.github.avro-kotlin.avro4k/avro4k-core)](https://search.maven.org/artifact/com.github.avro-kotlin.avro4k/avro4k-core)\n[![Kotlin](https://img.shields.io/badge/kotlin-2.2-blue.svg?logo=kotlin)](http://kotlinlang.org)\n[![Avro spec](https://img.shields.io/badge/avro%20spec-1.12.1-blue.svg?logo=apache)](https://avro.apache.org/docs/1.12.1/specification/)\n\n# Introduction\n\n**Avro4k** (or Avro for Kotlin) is a library that brings [Avro](https://avro.apache.org/) serialization format in kotlin, based on the **reflection-less** kotlin library\ncalled [kotlinx-serialization](https://github.com/Kotlin/kotlinx.serialization).\n\nHere are the main features:\n\n- **Full avro support**, including logical types, unions, recursive types, and schema evolution :white_check_mark:\n- **Encode and decode** anything to and from binary format, and also in generic data :toolbox:\n- **Generate schemas** based on your values and data classes :pencil:\n- **Customize** the generated schemas and encoded data with annotations :construction_worker:\n- **Fast** as it is reflection-less :rocket: (check the benchmarks [here](benchmark/README.md#results))\n- **Simple API** to get started quickly, also with native support of java standard classes like `UUID`, `BigDecimal`, `BigInteger` and `java.time` module :1st_place_medal:\n- **Relaxed matching** for easy schema evolution as it natively [adapts compatible types](#types-matrix) :cyclone:\n- **Kafka confluent's schema registry ready** thanks to the [confluent-kafka-serializer module](confluent-kafka-serializer/README.md), allowing to use avro4k in any kafka or spring cloud project :white_check_mark:\n- **Official gradle plugin** to generate (not only) data classes from avro schemas :wrench: (check the [gradle-plugin](gradle-plugin/README.md) documentation for more details)\n\n\u003e [!WARNING]\n\u003e **Important**: As of today, avro4k is **only available for JVM platform**, and theoretically for android platform (as apache avro library is already **android-ready**). \u003cbr/\u003eIf\n\u003e you would like to have js/wasm/native compatible platforms, please put a :thumbsup: on [this issue](https://github.com/avro-kotlin/avro4k/issues/207)\n\n# Quick start\n\n## Basic \"pure\" avro encoding\n\nIn this example, we will see how to encode and decode Avro objects in their \"pure\" format, which means that the schema is not prefixed to the data.\n\n\u003cdetails open\u003e\n\u003csummary\u003eExample:\u003c/summary\u003e\n\n```kotlin\npackage myapp\n\nimport com.github.avrokotlin.avro4k.*\nimport kotlinx.serialization.*\n\n@Serializable\ndata class Project(val name: String, val language: String)\n\nfun main() {\n    // Generating schemas\n    val schema = Avro.schema\u003cProject\u003e()\n    println(schema.toString()) // {\"type\":\"record\",\"name\":\"Project\",\"namespace\":\"myapp\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"language\",\"type\":\"string\"}]}\n\n    // Serializing objects\n    val data = Project(\"kotlinx.serialization\", \"Kotlin\")\n    val bytes = Avro.encodeToByteArray(data)\n\n    // Deserializing objects\n    val obj = Avro.decodeFromByteArray\u003cProject\u003e(bytes)\n    println(obj) // Project(name=kotlinx.serialization, language=Kotlin)\n}\n```\n\n\u003c/details\u003e\n\n### JVM stream encoding\n\nAvro4k is also able to encode and decode objects to and from any kotlinx-io `Sink` and `Source`, which means any supported bridge to kotlinx-io is supported by avro4k.\n\nAs an example, for JVM streams, you can use `InputStream.asSource().buffered()` and `OutputStream.asSink().buffered()`.\n\n\u003e [!NOTE]\n\u003e Note the required `buffered()` call to allow avro4k accessing the `read` and `write` methods.\n\n\u003e [!WARNING]\n\u003e Do not use `ByteArrayInputStream` and `ByteArrayOutputStream`, prefer using `kotlinx.io.Buffer` instead as it is more efficient and allows better performance.\n\n\u003cdetails\u003e\n\u003csummary\u003eExample:\u003c/summary\u003e\n\n```kotlin\npackage myapp\n\nimport com.github.avrokotlin.avro4k.*\nimport kotlinx.serialization.*\nimport java.io.*\nimport kotlinx.io.*\n\n@Serializable\ndata class Project(val name: String, val language: String)\n\nfun main() {\n    // Serializing objects\n    val data = Project(\"kotlinx.serialization\", \"Kotlin\")\n    val outputStream: OutputStream = TODO(\"Your output stream here, e.g. FileOutputStream, etc.\")\n    val sink = output.asSink().buffered()\n    Avro.encodeToSink(data, sink)\n    sink.close() // Will flush the data to the output stream at the same time\n\n    // Deserializing objects\n    val inputStream: InputStream = TODO(\"Your input stream here, e.g. FileInputStream, etc.\")\n    val source = inputStream.asSource().buffered()\n    // Be sure to read all the content from the soure, or you may lose data from the upper stream, as buffering means that the source may read more bytes to fill the buffer.\n    val obj = Avro.decodeFromSource\u003cProject\u003e(source)\n    println(obj) // Project(name=kotlinx.serialization, language=Kotlin)\n}\n```\n\n\u003c/details\u003e\n\n### Encode many objects in the same stream\n\nYou can use kotlinx-io's `Buffer` to encode many objects in the same stream, and then extract the whole bytes from the buffer.\n\n\u003e [!WARNING]\n\u003e As kotlinx-io is using an internal buffer pool, each `Buffer` instance you create must be fully consumed to allow the internal buffer segments to be returned to the pool and reused.\n\u003e More detailsin the [kotlinx-io documentation](https://github.com/Kotlin/kotlinx-io).\n\n\u003cdetails\u003e\n\u003csummary\u003eExample:\u003c/summary\u003e\n\n```kotlin\npackage myapp\n\nimport com.github.avrokotlin.avro4k.*\nimport kotlinx.serialization.*\nimport kotlinx.io.*\n\n@Serializable\ndata class Project(val name: String, val language: String)\n\nfun main() {\n    // Serializing objects\n    val buffer = Buffer()\n    Avro.encodeToSink(data1, buffer)\n    Avro.encodeToSink(data2, buffer)\n    Avro.encodeToSink(data3, buffer)\n    // You need to consume the whole buffer by passing the buffer to the wanted framework or read the byte-array, or you may have kotlinx-io internal buffer leaks.\n    val bytes = buffer.readByteArray()\n\n    // Deserializing objects\n    val source: Source = TODO(\"a Buffer, or any other source from kotlinx-io\")\n    val data1 = Avro.decodeFromSource\u003cProject\u003e(source)\n    val data2 = Avro.decodeFromSource\u003cProject\u003e(source)\n    val data3 = Avro.decodeFromSource\u003cProject\u003e(source)\n}\n```\n\n\u003c/details\u003e\n\n### Custom encoding with a Buffer\n\nThe following example shows how to encode in a custom format using a `Buffer` to write the data. The example is going to encode like this: `\u003ctimestamp\u003e\u003cschema\u003e\u003cbinary-length\u003e\u003cbinary-data\u003e`.\n\nYou will still encode the data in pure avro binary format, while you will be able to add some metadata before the data (or after), compress the data, and much more.\n\n\u003cdetails\u003e\n\u003csummary\u003eExample:\u003c/summary\u003e\n\n```kotlin\npackage myapp\n\nimport com.github.avrokotlin.avro4k.*\nimport kotlinx.serialization.*\nimport kotlinx.io.*\n\n@Serializable\ndata class Project(val name: String, val language: String)\n\nfun main() {\n    // Serializing objects\n    val buffer = Buffer()\n    buffer.writeInt(theTimestamp)\n    buffer.writeString(theSchema)\n    \n    val dataBuffer = Buffer()\n    Avro.encodeToSink(data, dataBuffer)\n    // You need to consume the whole buffer by passing the buffer to the wanted framework or read the byte-array, or you may have kotlinx-io internal buffer leaks.\n    val bytes = buffer.readByteArray()\n    buffer.writeInt()\n\n    // Deserializing objects\n    val source: Source = TODO(\"a Buffer, or any other source from kotlinx-io\")\n    val data1 = Avro.decodeFromSource\u003cProject\u003e(source)\n    val data2 = Avro.decodeFromSource\u003cProject\u003e(source)\n    val data3 = Avro.decodeFromSource\u003cProject\u003e(source)\n}\n```\n\n\u003c/details\u003e\n\n## Single object\n\nAvro4k provides a way to encode and decode single objects with `AvroSingleObject` class. This encoding will prefix the binary data with the schema fingerprint to\nallow knowing the writer schema when reading the data. The downside is that you need to provide a schema registry to get the schema from the fingerprint.\nThis format is perfect for payloads sent through message brokers like kafka or rabbitmq as it is the most compact schema-aware format.\n\n\u003cdetails\u003e\n\u003csummary\u003eExample:\u003c/summary\u003e\n\n```kotlin\npackage myapp\n\nimport com.github.avrokotlin.avro4k.*\nimport kotlinx.serialization.*\nimport org.apache.avro.SchemaNormalization\n\n@Serializable\ndata class Project(val name: String, val language: String)\n\nfun main() {\n    val schema = Avro.schema\u003cProject\u003e()\n    val schemasByFingerprint = mapOf(SchemaNormalization.parsingFingerprint64(schema) to schema)\n    val singleObjectInstance = AvroSingleObject(schemasByFingerprint::get)\n\n    // Serializing objects\n    val data = Project(\"kotlinx.serialization\", \"Kotlin\")\n    val bytes = singleObjectInstance.encodeToByteArray(data)\n\n    // Deserializing objects\n    val obj = singleObjectInstance.decodeFromByteArray\u003cProject\u003e(bytes)\n    println(obj) // Project(name=kotlinx.serialization, language=Kotlin)\n}\n```\n\n\u003c/details\u003e\n\n\u003e For more details, check in the avro spec the [single object encoding](https://avro.apache.org/docs/1.12.1/specification/#single-object-encoding).\n\n## Object container\n\nAvro4k provides a way to encode and decode object container — also known as data file — with `AvroObjectContainer` class. This encoding will prefix the binary data with the\nfull schema to allow knowing the writer schema when reading the data. This format is perfect for storing many long-term objects in a single file.\n\nBe aware that consuming the decoded `Sequence` needs to be done **before** closing the stream, or you will get an exception as a sequence is a \"hot\" source,\nwhich means that if there is millions of objects in the file, all the objects are extracted one-by-one when requested. If you take only the first 10 objects and close the stream,\nthe remaining objects won't be extracted. Use carefully `sequence.toList()` as it could lead to OutOfMemoryError as extracting millions of objects may not fit in memory.\n\n\u003cdetails\u003e\n\u003csummary\u003eExample:\u003c/summary\u003e\n\n```kotlin\npackage myapp\n\nimport com.github.avrokotlin.avro4k.*\nimport kotlinx.serialization.*\n\n@Serializable\ndata class Project(val name: String, val language: String)\n\nfun main() {\n    // Serializing objects\n    val valuesToEncode = sequenceOf(\n        Project(\"kotlinx.serialization\", \"Kotlin\"),\n        Project(\"java.lang\", \"Java\"),\n        Project(\"avro4k\", \"Kotlin\"),\n    )\n    Files.newOutputStream(Path(\"your-file.bin\")).use { fileStream -\u003e\n        AvroObjectContainer.openWriter(fileStream).use { writer -\u003e\n            valuesToEncode.forEach { writer.write(it) }\n        }\n    }\n\n    // Deserializing objects\n    Files.newInputStream(Path(\"your-file.bin\")).use { fileStream -\u003e\n        AvroObjectContainer.decodeFromStream\u003cProject\u003e(fileStream).forEach {\n            println(it) // Project(name=kotlinx.serialization, language=Kotlin) ...\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n\u003e For more details, see the Avro spec on [object container files](https://avro.apache.org/docs/1.12.1/specification/#object-container-files).\n\n# Important notes\n\n- **Avro4k** is highly based on apache avro library, that implies all the schema validation is done by it\n- All members annotated with `@ExperimentalAvro4kApi` are **subject to changes** in future releases without any notice as they are experimental, so please\n  check the release notes to check the needed migration. At least, given a version `A.B.C`, only the minor `B` number will be incremented, not the major `A`.\n- **Avro4k** also supports encoding and decoding generic data, mainly because of confluent schema registry compatibility as their serializers only handle generic data. When avro4k\n  will support their schema registry, the generic encoding will be removed to keep this library as simple as possible.\n\n# Setup\n\n\u003cdetails open\u003e\n  \u003csummary\u003e\u003cb\u003eGradle Kotlin DSL\u003c/b\u003e\u003c/summary\u003e\n\n```kotlin\nplugins {\n    kotlin(\"jvm\") version kotlinVersion\n    kotlin(\"plugin.serialization\") version kotlinVersion\n}\n\ndependencies {\n    implementation(\"com.github.avro-kotlin.avro4k:avro4k-core:$avro4kVersion\")\n}\n```\n\n\u003c/details\u003e\n\n\u003cbr\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cb\u003eGradle Groovy DSL\u003c/b\u003e\u003c/summary\u003e\n\n```groovy\nplugins {\n    id 'org.jetbrains.kotlin.multiplatform' version kotlinVersion\n    id 'org.jetbrains.kotlin.plugin.serialization' version kotlinVersion\n}\n\ndependencies {\n    implementation \"com.github.avro-kotlin.avro4k:avro4k-core:$avro4kVersion\"\n}\n```\n\n\u003c/details\u003e\n\n\u003cbr\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cb\u003eMaven\u003c/b\u003e\u003c/summary\u003e\n\nAdd serialization plugin to Kotlin compiler plugin:\n\n```xml\n\n\u003cbuild\u003e\n    \u003cplugins\u003e\n        \u003cplugin\u003e\n            \u003cgroupId\u003eorg.jetbrains.kotlin\u003c/groupId\u003e\n            \u003cartifactId\u003ekotlin-maven-plugin\u003c/artifactId\u003e\n            \u003cversion\u003e${kotlin.version}\u003c/version\u003e\n            \u003cexecutions\u003e\n                \u003cexecution\u003e\n                    \u003cid\u003ecompile\u003c/id\u003e\n                    \u003cphase\u003ecompile\u003c/phase\u003e\n                    \u003cgoals\u003e\n                        \u003cgoal\u003ecompile\u003c/goal\u003e\n                    \u003c/goals\u003e\n                \u003c/execution\u003e\n            \u003c/executions\u003e\n            \u003cconfiguration\u003e\n                \u003ccompilerPlugins\u003e\n                    \u003cplugin\u003ekotlinx-serialization\u003c/plugin\u003e\n                \u003c/compilerPlugins\u003e\n            \u003c/configuration\u003e\n            \u003cdependencies\u003e\n                \u003cdependency\u003e\n                    \u003cgroupId\u003eorg.jetbrains.kotlin\u003c/groupId\u003e\n                    \u003cartifactId\u003ekotlin-maven-serialization\u003c/artifactId\u003e\n                    \u003cversion\u003e${kotlin.version}\u003c/version\u003e\n                \u003c/dependency\u003e\n            \u003c/dependencies\u003e\n        \u003c/plugin\u003e\n    \u003c/plugins\u003e\n\u003c/build\u003e\n```\n\nAdd the avro4k dependency:\n\n```xml\n\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.avro-kotlin.avro4k\u003c/groupId\u003e\n    \u003cartifactId\u003eavro4k-core\u003c/artifactId\u003e\n    \u003cversion\u003e${avro4k.version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n\u003c/details\u003e\n\n## Versions matrix\n\n| Avro4k     | Kotlin   | Kotlin API/language | Kotlin serialization |\n|------------|----------|---------------------|----------------------|\n| `\u003e= 2.0.0` | `\u003e= 2.0` | `\u003e= 1.9`            | `\u003e= 1.7.0`           |\n| `\u003c 2.0.0`  | `\u003e= 1.6` | `\u003e= 1.6`            | `\u003e= 1.3`             |\n\n\u003e [!WARNING]\n\u003e Starting from avro4k v2, you need to ensure that `kotlinx-serialization-core` dependency has the version at least to `1.7.0`, or you will get `NoSuchMethodError: SerialDescriptorsKt.getNonNullOriginal` by checking it using `./gradlew dependencies`. If the version is not the good one, please explicit the dependency with the version. It is generally due to dependency management plugins like spring, or other transitive constraints.\n\u003e You can enhance your search using `./gradlew dependencies --scan` to have a web ui to search and understand the origin of the issue.\n\n# How to generate schemas\n\nWriting schemas manually or using the Java based `SchemaBuilder` can be tedious.\n`kotlinx-serialization` simplifies this generating for us the corresponding descriptors to allow generating avro schemas easily, without any reflection.\nAlso, it provides native compatibility with data classes (including open and sealed classes), inline classes, any collection, array, enums, and primitive values.\n\n\u003e [!NOTE]\n\u003e For more information about the avro schema, please refer to the [avro specification](https://avro.apache.org/docs/1.12.1/specification/)\n\nTo allow generating a schema for a specific class, you need to annotate it with `@Serializable`:\n\n```kotlin\n@Serializable\ndata class Ingredient(val name: String, val sugar: Double)\n\n@Serializable\ndata class Pizza(val name: String, val ingredients: List\u003cIngredient\u003e, val topping: Ingredient?, val vegetarian: Boolean)\n```\n\nThen you can generate the schema using the `Avro.schema` function:\n\n```kotlin\nval schema = Avro.schema\u003cPizza\u003e()\nprintln(schema.toString(true))\n```\n\nThe generated schema will look as follows:\n\n```json\n{\n    \"type\": \"record\",\n    \"name\": \"Pizza\",\n    \"namespace\": \"com.github.avrokotlin.avro4k.example\",\n    \"fields\": [\n        {\n            \"name\": \"name\",\n            \"type\": \"string\"\n        },\n        {\n            \"name\": \"ingredients\",\n            \"type\": {\n                \"type\": \"array\",\n                \"items\": {\n                    \"type\": \"record\",\n                    \"name\": \"Ingredient\",\n                    \"fields\": [\n                        {\n                            \"name\": \"name\",\n                            \"type\": \"string\"\n                        },\n                        {\n                            \"name\": \"sugar\",\n                            \"type\": \"double\"\n                        }\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"topping\",\n            \"type\": [\n                \"null\",\n                {\n                    \"type\": \"record\",\n                    \"name\": \"Ingredient\"\n                }\n            ],\n            \"default\": null\n        },\n        {\n            \"name\": \"vegetarian\",\n            \"type\": \"boolean\"\n        }\n    ]\n}\n```\n\nIf you need to configure your `Avro` instance, you need to create your own instance of `Avro` with the wanted configuration, and then use it to generate the schema:\n\n```kotlin\nval yourAvroInstance = Avro {\n    // your configuration\n}\nyourAvroInstance.schema\u003cPizza\u003e()\n```\n\n# Usage\n\n## Customizing the configuration\n\nBy default, `Avro` is configured with the following behavior:\n- `implicitNulls`: The nullable fields are considered null when decoding if the writer record's schema does not contain this field.\n- `implicitEmptyCollections`: The non-nullable map and collection fields are considered empty when decoding if the writer record's schema does not contain this field.\n  - If `implicitNulls` is true, it takes precedence so the empty collections are set as null if the value is missing instead of an empty collection.\n- `validateSerialization`: There is no validation of the schema when encoding or decoding data, which means that serializing using a custom serializer could lead to unexpected behavior. Be careful with your custom serializers. More details [in this section](#set-a-custom-schema).\n- `fieldNamingStrategy`: The record's field naming strategy is using the original kotlin field name. To change it, [check this section](#changing-records-field-name).\n- `logicalTypes`: Indicates how a logical type should be deserialized when decoding generically to `Any`. Check this section for more details: [generic decoding](#generic-decoding-for-decoding-a-type-that-is-not-known-at-compile-time).\n\nSo each time you call a method on the `Avro` object implicitely invoke the default configuration. Example:\n\n```kotlin\nAvro.encodeToByteArray(MyData(\"value\"))\nAvro.decodeFromByteArray(bytes)\nAvro.schema\u003cMyData\u003e()\n```\n\nIf you need to change the default behavior, you need to create your own instance of `Avro` with the wanted configuration:\n\n```kotlin\nval yourAvroInstance = Avro {\n    fieldNamingStrategy = FieldNamingStrategy.Builtins.SnakeCase\n    implicitNulls = false\n    implicitEmptyCollections = false\n    validateSerialization = true\n    setLogicalTypeSerializer(\"your-logical-type\", YourSerializer())\n}\nyourAvroInstance.encodeToByteArray(MyData(\"value\"))\nyourAvroInstance.decodeFromByteArray(bytes)\nyourAvroInstance.schema\u003cMyData\u003e()\n```\n\n## Types matrix\n\n| Kotlin type                  | Generated schema type                       | Other compatible writer types                                | Compatible logical type  | Note / Serializer class                                                                                                                             |\n|------------------------------|---------------------------------------------|--------------------------------------------------------------|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|\n| `Boolean`                    | `boolean`                                   | `string`                                                     |                          |                                                                                                                                                     |\n| `Byte`, `Short`, `Int`       | `int`                                       | `long`, `float`, `double`, `string`                          |                          |                                                                                                                                                     |\n| `Long`                       | `long`                                      | `int`, `float`, `double`, `string`                           |                          |                                                                                                                                                     |\n| `Float`                      | `float`                                     | `double`, `string`                                           |                          |                                                                                                                                                     |\n| `Double`                     | `double`                                    | `float`, `string`                                            |                          |                                                                                                                                                     |\n| `Char`                       | `int`                                       | `string` (exactly 1 char required)                           | `char`                   | The value serialized is the char code. When reading from a `string`, requires exactly 1 char                                                        |\n| `String`                     | `string`                                    | `bytes` (UTF8), `fixed` (UTF8)                               |                          |                                                                                                                                                     |\n| `ByteArray`                  | `bytes`                                     | `string` (UTF8), `fixed` (UTF8)                              |                          |                                                                                                                                                     |\n| `Map\u003c*, *\u003e`                  | `map`                                       |                                                              |                          | The map key must be string-able. Mainly everything is string-able except null and composite types (collection, data classes)                        |\n| `Collection\u003c*\u003e`              | `array`                                     |                                                              |                          |                                                                                                                                                     |\n| `data class`                 | `record`                                    |                                                              |                          |                                                                                                                                                     |\n| `enum class`                 | `enum`                                      | `string`                                                     |                          |                                                                                                                                                     |\n| `@AvroFixed`-compatible      | `fixed`                                     | `bytes`, `string`                                            |                          | Throws an error at runtime if the writer type is not present in the column \"other compatible writer types\"                                          |\n| `@AvroStringable`-compatible | `string`                                    | `int`, `long`, `float`, `double`, `string`, `fixed`, `bytes` |                          | Ignored when the writer type is not present in the column \"other compatible writer types\"                                                           |\n| `java.math.BigDecimal`       | `fixed` with `@AvroDecimal`                 | `int`, `long`, `float`, `double`, `string`, `fixed`, `bytes` | `decimal`                | To use it, annotate the field with `@AvroDecimal` to give the `scale` and the `precision`                                                           |\n| `java.math.BigDecimal`       | `bytes`                                     | `int`, `long`, `float`, `double`, `string`, `fixed`, `bytes` | `decimal`, `big-decimal` | To generate `decimal`, annotate the field with `@AvroDecimal`. To generate `big-decimal`, do not annotate with `@AvroDecimal`                       |\n| `java.math.BigDecimal`       | `string`                                    | `int`, `long`, `float`, `double`, `fixed`, `bytes`           |                          | To use it, annotate the field with `@AvroStringable`. `@AvroDecimal` is ignored in that case                                                        |\n| `java.util.UUID`             | `string`, `fixed(16)` with `@AvroFixed(16)` |                                                              | `uuid`                   | To use it, just annotate the field with `@Contextual`                                                                                               |\n| `java.net.URL`               | `string`                                    |                                                              |                          | To use it, just annotate the field with `@Contextual`                                                                                               |\n| `java.math.BigInteger`       | `string`                                    | `int`, `long`, `float`, `double`                             |                          | To use it, just annotate the field with `@Contextual`                                                                                               |\n| `java.time.LocalDate`        | `int`                                       | `long`, `string` (ISO8601)                                   | `date`                   | To use it, just annotate the field with `@Contextual`                                                                                               |\n| `java.time.Instant`          | `long`                                      | `string` (ISO8601)                                           | `timestamp-millis`       | To use it, just annotate the field with `@Contextual`                                                                                               |\n| `java.time.Instant`          | `long`                                      | `string` (ISO8601)                                           | `timestamp-micros`       | To use it, [register the serializer](#support-additional-non-serializable-types) `com.github.avrokotlin.avro4k.serializer.InstantToMicroSerializer` |\n| `java.time.LocalDateTime`    | `long`                                      | `string` (ISO8601)                                           | `timestamp-millis`       | To use it, just annotate the field with `@Contextual`                                                                                               |\n| `java.time.LocalTime`        | `int`                                       | `long`, `string` (ISO8601)                                   | `time-millis`            | To use it, just annotate the field with `@Contextual`                                                                                               |\n| `java.time.Duration`         | `fixed(12)`                                 | `string` (ISO8601)                                           | `duration`               | To use it, just annotate the field with `@Contextual`                                                                                               |\n| `java.time.Period`           | `fixed(12)`                                 | `string` (ISO8601)                                           | `duration`               | To use it, just annotate the field with `@Contextual`                                                                                               |\n| `kotlin.time.Duration`       | `fixed(12)`                                 | `string` (ISO8601)                                           | `duration`               |                                                                                                                                                     |\n| `kotlin.uuid.Uuid`           | `string`, `fixed(16)` with `@AvroFixed(16)` |                                                              | `uuid`                   | Native support, no annotation required                                                                                                              |\n\n\u003e [!NOTE]\n\u003e For more details, check the [built-in classes in kotlinx-serialization](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/builtin-classes.md)\n\n## Add documentation to a schema\n\nYou may want to add documentation to a schema to provide more information about a field or a named type (only RECORD and ENUM for the moment).\n\n\u003e [!WARNING]\n\u003e Do not use `@org.apache.avro.reflect.AvroDoc` as this annotation is not visible by Avro4k.\n\n```kotlin\nimport com.github.avrokotlin.avro4k.AvroDoc\n\n@Serializable\n@AvroDoc(\"This is a record documentation\")\ndata class MyData(\n    @AvroDoc(\"This is a field documentation\")\n    val myField: String\n)\n\n@Serializable\n@AvroDoc(\"This is an enum documentation\")\nenum class MyEnum {\n    A,\n    B\n}\n```\n\n\u003e [!NOTE]\n\u003e This impacts only the schema generation.\n\n## Generic decoding (for decoding a type that is not known at compile time)\nWhen decoding, you may want to specify the type `Any` when you don't know at compile time the type of the data you are decoding.\n\nTo do so, just provide the type `Any` to the `decodeFromByteArray` (and the other `decodeFromX`) methods.\n\n\u003e [!WARNING]\n\u003e You need to provide the schema, or an error will be thrown as Avro4k is not able to infer the schema.\n\n```kotlin\nAvro.decodeFromByteArray\u003cAny\u003e(writerSchema, bytes)\nAvro.encodeToByteArray\u003cAny\u003e(writerSchema, data)\n```\n\nYou can also decode `Any` type *inside* a data class, or any other type (map, list, inline type, etc.) by annotating the field:\n\n```kotlin\n@Serializable\ndata class MyData(\n    // implicit serializer resolving to AnySerializer\n    @Contextual val myGenericField: Any,\n    // or explicitly\n    @Serializable(with = AnySerializer::class) val myAnotherGenericField: Any,\n)\nAvro.decodeFromByteArray\u003cMyData\u003e(writerSchema, bytes)\n\n// or with value classes\n@JvmInline\n@Serializable\nvalue class MyValue(\n    @Contextual val myData: Any\n)\nAvro.decodeFromByteArray\u003cMyData\u003e(writerSchema, bytes)\n\n// or with any other generic type\nAvro.decodeFromByteArray\u003cList\u003cAny\u003e\u003e(writerSchema, bytes)\nAvro.decodeFromByteArray\u003cMap\u003cString, Any\u003e\u003e(writerSchema, bytes)\nAvro.decodeFromByteArray\u003cYourType\u003cAny\u003e\u003e(writerSchema, bytes)\n```\n\n## Support additional non-serializable types\n\nWhen looking at the [types matrix](#types-matrix), you can see some of them natively supported by Avro4k, but some others are not.\nAlso, your own types may not be serializable.\n\nTo fix it, you need to create a custom **serializer** that will handle the serialization and deserialization of the value, and provide a descriptor.\n\n\u003e [!NOTE]\n\u003e This impacts the serialization and the deserialization. It can also impact the schema generation if the serializer is providing a custom logical type or a custom schema through\n\u003e the descriptor.\n\n### Write your own serializer\n\nTo create a custom serializer, you need to implement the `AvroSerializer` abstract class and override the `serializeAvro` and `deserializeAvro` methods.\nYou also need to override `getSchema` to provide the schema of your custom type as a custom serializer means non-standard encoding and decoding.\n\n\u003cdetails open\u003e\n\u003csummary\u003eCreate a serializer that needs Avro features like getting the schema or encoding bytes and fixed types\u003c/summary\u003e\n\n```kotlin\nobject YourTypeSerializer : AvroSerializer\u003cYourType\u003e(YourType::class.qualifiedName!!) {\n    override fun getSchema(context: SchemaSupplierContext): Schema {\n        // you can access the data class element, inlined elements from value classes, and their annotations\n        // you can also access the avro configuration in the context\n        return ... /* create the corresponding schema using SchemaBuilder or Schema.create */\n    }\n\n    override fun serializeAvro(encoder: AvroEncoder, value: YourType) {\n        encoder.currentWriterSchema // you can access the current writer schema\n        encoder.encodeString(value.toString())\n    }\n\n    override fun deserializeAvro(decoder: AvroDecoder): YourType {\n        decoder.currentWriterSchema // you can access the current writer schema\n        return YourType.fromString(decoder.decodeString())\n    }\n\n    override fun serializeGeneric(encoder: Encoder, value: YourType) {\n        // you may want to implement this function if you also want to use the serializer outside of Avro4k\n        encoder.encodeString(value.toString())\n    }\n\n    override fun deserializeGeneric(decoder: Decoder): YourType {\n        // you may want to implement this function if you also want to use the serializer outside of Avro4k\n        return YourType.fromString(decoder.decodeString())\n    }\n}\n```\n\n\u003c/details\u003e\n\nYou may want to just implement a `KSerializer` if you don't need specific Avro features, but you won't be able to associate a custom schema to it:\n\n\u003cdetails\u003e\n\u003csummary\u003eCreate a generic serializer that doesn't need specific Avro features\u003c/summary\u003e\n\n```kotlin\nobject YourTypeSerializer : KSerializer\u003cYourType\u003e {\n    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(\"YourType\", PrimitiveKind.STRING)\n\n    override fun serialize(encoder: Encoder, value: YourType) {\n        encoder.encodeString(value.toString())\n    }\n\n    override fun deserialize(decoder: Decoder): YourType {\n        return YourType.fromString(decoder.decodeString())\n    }\n}\n```\n\n\u003c/details\u003e\n\n### Register the serializer globally (not compile time)\n\nYou first need to configure your `Avro` instance with the wanted serializer instance:\n\n```kotlin\nimport kotlinx.serialization.modules.SerializersModule\nimport kotlinx.serialization.modules.contextual\n\nval myCustomizedAvroInstance = Avro {\n    serializersModule = SerializersModule {\n        // give the object serializer instance\n        contextual(YourTypeSerializerObject)\n        // or instanciate it if it's a class and not an object\n        contextual(YourTypeSerializerClass())\n    }\n}\n```\n\nThen just annotated the field with `@Contextual`:\n\n```kotlin\n@Serializable\ndata class MyData(\n    @Contextual val myField: YourType\n)\n```\n\n### Register the serializer just for a field at compile time\n\n```kotlin\n@Serializable\ndata class MyData(\n    @Serializable(with = YourTypeSerializer::class) val myField: YourType\n)\n```\n\n## Changing record's field name\n\nBy default, field names are the original name of the kotlin fields in the data classes.\n\n\u003e [!NOTE]\n\u003e This impacts the schema generation, the serialization and the deserialization of the field.\n\n### Individual field name change\n\nTo change a field name, annotate it with `@SerialName`:\n\n```kotlin\n@Serializable\ndata class MyData(\n    @SerialName(\"custom_field_name\") val myField: String\n)\n```\n\n\u003e [!NOTE]\n\u003e `@SerialName` will still be handled by the naming strategy\n\n### Field naming strategy (overall change)\n\nTo apply a naming strategy to all fields, you need to set the `fieldNamingStrategy` in the `Avro` configuration.\n\n\u003e [!NOTE]\n\u003e This is only applicable for RECORD fields, and not for ENUM symbols.\n\nThere is 3 built-ins strategies:\n\n- `NoOp` (default): keeps the original kotlin field name\n- `SnakeCase`: converts the original kotlin field name to snake_case with underscores before each uppercase letter\n- `PascalCase`: upper-case the first letter of the original kotlin field name\n- If you need more, please [file an issue](https://github.com/avro-kotlin/avro4k/issues/new/choose)\n\nFirst, create your own instance of `Avro` with the wanted naming strategy:\n\n```kotlin\n\nval myCustomizedAvroInstance = Avro {\n    fieldNamingStrategy = FieldNamingStrategy.Builtins.SnakeCase\n}\n\n```\n\nThen, use this instance to generate the schema or encode/decode data:\n\n```kotlin\npackage my.package\n\n@Serializable\ndata class MyData(val myField: String)\n\nval schema = myCustomizedAvroInstance.schema\u003cMyData\u003e() // {...,\"fields\":[{\"name\":\"my_field\",...}]}\n```\n\n## Set a default field value\n\nWhile reading avro binary data, you can miss a field (a kotlin field is present but not in the avro binary data), so Avro4k fails as it is not capable of constructing the kotlin\ntype without the missing field value.\n\nBy default ([check this section](#customizing-the-configuration) to opt out from this default behavior):\n- nullable fields are optional and `default: null` is automatically added to the field definition.\n- array fields are optional and `default: []` is automatically added to the field definition.\n- map fields are optional and `default: {}` is automatically added to the field definition.\n\n### @AvroDefault\n\nTo avoid this error, you can set a default value for a field by annotating it with `@AvroDefault`:\n\n```kotlin\nimport com.github.avrokotlin.avro4k.AvroDefault\n\n@Serializable\ndata class MyData(\n    @AvroDefault(\"default value\") val stringField: String,\n    @AvroDefault(\"42\") val intField: Int?,\n    @AvroDefault(\"\"\"{\"stringField\":\"custom value\"}\"\"\") val nestedType: MyData? = null\n)\n```\n\n\u003e [!NOTE]\n\u003e This impacts only the schema generation and the deserialization of the field, and not the serialization.\n\n\u003e [!WARNING]\n\u003e Do not use `@org.apache.avro.reflect.AvroDefault` as this annotation is not visible by Avro4k.\n\n### kotlin default value\n\nYou can also set a kotlin default value, but this default won't be present into the generated schema as Avro4k is not able to retrieve it:\n\n```kotlin\n@Serializable\ndata class MyData(\n    val stringField: String = \"default value\",\n    val intField: Int? = 42,\n)\n```\n\n\u003e This impacts only the deserialization of the field, and not the serialization or the schema generation.\n\n## Add aliases\n\nTo be able of reading from different written schemas, or able of writing to different schemas, you can add aliases to a named type (record, enum) field by annotating it\nwith `@AvroAlias`. The given aliases may contain the full name of the alias type or only the name.\n\n\u003e [Avro spec link](https://avro.apache.org/docs/1.12.1/specification/#aliases)\n\n\u003e [!NOTE]\n\u003e Aliases are not impacted by [naming strategy](#field-naming-strategy-overall-change), so you need to provide aliases directly applying the corresponding naming strategy if you\n\u003e need to respect it.\n\n```kotlin\nimport com.github.avrokotlin.avro4k.AvroAlias\n\n@Serializable\n@AvroAlias(\"full.name.RecordName\", \"JustOtherRecordName\")\ndata class MyData(\n    @AvroAlias(\"anotherFieldName\", \"old_field_name\") val myField: String\n)\n```\n\n\u003e [!NOTE]\n\u003e This impacts the schema generation, the serialization and the deserialization.\n\n\u003e [!WARNING]\n\u003e Do not use `@org.apache.avro.reflect.AvroAlias` as this annotation is not visible by Avro4k.\n\n## Add metadata to a schema (custom properties)\n\nYou can add custom properties to a schema to have additional metadata on a type.\nTo do so, you can annotate the data class or field with `@AvroProp`. The value can be a regular string or any json content:\n\n```kotlin\n@Serializable\n@AvroProp(\"custom_string_property\", \"The default non-json value\")\n@AvroProp(\"custom_int_property\", \"42\")\n@AvroProp(\"custom_json_property\", \"\"\"{\"key\":\"value\"}\"\"\")\ndata class MyData(\n    @AvroProp(\"custom_field_property\", \"Also working on fields\")\n    val myField: String\n)\n```\n\nTo add metadata to a type not owned by you, you can use a value class. Here an example with a `BigQuery` type that needs the property `sqlType = JSON` on `string` type:\n```kotlin\n@Serializable\nvalue class BigQueryJson(@AvroProp(\"sqlType\", \"JSON\") val value: String)\n\nprintln(Avro.schema\u003cBigQueryJson\u003e().toString(true)) // {\"type\":\"string\",\"sqlType\":\"JSON\"}\n```\n\n\u003e [!NOTE]\n\u003e This impacts only the schema generation. For more details, check the [avro specification](https://avro.apache.org/docs/1.12.1/specification/#schema_props).\n\n\u003e [!WARNING]\n\u003e Do not use `@org.apache.avro.reflect.AvroMeta` as this annotation is not visible by Avro4k.\n\n## Change enum values' name\n\nBy default, enum symbols are exactly the name of the enum values in the enum classes. To change this default, you need to annotate enum values with `@SerialName`.\n\n```kotlin\n@Serializable\nenum class MyEnum {\n    @SerialName(\"CUSTOM_NAME\")\n    A,\n    B,\n    C\n}\n```\n\n\u003e [!NOTE]\n\u003e This impacts the schema generation, the serialization and the deserialization.\n\n## Set enum default\n\nWhen reading with a schema but was written with a different schema, sometimes the reader can miss the enum symbol that triggers an error.\nTo avoid this error, you can set a default symbol for an enum by annotating the expected fallback with `@AvroEnumDefault`.\n\n```kotlin\n@Serializable\nenum class MyEnum {\n    A,\n\n    @AvroEnumDefault\n    B,\n\n    C\n}\n```\n\n\u003e [!NOTE]\n\u003e This impacts the schema generation, the serialization and the deserialization.\n\n## Change type name (RECORD and ENUM)\n\nRECORD and ENUM types in Avro have a name and a namespace (composing a full-name like `namespace.name`). By default, the name is the name of the class/enum and the namespace is the\npackage name.\nTo change this default, you need to annotate data classes and enums with `@SerialName`.\n\n\u003e [!WARNING]\n\u003e `@SerialName` is redefining the full-name of the annotated class or enum, so you **must** repeat the name or the namespace if you only need to change the namespace or the name\n\u003e respectively.\n\n\u003e [!NOTE]\n\u003e This impacts the schema generation, the serialization and the deserialization.\n\n### Changing the name while keeping the namespace\n\n```kotlin\npackage my.package\n\n@Serializable\n@SerialName(\"my.package.MyRecord\")\ndata class MyData(val myField: String)\n```\n\n### Changing the namespace while keeping the name\n\n```kotlin\npackage my.package\n\n@Serializable\n@SerialName(\"custom.namespace.MyData\")\ndata class MyData(val myField: String)\n```\n\n### Changing the name and the namespace\n\n```kotlin\npackage my.package\n\n@Serializable\n@SerialName(\"custom.namespace.MyRecord\")\ndata class MyData(val myField: String)\n```\n\n## Change type name (FIXED only)\n\n\u003e [!WARNING]\n\u003e For the moment, it is not possible to manually change the namespace or the name of a FIXED type as the type name is coming from the field name and the namespace from the\n\u003e enclosing data class package.\n\n## Set a custom schema\n\nTo associate a type or a field to a custom schema, you need to create a serializer that will handle the serialization and deserialization of the value, and provide the expected schema.\n\nSee [support additional non-serializable types](#support-additional-non-serializable-types) section to get detailed explanation about writing a serializer and\nregistering it.\n\n## Skip a kotlin field\n\nTo skip a field during encoding, you can annotate it with `@kotlinx.serialization.Transient`.\nNote that you need to provide a default value for the field as the field will be totally discarded also during encoding (IntelliJ should trigger a warn).\n\n```kotlin\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.Transient\n\n@Serializable\ndata class Foo(val a: String, @Transient val b: String = \"default value\")\n```\n\n\u003e [!NOTE]\n\u003e This impacts the schema generation, the serialization and the deserialization.\n\n## Force a field to be a `string` type\n\nYou can force a field (or the value class' property) to have its inferred schema as a `string` type by annotating it with `@AvroString`.\n\nCompatible types visible in the [types matrix](#types-matrix), indicated by the \"Other compatible writer types\" column. The **writer schema compatibility is still respected**, so if the field has been written as an int, a stringified int will be deserialized as an int without the need of parsing it. It is the same for the rerverse: If an int has been written as a string, it will be deserialized as an int by parsing the string content.\n\n\u003e [!NOTE]\n\u003e Note that the type must be compatible with the `string` type, otherwise it will be ignored.\n\u003e Your custom serializer generated schema must handle this annotation, or it will be ignored.\n\n**Examples:**\n```kotlin\n@Serializable\ndata class MyData(\n    @AvroString val anInt: Int,\n    @AvroString val rawString: ByteArray,\n    @AvroString @Contextual val bigDecimal: BigDecimal,\n)\n@JvmInline\n@Serializable\nvalue class StringifiedPrice(\n    @AvroString val amount: Double,\n)\n```\n\n\u003e [!NOTE]\n\u003e This impacts the schema generation, the serialization and the deserialization.\n\n# Nullable fields, optional fields and compatibility\n\nWith avro, you can have nullable fields and optional fields, that are taken into account for compatibility checking when using the schema registry.\n\nBut if you want to remove a nullable field that is not optional, depending on the compatibility mode, it may not be compatible because of the missing default value.\n\n- What is an optional field ?\n\n\u003e An optional field is a field that have a *default* value, like an int with a default as `-1`.\n\n- What is a nullable field ?\n\n\u003e A nullable field is a field that contains a `null` type in its type union, but **it's not an optional field if you don't put `default` value to `null`**.\n\nSo to mark a field as optional and facilitate avro contract evolution regarding compatibility checks, then set `default` to `null`.\n\n# Known problems\n\n- Kotlin 1.7.20 up to 1.8.10 cannot properly compile @SerialInfo-Annotations on enums (see https://github.com/Kotlin/kotlinx.serialization/issues/2121).\n  This is fixed with kotlin 1.8.20. So if you are planning to use any of avro4k's annotations on enum types, please make sure that you are using kotlin \u003e= 1.8.20.\n- Avro 1.12.0 introduced corruption for list of double in generic encoding, where doubles are casted to floats. Will be fixed for the next version when it will be released \n\n# Migrating from v1 to v2\nHeads up to the [migration guide](Migrating-from-v1.md) to update your code from avro4k v1 to v2.\n\n# Contributions\n\nContributions to avro4k are always welcome. Good ways to contribute include:\n\n- Raising bugs and feature requests\n- Fixing bugs and enhancing the API\n- Improving the performance of avro4k\n- Adding documentation\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favro-kotlin%2Favro4k","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Favro-kotlin%2Favro4k","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favro-kotlin%2Favro4k/lists"}