What the MTU measures
MTU stands for maximum transmission unit. On a Bluetooth LE connection, the number that matters to apps is the ATT_MTU. The Attribute Protocol chapter of the Core Specification defines it as the maximum size of any packet sent between a client and a server.
Every read, write and notification in GATT travels as one Attribute Protocol packet, so the ATT_MTU caps how much data any single operation can move. The GATT chapter sets the default ATT_MTU for LE at 23 bytes, and both client and server must support at least that.
Where the bytes go
Part of each packet is protocol overhead. The ATT chapter gives the formats:
- Notification. One byte of opcode and two bytes of attribute handle, then the value. The value can be up to ATT_MTU minus 3 bytes. Anything longer is cut off, and only the first ATT_MTU minus 3 bytes are sent.
- Write without response. The same layout: up to ATT_MTU minus 3 bytes of value.
- Read response. One byte of opcode, then up to ATT_MTU minus 1 bytes of value.
At the default of 23, that leaves 20 bytes for a notification or a write without response.
How devices agree on a bigger MTU
A client that can handle more than the default can run the Exchange MTU procedure. The GATT chapter describes it: the client sends the largest packet it can receive, the server replies with the largest it can receive, and both then use the smaller of the two. The procedure may run only once per connection. If either side reports a value below the default, the ATT_MTU stays at the default.
Two related numbers come up often. The ATT chapter caps a single attribute value at 512 bytes, and from Android 14 the Android stack asks for 517 bytes, as described below.
The radio does not have to carry the whole packet in one go. The L2CAP chapter allows lower layers to fragment a packet to fit their own size limits, and the receiver recombines the fragments. A large ATT_MTU therefore works even if each radio packet is smaller.
What the platforms do
- Android. An app calls
BluetoothGatt.requestMtu()and learns the result inonMtuChanged. The reference warns that for a write without response, data longer than the MTU is truncated. From Android 14, the stack requests 517 bytes the first time any GATT client asks and ignores later requests on that connection, and the final value is the smaller of 517 and what the remote device offers. - iOS and macOS. Apps read from Core Bluetooth how much fits.
maximumWriteValueLength(for:)on a peripheral gives the maximum bytes for a single write of a given type. On the peripheral side, a central’smaximumUpdateValueLengthcaps notifications, andupdateValue(_:for:onSubscribedCentrals:)truncates anything longer.
Always use the value the platform reports. The same phone can end up with a different MTU on each connection, because the result depends on what each peer supports.
Why it limits message size
A chat message, a JSON document or a sensor batch will often be longer than one packet, even with a negotiated MTU. GATT has procedures for long attribute values, prepared writes and blob reads, but they are bounded by the 512-byte attribute limit and add round trips. A common approach is for the app to do its own framing instead:
- Split the message into chunks that fit the reported write or notification size.
- Put a small header on each chunk, such as a message ID, a sequence number and a final-chunk flag.
- Send the chunks with writes in one direction and notifications in the other.
- Reassemble on the receiving side, and discard or re-request incomplete messages.
Three practical points follow. First, size chunks from the negotiated value, not a constant, and fall back to the default when the peer reports something too small. Second, a larger MTU means fewer headers per byte of data. Third, notifications and writes without response get no confirmation from the receiver, so anything that must arrive needs an acknowledgement at the application level.
The same chunking idea scales up. Files and other large payloads crossing several devices are broken into larger application-level pieces, then each piece is split again to fit each BLE link; how large files move across a mesh covers that layer.
An SDK that runs its own protocol over BLE has to handle all of this. The Offline Protocol mesh SDK tracks MTU agreement between peers and exposes an undersizedMtuReports diagnostic counter for when a peer reports an MTU too small to carry a fragment header and a conservative default is used.