diff --git a/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/BsonConfiguration.kt b/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/BsonConfiguration.kt
index 027fe8925da..9138bf12276 100644
--- a/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/BsonConfiguration.kt
+++ b/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/BsonConfiguration.kt
@@ -31,4 +31,9 @@ public data class BsonConfiguration(
     val encodeDefaults: Boolean = true,
     val explicitNulls: Boolean = false,
     val classDiscriminator: String = "_t",
+    val bsonNamingStrategy: BsonNamingStrategy? = null
 )
+
+public enum class BsonNamingStrategy {
+    SNAKE_CASE,
+}
diff --git a/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/BsonDecoder.kt b/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/BsonDecoder.kt
index 99e5d2acb17..c00d09345d0 100644
--- a/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/BsonDecoder.kt
+++ b/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/BsonDecoder.kt
@@ -37,11 +37,13 @@ import org.bson.BsonType
 import org.bson.BsonValue
 import org.bson.codecs.BsonValueCodec
 import org.bson.codecs.DecoderContext
+import org.bson.codecs.kotlinx.utils.BsonCodecUtils.cacheElementNamesByDescriptor
 import org.bson.codecs.kotlinx.utils.BsonCodecUtils.createBsonArrayDecoder
 import org.bson.codecs.kotlinx.utils.BsonCodecUtils.createBsonDecoder
 import org.bson.codecs.kotlinx.utils.BsonCodecUtils.createBsonDocumentDecoder
 import org.bson.codecs.kotlinx.utils.BsonCodecUtils.createBsonMapDecoder
 import org.bson.codecs.kotlinx.utils.BsonCodecUtils.createBsonPolymorphicDecoder
+import org.bson.codecs.kotlinx.utils.BsonCodecUtils.getCachedElementNamesByDescriptor
 import org.bson.internal.NumberCodecHelper
 import org.bson.internal.StringCodecHelper
 import org.bson.types.ObjectId
@@ -102,6 +104,7 @@ internal sealed class AbstractBsonDecoder(
                     elementDescriptor.serialName, elementDescriptor.isNullable && !descriptor.isElementOptional(it))
             }
         this.elementsMetadata = elementsMetadata
+        cacheElementNamesByDescriptor(descriptor, configuration)
     }
 
     override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
@@ -129,7 +132,13 @@ internal sealed class AbstractBsonDecoder(
             }
 
         return name?.let {
-            val index = descriptor.getElementIndex(it)
+            val index =
+                if (configuration.bsonNamingStrategy == BsonNamingStrategy.SNAKE_CASE) {
+                    getCachedElementNamesByDescriptor(descriptor)[it]?.let { name -> descriptor.getElementIndex(name) }
+                        ?: UNKNOWN_NAME
+                } else {
+                    descriptor.getElementIndex(it)
+                }
             return if (index == UNKNOWN_NAME) {
                 reader.skipValue()
                 decodeElementIndexImpl(descriptor)
diff --git a/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/BsonEncoder.kt b/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/BsonEncoder.kt
index 1470bbb76a5..8a34bccdb36 100644
--- a/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/BsonEncoder.kt
+++ b/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/BsonEncoder.kt
@@ -31,6 +31,7 @@ import org.bson.BsonValue
 import org.bson.BsonWriter
 import org.bson.codecs.BsonValueCodec
 import org.bson.codecs.EncoderContext
+import org.bson.codecs.kotlinx.utils.BsonCodecUtils.convertCamelCase
 import org.bson.types.ObjectId
 
 /**
@@ -203,7 +204,15 @@ internal open class BsonEncoderImpl(
     }
 
     internal fun encodeName(value: Any) {
-        writer.writeName(value.toString())
+        val name =
+            value.toString().let {
+                if (configuration.bsonNamingStrategy == BsonNamingStrategy.SNAKE_CASE) {
+                    convertCamelCase(it, '_')
+                } else {
+                    it
+                }
+            }
+        writer.writeName(name)
         state = STATE.VALUE
     }
 
diff --git a/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/JsonBsonDecoder.kt b/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/JsonBsonDecoder.kt
index 4b0eee8213a..bd8b6739958 100644
--- a/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/JsonBsonDecoder.kt
+++ b/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/JsonBsonDecoder.kt
@@ -31,6 +31,7 @@ import org.bson.AbstractBsonReader
 import org.bson.BsonBinarySubType
 import org.bson.BsonType
 import org.bson.UuidRepresentation
+import org.bson.codecs.kotlinx.utils.BsonCodecUtils.toJsonNamingStrategy
 import org.bson.internal.UuidHelper
 
 @OptIn(ExperimentalSerializationApi::class)
@@ -42,6 +43,7 @@ internal interface JsonBsonDecoder : BsonDecoder, JsonDecoder {
         explicitNulls = configuration.explicitNulls
         encodeDefaults = configuration.encodeDefaults
         classDiscriminator = configuration.classDiscriminator
+        namingStrategy = configuration.bsonNamingStrategy.toJsonNamingStrategy()
         serializersModule = this@JsonBsonDecoder.serializersModule
     }
 
diff --git a/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/JsonBsonEncoder.kt b/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/JsonBsonEncoder.kt
index 6cff36a0909..4a754834e6d 100644
--- a/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/JsonBsonEncoder.kt
+++ b/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/JsonBsonEncoder.kt
@@ -30,6 +30,7 @@ import kotlinx.serialization.json.int
 import kotlinx.serialization.json.long
 import kotlinx.serialization.modules.SerializersModule
 import org.bson.BsonWriter
+import org.bson.codecs.kotlinx.utils.BsonCodecUtils.toJsonNamingStrategy
 import org.bson.types.Decimal128
 
 @OptIn(ExperimentalSerializationApi::class)
@@ -52,6 +53,7 @@ internal class JsonBsonEncoder(
         explicitNulls = configuration.explicitNulls
         encodeDefaults = configuration.encodeDefaults
         classDiscriminator = configuration.classDiscriminator
+        namingStrategy = configuration.bsonNamingStrategy.toJsonNamingStrategy()
         serializersModule = this@JsonBsonEncoder.serializersModule
     }
 
diff --git a/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/utils/BsonCodecUtils.kt b/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/utils/BsonCodecUtils.kt
index eabfebc5833..21549de7d34 100644
--- a/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/utils/BsonCodecUtils.kt
+++ b/bson-kotlinx/src/main/kotlin/org/bson/codecs/kotlinx/utils/BsonCodecUtils.kt
@@ -16,7 +16,10 @@
 package org.bson.codecs.kotlinx.utils
 
 import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.SerializationException
 import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.descriptors.elementNames
+import kotlinx.serialization.json.JsonNamingStrategy
 import kotlinx.serialization.modules.SerializersModule
 import org.bson.AbstractBsonReader
 import org.bson.BsonWriter
@@ -28,6 +31,7 @@ import org.bson.codecs.kotlinx.BsonDocumentDecoder
 import org.bson.codecs.kotlinx.BsonEncoder
 import org.bson.codecs.kotlinx.BsonEncoderImpl
 import org.bson.codecs.kotlinx.BsonMapDecoder
+import org.bson.codecs.kotlinx.BsonNamingStrategy
 import org.bson.codecs.kotlinx.BsonPolymorphicDecoder
 import org.bson.codecs.kotlinx.JsonBsonArrayDecoder
 import org.bson.codecs.kotlinx.JsonBsonDecoderImpl
@@ -59,6 +63,8 @@ internal object BsonCodecUtils {
         }
     }
 
+    private val cachedElementNamesByDescriptor: MutableMap<String, Map<String, String>> = mutableMapOf()
+
     internal fun createBsonEncoder(
         writer: BsonWriter,
         serializersModule: SerializersModule,
@@ -116,4 +122,71 @@ internal object BsonCodecUtils {
         return if (hasJsonDecoder) JsonBsonMapDecoder(descriptor, reader, serializersModule, configuration)
         else BsonMapDecoder(descriptor, reader, serializersModule, configuration)
     }
+
+    internal fun cacheElementNamesByDescriptor(descriptor: SerialDescriptor, configuration: BsonConfiguration) {
+        val convertedNameMap =
+            when (configuration.bsonNamingStrategy) {
+                BsonNamingStrategy.SNAKE_CASE -> {
+                    val snakeCasedNames = descriptor.elementNames.associateWith { name -> convertCamelCase(name, '_') }
+
+                    snakeCasedNames.entries
+                        .groupBy { entry -> entry.value }
+                        .filter { group -> group.value.size > 1 }
+                        .entries
+                        .forEach { group ->
+                            val keys = group.value.joinToString(", ") { entry -> entry.key }
+                            throw SerializationException(
+                                "$keys in ${descriptor.serialName} generate same name: ${group.key}.")
+                        }
+
+                    snakeCasedNames.entries.associate { it.value to it.key }
+                }
+                else -> emptyMap()
+            }
+
+        cachedElementNamesByDescriptor[descriptor.serialName] = convertedNameMap
+    }
+
+    internal fun getCachedElementNamesByDescriptor(descriptor: SerialDescriptor): Map<String, String> {
+        return cachedElementNamesByDescriptor[descriptor.serialName] ?: emptyMap()
+    }
+
+    // https://github.com/Kotlin/kotlinx.serialization/blob/f9f160a680da9f92c3bb121ae3644c96e57ba42e/formats/json/commonMain/src/kotlinx/serialization/json/JsonNamingStrategy.kt#L142-L174
+    internal fun convertCamelCase(value: String, delimiter: Char) =
+        buildString(value.length * 2) {
+            var bufferedChar: Char? = null
+            var previousUpperCharsCount = 0
+
+            value.forEach { c ->
+                if (c.isUpperCase()) {
+                    if (previousUpperCharsCount == 0 && isNotEmpty() && last() != delimiter) append(delimiter)
+
+                    bufferedChar?.let(::append)
+
+                    previousUpperCharsCount++
+                    bufferedChar = c.lowercaseChar()
+                } else {
+                    if (bufferedChar != null) {
+                        if (previousUpperCharsCount > 1 && c.isLetter()) {
+                            append(delimiter)
+                        }
+                        append(bufferedChar)
+                        previousUpperCharsCount = 0
+                        bufferedChar = null
+                    }
+                    append(c)
+                }
+            }
+
+            if (bufferedChar != null) {
+                append(bufferedChar)
+            }
+        }
+
+    internal fun BsonNamingStrategy?.toJsonNamingStrategy(): JsonNamingStrategy? {
+        return when (this) {
+            BsonNamingStrategy.SNAKE_CASE -> JsonNamingStrategy.SnakeCase
+            else -> null
+        }
+    }
 }
diff --git a/bson-kotlinx/src/test/kotlin/org/bson/codecs/kotlinx/KotlinSerializerCodecTest.kt b/bson-kotlinx/src/test/kotlin/org/bson/codecs/kotlinx/KotlinSerializerCodecTest.kt
index aa749368e04..85d922c3096 100644
--- a/bson-kotlinx/src/test/kotlin/org/bson/codecs/kotlinx/KotlinSerializerCodecTest.kt
+++ b/bson-kotlinx/src/test/kotlin/org/bson/codecs/kotlinx/KotlinSerializerCodecTest.kt
@@ -81,6 +81,7 @@ import org.bson.codecs.kotlinx.samples.DataClassWithBsonId
 import org.bson.codecs.kotlinx.samples.DataClassWithBsonIgnore
 import org.bson.codecs.kotlinx.samples.DataClassWithBsonProperty
 import org.bson.codecs.kotlinx.samples.DataClassWithBsonRepresentation
+import org.bson.codecs.kotlinx.samples.DataClassWithCamelCase
 import org.bson.codecs.kotlinx.samples.DataClassWithCollections
 import org.bson.codecs.kotlinx.samples.DataClassWithContextualDateValues
 import org.bson.codecs.kotlinx.samples.DataClassWithDataClassMapKey
@@ -94,6 +95,7 @@ import org.bson.codecs.kotlinx.samples.DataClassWithFailingInit
 import org.bson.codecs.kotlinx.samples.DataClassWithJsonElement
 import org.bson.codecs.kotlinx.samples.DataClassWithJsonElements
 import org.bson.codecs.kotlinx.samples.DataClassWithJsonElementsNullable
+import org.bson.codecs.kotlinx.samples.DataClassWithKotlinAllowedName
 import org.bson.codecs.kotlinx.samples.DataClassWithListThatLastItemDefaultsToNull
 import org.bson.codecs.kotlinx.samples.DataClassWithMutableList
 import org.bson.codecs.kotlinx.samples.DataClassWithMutableMap
@@ -105,6 +107,7 @@ import org.bson.codecs.kotlinx.samples.DataClassWithNulls
 import org.bson.codecs.kotlinx.samples.DataClassWithPair
 import org.bson.codecs.kotlinx.samples.DataClassWithParameterizedDataClass
 import org.bson.codecs.kotlinx.samples.DataClassWithRequired
+import org.bson.codecs.kotlinx.samples.DataClassWithSameSnakeCaseName
 import org.bson.codecs.kotlinx.samples.DataClassWithSequence
 import org.bson.codecs.kotlinx.samples.DataClassWithSimpleValues
 import org.bson.codecs.kotlinx.samples.DataClassWithTriple
@@ -1126,6 +1129,38 @@ class KotlinSerializerCodecTest {
         }
     }
 
+    @Test
+    fun testSnakeCaseNamingStrategy() {
+        val expected =
+            """{"two_words": "", "my_property": "", "camel_case_underscores": "", "url_mapping": "",
+            | "my_http_auth": "", "my_http2_api_key": "", "my_http2fast_api_key": ""}"""
+                .trimMargin()
+        val dataClass = DataClassWithCamelCase()
+        assertRoundTrips(expected, dataClass, BsonConfiguration(bsonNamingStrategy = BsonNamingStrategy.SNAKE_CASE))
+    }
+
+    @Test
+    fun testSameSnakeCaseName() {
+        val expected = """{"my_http_auth": ""}"""
+        val dataClass = DataClassWithSameSnakeCaseName()
+        val exception =
+            assertThrows<SerializationException> {
+                assertRoundTrips(
+                    expected, dataClass, BsonConfiguration(bsonNamingStrategy = BsonNamingStrategy.SNAKE_CASE))
+            }
+        assertEquals(
+            "myHTTPAuth, myHttpAuth in org.bson.codecs.kotlinx.samples.DataClassWithSameSnakeCaseName " +
+                "generate same name: my_http_auth.",
+            exception.message)
+    }
+
+    @Test
+    fun testKotlinAllowedName() {
+        val expected = """{"имя_переменной": "", "variable _name": ""}"""
+        val dataClass = DataClassWithKotlinAllowedName()
+        assertRoundTrips(expected, dataClass, BsonConfiguration(bsonNamingStrategy = BsonNamingStrategy.SNAKE_CASE))
+    }
+
     private inline fun <reified T : Any> assertRoundTrips(
         expected: String,
         value: T,
@@ -1184,6 +1219,7 @@ class KotlinSerializerCodecTest {
         serializersModule: SerializersModule = defaultSerializersModule,
         configuration: BsonConfiguration = BsonConfiguration()
     ): T {
+        println("Deserializing: ${value.toJson()}")
         val codec = KotlinSerializerCodec.create(T::class, serializersModule, configuration)!!
         return codec.decode(BsonDocumentReader(value), DecoderContext.builder().build())
     }
diff --git a/bson-kotlinx/src/test/kotlin/org/bson/codecs/kotlinx/samples/DataClasses.kt b/bson-kotlinx/src/test/kotlin/org/bson/codecs/kotlinx/samples/DataClasses.kt
index e7a06600d20..91b8f5a218a 100644
--- a/bson-kotlinx/src/test/kotlin/org/bson/codecs/kotlinx/samples/DataClasses.kt
+++ b/bson-kotlinx/src/test/kotlin/org/bson/codecs/kotlinx/samples/DataClasses.kt
@@ -102,6 +102,29 @@ data class DataClassWithDefaults(
     val listSimple: List<String> = listOf("a", "b", "c")
 )
 
+@Serializable
+data class DataClassWithCamelCase(
+    val twoWords: String = "",
+    @Suppress("ConstructorParameterNaming") val MyProperty: String = "",
+    @Suppress("ConstructorParameterNaming") val camel_Case_Underscores: String = "",
+    @Suppress("ConstructorParameterNaming") val URLMapping: String = "",
+    val myHTTPAuth: String = "",
+    val myHTTP2ApiKey: String = "",
+    val myHTTP2fastApiKey: String = "",
+)
+
+@Serializable
+data class DataClassWithSameSnakeCaseName(
+    val myHTTPAuth: String = "",
+    val myHttpAuth: String = "",
+)
+
+@Serializable
+data class DataClassWithKotlinAllowedName(
+    @Suppress("ConstructorParameterNaming") val имяПеременной: String = "",
+    @Suppress("ConstructorParameterNaming") val `variable Name`: String = "",
+)
+
 @Serializable data class DataClassWithNulls(val boolean: Boolean?, val string: String?, val listSimple: List<String?>?)
 
 @Serializable