Back to Blog

Kotlin to Swift Export: Native iOS Integration Guide 2025

Kotlin 2.2.20's Swift Export delivers native Swift interop for KMP. Code examples, migration guide, and production readiness assessment for iOS teams.

Posted by

Kotlin to Swift Export: Native iOS Integration Guide 2025

TL;DR: Why Swift Export is a game-changer for KMP

The Problem: Kotlin Multiplatform code has always been accessible from iOS through Objective-C headers: clunky, verbose, and frustrating for Swift developers.
The Solution: Kotlin 2.2.20 introduces Swift Export, allowing you to export Kotlin code directly as native Swift modules. No more Objective-C bridge.
What This Means:
  • Swift developers call KMP code that looks and feels like native Swift
  • Better IDE autocomplete and type inference in Xcode
  • Preserved nullability, packages, and type aliases
  • Multi-module support with clean separation
The Catch: It's experimental in 2025, with a stable release planned for 2026. But early adopters are already using it in production.

The Objective-C bridge problem that's been haunting iOS developers

If you've worked with Kotlin Multiplatform on iOS, you know the pain: your beautifully written Kotlin code gets exposed to Swift through Objective-C headers.
This creates friction at every turn:

What iOS developers struggle with today

1. Type information gets lost in translation
Kotlin generics don't fully translate through Objective-C. Your type-safe Kotlin code becomes loosely-typed Swift code with lots of Any types.
2. Nullable types require awkward boxing
In Objective-C interop, primitive types like Int? require boxing with KotlinInt, making Swift code verbose and un-idiomatic.
3. Packages get flattened
Kotlin's clean package structure gets collapsed into a flat namespace, leading to naming conflicts and poor organization in Swift.
4. No default arguments
Swift developers must specify every parameter, even when Kotlin code defines sensible defaults.
5. Enums become integers instead of reference types
Type-safe Kotlin enums get represented as integers in Objective-C, losing compile-time safety in Swift.
The result? iOS developers feel like second-class citizens. They're calling "foreign" code that doesn't follow Swift conventions, and that creates resistance to KMP adoption.
As one iOS developer put it:
"I love the idea of sharing business logic, but having to deal with Objective-C-generated headers in 2025 feels like using a fax machine to send emails."
This is the problem Swift Export solves.

What is Swift Export? The direct Kotlin-to-Swift bridge

Swift Export is a new experimental feature in Kotlin 2.2.20 that exports Kotlin code directly as native Swift modules with no Objective-C intermediary required.
Instead of this flow:
Kotlin Code → Objective-C Headers → Swift Code (painful)
You get this:
Kotlin Code → Native Swift Modules (seamless)

Key improvements with Swift Export

✅ Multi-module support

Each Kotlin module is exported as a separate Swift module. This means cleaner imports and better dependency management in Xcode.
swift
// Clean, modular imports import SharedNetworking import SharedDatabase import SharedAuth // Instead of everything coming from one giant "Shared" module

✅ Package preservation

Kotlin packages are explicitly preserved during export, avoiding naming conflicts and maintaining your intended code structure.
kotlin
// Kotlin package com.myapp.auth class LoginManager package com.myapp.network class LoginManager // Different class, same name
swift
// Swift - No naming conflicts! import com.myapp.auth.LoginManager import com.myapp.network.LoginManager

✅ Type aliases preserved

Kotlin type aliases are exported and preserved in Swift, improving code readability.
kotlin
// Kotlin typealias UserId = String typealias ProductId = Long fun getUser(id: UserId): User
swift
// Swift - Type aliases work! typealias UserId = String typealias ProductId = Int64 func getUser(id: UserId) -> User

✅ Enhanced nullability

Enhanced nullability for primitives, unlike Objective-C interop which required boxing types like Int?.
kotlin
// Kotlin fun calculateDiscount(code: String?): Int?
swift
// With Objective-C bridge (OLD WAY): func calculateDiscount(code: String?) -> KotlinInt? // Awkward! // With Swift Export (NEW WAY): func calculateDiscount(code: String?) -> Int? // Natural!

✅ No ambiguity with overloaded functions

You can call Kotlin's overloaded functions in Swift without ambiguity.
kotlin
// Kotlin fun fetchData(id: String): Data fun fetchData(id: String, useCache: Boolean): Data
swift
// Swift - Both overloads work correctly let data1 = fetchData(id: "123") let data2 = fetchData(id: "123", useCache: true)

How to set up Swift Export in your KMP project

Ready to try Swift Export? Here's how to configure it in your Kotlin Multiplatform project.

Step 1: Update to Kotlin 2.2.20+

Swift Export is available by default in Kotlin 2.2.20 and later.
Update your gradle/libs.versions.toml:
toml
[versions] kotlin = "2.2.20"

Step 2: Enable Swift Export

Add this to your gradle.properties:
properties
kotlin.experimental.swift-export.enabled=true

Step 3: Configure Swift Export in build.gradle.kts

Here's how to configure Swift Export for your shared module:
kotlin
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework import org.jetbrains.kotlin.gradle.ExperimentalSwiftExportDsl plugins { alias(libs.plugins.kotlinMultiplatform) } kotlin { // iOS targets iosX64() iosArm64() iosSimulatorArm64() @OptIn(ExperimentalSwiftExportDsl::class) swiftExport { // Root module name moduleName = "Shared" // Flatten packages to avoid deeply nested imports flattenPackage = "com.myapp.shared" // Export additional modules export(project(":networking")) { moduleName = "SharedNetworking" flattenPackage = "com.myapp.networking" } export(project(":database")) { moduleName = "SharedDatabase" flattenPackage = "com.myapp.database" } } sourceSets { commonMain.dependencies { // Your common dependencies implementation(libs.kotlinx.coroutines.core) } iosMain.dependencies { // iOS-specific dependencies } } }

Step 4: Update your Xcode build phase

Replace the embedAndSignAppleFrameworkForXcode task with the new embedSwiftExportForXcode task:
In Xcode, go to your app target → Build Phases → Run Script, and update the script:
bash
# OLD (Objective-C bridge): ./gradlew :shared:embedAndSignAppleFrameworkForXcode # NEW (Swift Export): ./gradlew :shared:embedSwiftExportForXcode

Step 5: Build and use in Swift

Build your project, and you can now import and use your Kotlin code as native Swift modules:
swift
import Shared import SharedNetworking import SharedDatabase class MyViewController: UIViewController { let authManager = AuthManager() let apiClient = ApiClient() func login(email: String, password: String) { // Calling Kotlin code feels like native Swift! authManager.login(email: email, password: password) { result in switch result { case .success(let user): print("Logged in: \(user.name)") case .failure(let error): print("Login failed: \(error)") } } } }

Real-world example: Before and after Swift Export

Let's see the difference Swift Export makes with a real-world authentication example.

Kotlin shared code

kotlin
// shared/src/commonMain/kotlin/com/myapp/auth/AuthRepository.kt package com.myapp.auth typealias UserId = String data class User( val id: UserId, val name: String, val email: String, val isPremium: Boolean ) class AuthRepository { suspend fun login(email: String, password: String): Result<User> { // Simulate API call delay(1000) return if (email.isNotEmpty() && password.length >= 6) { Result.success( User( id = "user_123", name = "John Doe", email = email, isPremium = false ) ) } else { Result.failure(Exception("Invalid credentials")) } } fun logout(userId: UserId?) { println("Logging out user: $userId") } fun getDiscount(isPremium: Boolean?): Int? { return when (isPremium) { true -> 20 false -> 5 null -> null } } }

Using it in Swift: OLD WAY (Objective-C bridge)

swift
// ❌ Painful: Objective-C bridge version import shared class LoginViewModel { let authRepo = SharedAuthRepository() func login(email: String, password: String) { // Awkward: Calling suspend functions requires ceremony authRepo.login(email: email, password: password) { result, error in if let result = result { // Type casting required if let user = result as? User { print("User: \(user.name)") } } } } func logout(userId: String?) { // Verbose: Optional primitives require KotlinInt wrapping if let userId = userId { authRepo.logout(userId: userId) } else { authRepo.logout(userId: nil) } } func getDiscount(isPremium: Bool?) { // Awkward: Nullable Bool becomes KotlinBoolean let kotlinBool = isPremium != nil ? KotlinBoolean(value: isPremium!) : nil let discount = authRepo.getDiscount(isPremium: kotlinBool) // Result is KotlinInt?, not Int? if let kotlinDiscount = discount { let intDiscount = kotlinDiscount.intValue // Need to unwrap print("Discount: \(intDiscount)%") } } }

Using it in Swift: NEW WAY (Swift Export)

swift
// ✅ Beautiful: Swift Export version import Shared import com.myapp.auth // Package preserved! class LoginViewModel { let authRepo = AuthRepository() func login(email: String, password: String) async { // Clean: async/await works naturally do { let user = try await authRepo.login(email: email, password: password) print("User: \(user.name)") print("Premium: \(user.isPremium)") } catch { print("Login failed: \(error)") } } func logout(userId: UserId?) { // Natural: Optional String just works authRepo.logout(userId: userId) } func getDiscount(isPremium: Bool?) { // Clean: Native Swift optionals if let discount = authRepo.getDiscount(isPremium: isPremium) { print("Discount: \(discount)%") // Native Int, not KotlinInt } } }
See the difference? The Swift Export version looks and feels like native Swift code. Your iOS team will actually enjoy using your shared Kotlin code.

What works (and what doesn't) in Swift Export 2025

Swift Export is experimental in 2025, which means it's functional but still evolving. Here's what you need to know:

✅ What works well right now

  • Basic types and nullability - Strings, primitives, nullable types all map cleanly
  • Data classes - Kotlin data classes become Swift structs
  • Functions and methods - Including overloads and default parameters
  • Packages and modules - Multi-module projects with preserved structure
  • Type aliases - Your type aliases work in Swift
  • Enums - Kotlin enums map to Swift enums (not integers!)
  • Basic coroutines support - Suspend functions can be called from Swift

⚠️ What's coming but not ready yet

  • Full suspend function support - Currently works but will be enhanced with better async/await integration
  • Kotlin Flow to Swift Combine/AsyncSequence - Planned for future releases
  • Advanced generics - Some complex generic scenarios still need work
  • Codable conformance - Automatic Codable generation for Kotlin classes (highly requested)
  • Full Swift Package Manager integration - Currently works with direct integration only
Timeline: JetBrains aims for a stable release covering most features for idiomatic interoperability in 2026.

🚫 Current limitations to be aware of

  • Experimental status - APIs and behavior may change between releases
  • Direct integration only - Works with direct Xcode integration, not yet with CocoaPods/Carthage
  • Documentation evolving - Official docs are improving but not comprehensive yet
  • Limited third-party tooling - Tools like SKIE don't yet support Swift Export

Migration guide: From Objective-C bridge to Swift Export

Already have a KMP project using the Objective-C bridge? Here's how to migrate to Swift Export.

Migration strategy: Gradual is better

You don't need to migrate everything at once. Here's a safe, incremental approach:

Phase 1: Set up Swift Export in parallel (Week 1)

  1. Update to Kotlin 2.2.20+
  2. Enable Swift Export in gradle.properties
  3. Configure swiftExport DSL in build.gradle.kts
  4. Keep your existing Objective-C bridge working

Phase 2: Test with a single module (Week 2)

  1. Choose a low-risk module (like a utility library)
  2. Export it with Swift Export
  3. Update Swift code to use the new module
  4. Test thoroughly before proceeding

Phase 3: Migrate remaining modules (Weeks 3-4)

  1. Migrate one module at a time
  2. Update Swift imports and usage
  3. Remove Objective-C bridge dependencies as you go

Phase 4: Remove Objective-C bridge (Week 5)

  1. Once all modules are migrated, remove the old framework task
  2. Clean up build scripts and configurations
  3. Update CI/CD pipelines

Common migration issues and solutions

Issue: "Module not found" in Xcode

Solution: Make sure you've updated the Xcode build phase to use embedSwiftExportForXcode instead of embedAndSignAppleFrameworkForXcode.

Issue: Type mismatches after migration

Solution: Update your Swift code to use native types instead of Kotlin wrapper types:
swift
// Before: let count: KotlinInt = repository.getCount() // After: let count: Int = repository.getCount()

Issue: Suspend functions not working

Solution: Ensure you're using async/await syntax instead of completion handlers:
swift
// Before (Objective-C bridge): repository.fetchData { result, error in // Handle result } // After (Swift Export): Task { do { let result = try await repository.fetchData() // Handle result } catch { // Handle error } }

Issue: Build errors with CocoaPods/Carthage

Solution: Swift Export currently works only with direct integration. You'll need to use the direct framework integration approach (embed the framework directly in your Xcode project).

Should you use Swift Export in production today?

Swift Export is experimental, but that doesn't mean you can't use it in production. Here's an honest assessment:

✅ Use Swift Export if...

1. You're starting a new KMP project
There's no migration cost, and you'll benefit from better Swift interop from day one.
2. Your iOS team is frustrated with Objective-C headers
The productivity boost and improved developer experience may outweigh the experimental status.
3. You can tolerate API changes between Kotlin releases
Experimental features evolve, but JetBrains is committed to stabilizing Swift Export.
4. You're using direct framework integration
Swift Export works great with this setup. If you're already using it, migration is straightforward.
5. You're willing to be an early adopter
Early feedback helps shape the final API. You'll influence the direction of the feature.

⚠️ Wait for stable release if...

1. You need guaranteed API stability
Wait for the stable release in 2026 if you can't tolerate breaking changes.
2. You rely on CocoaPods or Carthage
Swift Export doesn't support these yet. Stick with the Objective-C bridge for now.
3. You use complex third-party KMP libraries
Not all libraries have tested Swift Export compatibility. Verify before migrating.
4. Your team has limited capacity for troubleshooting
Experimental features occasionally have rough edges. Wait if you can't dedicate time to debugging.
5. You're in a regulated industry with strict tooling requirements
Banking, healthcare, and other regulated industries may require stable, production-ready tooling.

The pragmatic approach: Test it now, adopt when stable

Here's what we recommend for most teams:
  1. Experiment with Swift Export in a side project or feature branch - Get familiar with the setup and benefits
  2. Monitor JetBrains' roadmap updates - Track progress toward the stable release in 2026
  3. Prepare your codebase - Structure your Kotlin code to be Swift-friendly (avoid problematic patterns)
  4. Plan migration - Have a migration strategy ready for when stable release arrives
  5. Adopt gradually - When you do migrate, start with low-risk modules
Bottom line: Swift Export is production-ready enough for many teams, especially new projects. But if you need absolute stability, waiting until 2026 for the stable release is reasonable.

The future of KMP iOS development: What's coming next

Swift Export is just the beginning. Here's what JetBrains has planned for Kotlin Multiplatform iOS development:

2025-2026 Roadmap highlights

Built-in suspend function support

Native async/await integration without manual bridging. Kotlin coroutines will map directly to Swift concurrency.
kotlin
// Kotlin suspend fun fetchUser(id: String): User
swift
// Swift - Works seamlessly with async/await let user = try await fetchUser(id: "123")

Kotlin Flow to Swift AsyncSequence

Automatic bridging from Kotlin Flow to Swift's native AsyncSequence.
kotlin
// Kotlin fun observeUsers(): Flow<List<User>>
swift
// Swift - Native for-await loop for await users in observeUsers() { print("Users updated: \(users.count)") }

Codable conformance generation

Automatic Codable protocol conformance for Kotlin data classes, making JSON serialization seamless in Swift.
kotlin
// Kotlin @Serializable data class User(val id: String, val name: String)
swift
// Swift - Automatically Codable! let json = try JSONEncoder().encode(user) let decoded = try JSONDecoder().decode(User.self, from: json)

Swift Package Manager integration

First-class support for distributing KMP libraries as Swift Packages.
swift
// Package.swift dependencies: [ .package(url: "https://github.com/mycompany/kmp-sdk", from: "1.0.0") ]

Stable release in 2026

JetBrains aims for a stable release covering most features essential for idiomatic interoperability between Swift and Kotlin by 2026.

Why this matters for iOS adoption

The biggest barrier to KMP adoption has always been iOS developer resistance. Swift Export addresses this head-on:
  • No more "it feels foreign" objections - Swift Export makes KMP code feel native
  • Better iOS team morale - Developers enjoy working with clean, idiomatic Swift APIs
  • Easier hiring - iOS developers don't need to learn Objective-C quirks
  • Faster adoption - Less friction means faster time-to-value for KMP
By 2026, calling Kotlin code from Swift will be as natural as calling Swift code from Swift. That's the vision, and JetBrains is executing on it.

Beyond the bridge: Alternative interop solutions

Swift Export isn't the only way to improve KMP-iOS interop. Here are complementary tools and techniques:

SKIE: Enhanced Objective-C interop

What it does: SKIE hooks into the KMP build pipeline to enhance the generated Objective-C code with better Swift compatibility.
When to use it: If you're stuck on the Objective-C bridge for now, SKIE improves the developer experience significantly.
Key features:
  • Automatic suspend function to async/await conversion
  • Flow to AsyncSequence bridging
  • Better enum support
  • Default parameters in Swift
Note: SKIE doesn't currently support Swift Export, but it's a great interim solution.

KMMBridge: Simplified iOS distribution

What it does: Simplifies publishing and distributing KMP frameworks to iOS apps via CocoaPods, Swift Package Manager, or direct integration.
When to use it: If you're building an SDK or library to be consumed by multiple iOS apps.
Compatibility: Works with both Objective-C bridge and Swift Export (when using direct integration).

Wrapper classes: Manual Swift-friendly API

What it does: Create a thin Swift wrapper layer around your KMP code to provide a more idiomatic Swift API.
When to use it: When you need precise control over the iOS API surface, or when dealing with complex types that don't translate well.
swift
// Swift wrapper around KMP code class UserRepository { private let kmpRepo: KmpUserRepository init() { self.kmpRepo = KmpUserRepository() } func fetchUser(id: String) async throws -> User { return try await withCheckedThrowingContinuation { continuation in kmpRepo.fetchUser(id: id) { result, error in if let error = error { continuation.resume(throwing: error) } else if let result = result { continuation.resume(returning: result) } } } } }
Tradeoff: More boilerplate, but maximum control and compatibility.

Getting started with Swift Export today

Ready to give Swift Export a try? Here's your action plan:

Your Swift Export getting started checklist

  1. Create a sample project
  2. Update your existing project
    • Upgrade to Kotlin 2.2.20 or later
    • Enable Swift Export in gradle.properties
    • Configure the swiftExport DSL in build.gradle.kts
  3. Test with a low-risk module
    • Choose a utility or helper module
    • Export it and verify in Xcode
    • Write Swift code that uses it
  4. Monitor the roadmap
  5. Join the community

Swift Export vs Objective-C bridge: The final verdict

Let's summarize when to use each approach:

✅ Use Swift Export when:

  • Starting a new KMP project
  • Your iOS team values developer experience
  • You use direct framework integration
  • You're comfortable with experimental features
  • You want to prepare for the future of KMP
  • You can dedicate time to potential debugging
  • Your project uses modern Swift concurrency

⚠️ Stick with Objective-C bridge when:

  • You need guaranteed API stability
  • You rely on CocoaPods/Carthage
  • You use third-party tools like SKIE (for now)
  • You're in a regulated industry
  • Your project is already working well
  • You can wait for the 2026 stable release
  • Migration risk outweighs benefits currently
The trend is clear: Swift Export represents the future of KMP iOS development. The Objective-C bridge isn't going away immediately, but JetBrains' focus is on making Swift Export the standard by 2026.
If you're planning a KMP project that will be around for 2+ years, Swift Export is worth serious consideration, even in its experimental state.

Ready to build production-ready KMP apps with great iOS support?

Swift Export solves the iOS interop problem, but you still need to build the rest of your app: authentication, payments, push notifications, CI/CD pipelines, and more.
You could spend weeks setting all this up from scratch, or you could start shipping features on day one.

KMPShip: Production-ready Kotlin Multiplatform starter kit

Everything you need to ship iOS and Android apps from a single codebase with Swift-friendly APIs built in.

✨ iOS-first developer experience:

  • 🔐 Authentication (Google, Apple, Email)
  • 💳 In-app purchases (StoreKit integration)
  • 🔔 Push notifications (APNs ready)
  • 🚀 CI/CD with Fastlane

💪 Built for Swift developers:

  • 📱 Swift-friendly API design
  • 📚 iOS-specific documentation
  • 🏗️ Clean architecture patterns
  • ⚡ Ready for Swift Export migration
Your iOS team will love working with shared Kotlin code. Your Android team will love sharing business logic. You'll love shipping faster.
Join developers shipping production apps with Kotlin Multiplatform

Sources and references

Official documentation and announcements:

Technical guides and tutorials:

Community insights and best practices:


Note: Swift Export is an experimental feature as of Kotlin 2.2.20 (2025). Code examples are based on official documentation and community testing. Check the official Kotlin documentation for the latest updates and API changes.



Continue your Kotlin Multiplatform journey

Want to learn more about KMP iOS development?


Quick Questions About KMP Development

Still have questions about Kotlin Multiplatform? Get instant answers:

Ready to ship iOS and Android apps faster?

Skip the weeks of boilerplate setup and start building features that make you money.