MTU (Maximum Transmission Unit) is the maximum size of useful data in one BLE packet that can be transmitted between devices in a single GATT transaction. In BLE Classic (4.x), the MTU is fixed at 23 bytes, which is sufficient for small sensor readings but insufficient for file transfers or OTA updates. Bluetooth Core Specification 4.2 (2014) introduced the MTU Size Request procedure, allowing negotiation of a larger MTU — up to 247 bytes (BLE 5.0 — up to 251 bytes). Proper MTU configuration is one of the key performance factors for BLE applications transmitting large amounts of data.
Key Takeaways
Maximum Transmission Unit (MTU) in the context of BLE is the maximum size of an Application Protocol Data Unit (APDU) that a device can accept in a single GATT request. MTU is defined at the ATT (Attribute Protocol) level and includes the ATT header (1 byte) + useful data. By default, all BLE devices support MTU 23 bytes (23 = 1 byte ATT header + 22 bytes of data).
MTU is not a physical limit of the radio channel, but rather an agreement between devices at the GATT level. The physical BLE packet size at the Link Layer can be larger (up to 27 bytes in BLE 4.0, up to 257 bytes in BLE 5.0 with Data Length Extension), but the GATT layer limits how much data is transmitted per transaction. Data Length Extension (DLE) is a separate Link Layer mechanism that increases the physical packet to 251 bytes and must be negotiated independently.
The difference between MTU and DLE: ATT MTU — how much data is transmitted per GATT request, DLE — how much data fits in one Link Layer packet. For maximum speed, both parameters must be negotiated. Without DLE, even with MTU 247 bytes, data will be fragmented into multiple 27-byte Link Layer packets, reducing throughput.
MTU Size Request — a procedure initiated by the Central after establishing a GATT connection. The Central sends an MTU Request specifying its MTU Capacity (the maximum size it can accept). The Peripheral responds with an MTU Response containing its own value. The resulting MTU is the minimum of the two values. If the Central offers MTU 512 and the Peripheral supports only 128, the connection will use MTU 128.
import CoreBluetooth
// Request maximum MTU on iOS
func requestMTU(central: CBCentralManager,
peripheral: CBPeripheral) {
peripheral.maximumWriteValueLength(
for: .withoutResponse
)
// iOS negotiates MTU automatically on connection
// MTU = 512 for BLE 5.0 devices
let mtu = peripheral.maximumWriteValueLength(
for: .withResponse
)
print("Negotiated MTU: " +
String(mtu))
}
Timing of negotiation: The MTU Request should be sent after service discovery (discoverServices) but before active data transmission begins. In iOS Core Bluetooth, MTU is automatically negotiated upon connection — the developer does not need to manually send an MTU Request. On Android, requestMTU must be called explicitly. Once negotiated, the MTU remains fixed for the connection — renegotiation is impossible without disconnecting and reconnecting.
ATT (Attribute Protocol) — the protocol on which GATT is built. An ATT packet has a maximum size of 257 bytes (ATT_MTU-1). Of these, 1 byte is Opcode (operation type), 1 byte is Handle, and up to 255 bytes is Value. Therefore, the maximum MTU allowed by the ATT specification is 257 bytes (but in practice up to 251 bytes is used, since some overhead fields are still needed).
To send data larger than MTU, fragmentation is used at the application level. The developer manually splits data into chunks of size ≤ MTU and sends them sequentially. Each chunk is sent as a separate GATT Write Request. The receiving side assembles the chunks into a single buffer. There is no built-in fragmentation support in GATT — this is the developer's responsibility.
| BLE Version | Max MTU | Max DLE | ATT MTU Limit |
|---|---|---|---|
| BLE 4.0 / 4.1 | 23 bytes | 27 bytes | ATT fixed |
| BLE 4.2 | 247 bytes | 251 bytes | 257 bytes |
| BLE 5.0 | 251 bytes | 251 bytes | 257 bytes |
| Android + iOS | 512 / 517 | 251 bytes | Exceeds ATT |
Interesting fact: iOS and Android request MTU 512 and 517 bytes respectively, but this value exceeds the ATT limit. In practice, the BLE stack fragments such data automatically, sending them as multiple sequential GATT requests of up to 251 bytes each. To the developer, the difference is transparent — writeValue works with any size up to 512 bytes on iOS.
MTU size directly affects BLE connection throughput. With MTU 23 bytes, the maximum useful transfer rate is about 7–10 KB/s under ideal conditions. Increasing MTU to 247 bytes raises the speed to 60–90 KB/s (with DLE and optimal connection interval). This is especially important for applications transmitting images, audio fragments, or logs.
BLE transfer performance depends on three factors: MTU (how much data per ATT request), connection interval (how often exchange events occur), and DLE (how much data per Link Layer packet). The optimal configuration for maximum speed: MTU = 247, DLE = 251, connection interval = 7.5 ms (minimum value).
According to the Bluetooth SIG White Paper (2023), increasing MTU from 23 to 247 bytes with a connection interval of 30 ms increases throughput from 8 KB/s to 42 KB/s — a 5x increase. With a connection interval of 7.5 ms, throughput reaches 88 KB/s. For applications that do not require high speed (temperature sensors, BLE beacons), the standard MTU of 23 bytes remains sufficient.
iOS Core Bluetooth automatically negotiates MTU when connecting to a Peripheral. The developer can query the current MTU via maximumWriteValueLength but cannot set it manually. iOS uses MTU up to 512 bytes for BLE 5.0 devices and up to 247 for BLE 4.2. For writing large amounts of data, use writeType: .withResponse for guaranteed delivery.
// Request MTU on Android (Kotlin)
val bluetoothGatt: BluetoothGatt = ...
// Request MTU 517 bytes
bluetoothGatt.requestMtu(517)
// Handle result in callback
override fun onMtuChanged(
gatt: BluetoothGatt,
mtu: Int,
status: Int
) {
if (status == BluetoothGatt.GATT_SUCCESS) {
println("MTU negotiated: $mtu")
}
}
Android provides BluetoothGatt.requestMtu(int), which allows requesting any MTU up to 517 bytes. The actual MTU is determined by the peripheral device — if it only supports 23 bytes, Android will return MTU 23. To determine the current MTU, use gatt.requestMtu(0) — this returns the current value without attempting to change it. Android 12+ supports automatic MTU negotiation on connection via TRANSPORT_LE.
Cross-platform frameworks (Flutter, React Native) typically provide an API for requestMTU. In the FlutterBlue Plus library, MTU is set as a connection parameter. In RxAndroidBle — via the requestMtu method. It is recommended to always negotiate the maximum MTU immediately after service discovery, before data transmission begins, to avoid fragmentation at the application level.
OTA (Over-The-Air) firmware updates — the most MTU-demanding scenario in BLE. A typical IoT device firmware size is 100–500 KB. With MTU 23 bytes and a connection interval of 30 ms, transferring 100 KB takes about 2–3 minutes. With MTU 247 bytes and DLE 251 bytes — 20–40 seconds. And with MTU 512 bytes (iOS) — 10–15 seconds.
The OTA update process typically includes: fragmentation of the firmware into packets of size ≤ MTU, sequential transmission via Notify/Write, checksum verification on each packet, and acknowledgment of receipt. If a packet is lost, the device requests retransmission. OTA reliability critically depends on proper selection of MTU and connection interval.
Recommendations for OTA: negotiate the maximum MTU (247–512 bytes), set the connection interval to 7.5–15 ms (if the device supports it), use DLE (Data Length Extension) to increase the physical packet to 251 bytes. For devices with limited buffer memory (e.g., BLE modules based on nRF52), check the maximum MTU in the chip specification.
Frequently Asked Questions
The connection will use the default MTU — 23 bytes. For most IoT scenarios (transmitting sensor readings) this is sufficient. For transferring large amounts of data, the speed will be 5–10 times lower than with a negotiated MTU of 247 bytes.
No, MTU is negotiated once after connection and cannot be changed without disconnecting and reconnecting. Therefore, it is recommended to negotiate MTU immediately after service discovery, before active data transmission begins.
These are historically established empirical maximums for each stack. The actual ATT MTU is still limited to 257 bytes by the specification. Stacks automatically fragment data larger than 251 bytes into multiple packets, so the difference between 512 and 517 is insignificant.
MTU — the size of a GATT request at the ATT level. DLE — the size of a physical packet at the Link Layer. Without DLE, each GATT request (up to 247 bytes) is fragmented into 27-byte packets. With DLE, it is transmitted as a single packet. For maximum speed, both parameters need to be negotiated.
For a fitness tracker transmitting heart rate and step readings, the standard MTU of 23 bytes is sufficient. If you need to transfer workout history (10–50 KB in size), negotiate MTU 247 bytes to speed up data synchronization when connecting to a smartphone.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also