GATT — what is it, Generic Attribute Profile, and implementation in Android

Author: IT Sectr Published: 2026-07-14 Reading time: 10 min

GATT (Generic Attribute Profile) is a Bluetooth Low Energy (BLE) profile that defines the data structure and rules for information exchange between BLE devices. GATT is built on top of the Attribute Protocol (ATT) and organizes data into a hierarchy: services, characteristics, and descriptors. According to Bluetooth SIG (2025), the GATT profile is used in 98% of all BLE applications — from fitness trackers to smart locks and medical sensors.

Key Takeaways

  • GATT — a BLE profile that defines data transfer structure through services, characteristics, and descriptors
  • Hierarchy — a service contains characteristics, each characteristic has one value with optional descriptors
  • UUID — each service and characteristic is identified by a 16-bit (standard) or 128-bit (custom) UUID
  • Roles — GATT Server (provides data) and GATT Client (reads/writes data)
  • Android API — BluetoothGatt, BluetoothGattService, BluetoothGattCharacteristic — main classes for working with GATT

What is GATT?

GATT (Generic Attribute Profile) is a Bluetooth Low Energy (BLE) profile that defines how two BLE devices exchange data through the Attribute Protocol (ATT). GATT standardizes the data structure: all data is organized in a “service → characteristic → descriptor” hierarchy. The profile was introduced in the Bluetooth 4.0 specification (2010) alongside BLE and has remained the primary data transfer mechanism for energy-efficient Bluetooth devices ever since. Unlike classic Bluetooth, where data is transmitted through a serial port (SPP), GATT provides structured access to data through read, write, and notification operations.

The Role of GATT in the BLE Stack

The BLE stack consists of several layers: Physical Layer (radio), Link Layer (connection management), L2CAP (logical link control), ATT (Attribute Protocol — attribute access), and GATT (profile based on ATT). GATT is the topmost layer that application developers work with. The underlying ATT provides basic operations: read, write, notify, and indicate attributes. GATT adds semantics: it defines what a service and characteristic are, how they are grouped, and what rules apply when reading and writing them. According to the Bluetooth Core Specification 5.4 (2023), GATT supports up to 65,535 attributes (services + characteristics + descriptors) on a single device.

GATT Architecture: Services, Characteristics, and Descriptors

The GATT hierarchy consists of three levels. A Service is a logical group of characteristics that solves one task (e.g., “Battery Service” or “Heart Rate Service”). A Characteristic is a data unit with a known type: current battery level, sensor reading, switch state. Each characteristic has a value and one or more Descriptors that describe metadata: measurement units, notification settings, value range. UUID (Universally Unique Identifier) uniquely identifies each service and each characteristic.

Types of Characteristic Operations

GATT supports four types of operations for interacting with characteristics. Read — the client requests the current value of a characteristic from the server. Write — the client sends a new value to the server. Notify — the server sends a value to the client without acknowledgment (faster but less reliable). Indicate — the server sends a value with acknowledgment (more reliable but slower). The developer chooses the type based on the scenario: for heart rate sensor readings, Notify is sufficient; for writing smart lock configuration, Write with acknowledgment is needed.

kotlin
// GATT data structure in Android code
data class BleService(
    val uuid: UUID,
    val characteristics: List<BleCharacteristic>
)

data class BleCharacteristic(
    val uuid: UUID,
    val properties: Int, // READ, WRITE, NOTIFY, INDICATE
    val descriptors: List<BleDescriptor>,
    var value: ByteArray?
)

GATT and GAP: Differences Between BLE Profiles

The BLE specification defines two main profiles: GAP (Generic Access Profile) and GATT (Generic Attribute Profile). GAP is responsible for device discovery, connection establishment, and visibility management — it is the “network layer” of BLE. GATT is responsible for data exchange after connection is established — the “application layer.” The developer uses GAP for scanning and connecting to a device, and GATT for reading, writing, and receiving notifications from the connected device.

CharacteristicGAPGATT
PurposeDiscovery and connectionData exchange
RolesCentral / PeripheralClient / Server
ProtocolHCI, Link LayerATT (Attribute Protocol)
PhaseBefore connectionAfter connection
Main classBluetoothAdapterBluetoothGatt

GATT Roles

GATT defines two roles: GATT Server and GATT Client. The Server is a device that provides data (e.g., a fitness tracker sending heart rate readings). The Client is a device that requests data (e.g., a smartphone reading the readings). In most scenarios, the Android app acts as the GATT Client, and the BLE peripheral acts as the GATT Server. However, Android can also be a GATT Server — for example, when an app emulates a BLE device for other devices. The role is determined at the GATT connection setup stage and does not change during the session.

How GATT Communication Works in BLE

The GATT communication process begins after the BLE connection between devices is established. First, the GATT Client discovers services on the GATT Server through Service Discovery — Android performs this automatically upon connection via BluetoothGatt.discoverServices(). After discovery, the client gets a list of available services, characteristics, and descriptors. The client can then read characteristic values (Read), write new values (Write), or subscribe to notifications (Set Notify/Indicate). The GATT Server can send Notify/Indicate to the client at any time after the connection is established.

MTU — Maximum Transmission Unit

By default, the size of a single GATT packet (MTU, Maximum Transmission Unit) in BLE is 23 bytes, of which 3 bytes are the ATT header and 20 bytes are payload. If the application needs to transmit more data (e.g., OTA firmware update), MTU can be increased to 517 bytes via requestMtu() in Android. An increased MTU reduces the number of packets needed to transmit a single data block — instead of 50 small packets, you can send 2 large ones, which lowers power consumption and speeds up transmission. The maximum MTU depends on the BLE version and chip capabilities — Bluetooth 5.0 supports up to 517 bytes, older versions up to 247 bytes.

kotlin
// Subscribe to characteristic notifications
fun enableNotifications(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
    gatt.setCharacteristicNotification(characteristic, true)

    // Enable CCCD (Client Characteristic Configuration Descriptor)
    val cccdUuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
    val descriptor = characteristic.getDescriptor(cccdUuid)
    descriptor?.let {
        it.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
        gatt.writeDescriptor(it)
    }
}

Handling Notifications

To receive notifications from the GATT Server on Android, you need to implement the BluetoothGattCallback.onCharacteristicChanged() callback. In this method, the app receives the updated characteristic value each time the server sends a Notify or Indicate. For real-time data processing, use a buffer and Kotlin coroutines — this prevents blocking the UI thread during intensive notifications (e.g., 100+ heart rate readings per second). For Indications, you must call gatt.sendResponse() on the server side — on the Android client, this is handled automatically.

Implementing GATT on Android

On Android, working with GATT is implemented through the classes of the android.bluetooth package: BluetoothGatt (connection), BluetoothGattService (service), BluetoothGattCharacteristic (characteristic), and BluetoothGattDescriptor (descriptor). Connecting to a BLE device starts by calling BluetoothDevice.connectGatt() — this method returns a BluetoothGatt through which all subsequent operations are performed. All GATT callbacks arrive in BluetoothGattCallback — this is an asynchronous interface that is called on the same thread where BluetoothGatt was created. Important: all GATT operations must be performed sequentially — calling multiple operations simultaneously on the same BluetoothGatt leads to errors.

GATT Connection Lifecycle

Proper management of the GATT connection lifecycle is critical for BLE app stability. After calling connectGatt(), the app waits for the onConnectionStateChange() callback with STATE_CONNECTED state. Then the system automatically starts Service Discovery, after which onServicesDiscovered() is called. Only after this can you perform reading, writing, and subscription. When finished, always call gatt.close() in onDestroy() or onPause() — unclosed GATT connections drain the battery and may block reconnection to the same device on Android.

kotlin
// Connect to BLE device via GATT
private val gattCallback = object : BluetoothGattCallback() {
    override fun onConnectionStateChange(
        gatt: BluetoothGatt, status: Int, newState: Int
    ) {
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            gatt.discoverServices()
        }
    }

    override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
        val service = gatt.getService(UUID.fromString("180D"))
        val characteristic = service?.getCharacteristic(
            UUID.fromString("2A37")
        )
        characteristic?.let { enableNotifications(gatt, it) }
    }
}

Standard GATT Profiles and Services

Bluetooth SIG (Special Interest Group) defines dozens of standard GATT profiles and services with fixed 16-bit UUIDs. The most common ones: Battery Service (UUID 180F, Battery Level characteristic 2A19), Heart Rate service (UUID 180D, Heart Rate Measurement characteristic 2A37), Device Information service (UUID 180A, Manufacturer Name and Serial Number characteristics). Using standard services guarantees compatibility between devices from different manufacturers — any fitness bracelet with a Heart Rate service should work with any Android app that supports it.

Designing Custom GATT Services

If standard services do not cover the task, the developer can create custom GATT services with 128-bit UUIDs. When designing a custom service, you need to: define a logical group of characteristics (e.g., “Lock Control Service”), assign each characteristic the correct properties (Read, Write, Notify), define allowed values and measurement units through the Characteristic Presentation Format descriptor. For complex protocols, it is recommended to include a Command characteristic with Write property and a Status characteristic with Notify — this follows the Command/Status pattern adopted in industrial BLE applications.

kotlin
// Create custom GATT service (Android as GATT Server)
private fun createCustomService(): BluetoothGattService {
    val serviceUuid = UUID.fromString("12345678-1234-5678-1234-56789abcdef0")
    val service = BluetoothGattService(
        serviceUuid, BluetoothGattService.SERVICE_TYPE_PRIMARY
    )

    val charUuid = UUID.fromString("12345678-1234-5678-1234-56789abcdef1")
    val characteristic = BluetoothGattCharacteristic(
        charUuid,
        BluetoothGattCharacteristic.PROPERTY_READ or
        BluetoothGattCharacteristic.PROPERTY_NOTIFY,
        BluetoothGattCharacteristic.PERMISSION_READ
    )
    service.addCharacteristic(characteristic)
    return service
}

Frequently Asked Questions

What is GATT in BLE and why is it needed?

GATT (Generic Attribute Profile) is a Bluetooth Low Energy profile that defines the data exchange structure between devices. It is needed to standardize information access: all BLE devices organize data into services and characteristics, allowing any client to read sensor readings, control devices, and receive notifications.

How is GATT different from GAP?

GAP handles BLE device discovery and connection (scanning, advertising, connection setup). GATT handles data exchange after connection (reading, writing, notifications). GAP works before connection, GATT works after. Both profiles are mandatory for BLE, but perform different functions: GAP is the “network layer,” GATT is the “application layer.”

What GATT services and characteristics exist?

Standard services include Battery Service (180F, battery level), Heart Rate (180D, pulse), Device Information (180A, device data). Each service contains several characteristics with 16-bit UUIDs. Developers can create custom services with 128-bit UUIDs for specific tasks — such as smart lock control or fitness tracker data transmission.

How does GATT work on Android?

On Android, GATT is implemented through BluetoothGatt (connection), BluetoothGattService (service), BluetoothGattCharacteristic (characteristic), and BluetoothGattDescriptor (descriptor). Connection is made via connectGatt(), after which events come through the BluetoothGattCallback: onConnectionStateChange, onServicesDiscovered, onCharacteristicChanged (for notifications). All GATT operations must be performed sequentially.

What is a UUID in GATT?

UUID (Universally Unique Identifier) is a 16-bit or 128-bit identifier that uniquely identifies a service or characteristic in GATT. Standard Bluetooth SIG services use 16-bit UUIDs (e.g., 180D for Heart Rate). Custom developer services use 128-bit UUIDs (e.g., 12345678-1234-5678-1234-56789abcdef0). UUID allows the client to find the required data on the GATT Server.

Summary

  • GATT — a BLE profile for structured data exchange through a hierarchy of services, characteristics, and descriptors, based on the Attribute Protocol (ATT)
  • Data hierarchy — service (logical group), characteristic (specific value with a type), descriptor (metadata — units, notification settings)
  • GATT roles — Server (provides data, typically BLE peripheral) and Client (reads/writes, typically Android smartphone)
  • Operations — Read, Write, Notify (without acknowledgment, faster) and Indicate (with acknowledgment, more reliable); choice depends on the scenario
  • Android API — BluetoothGatt, BluetoothGattCallback, BluetoothGattService, BluetoothGattCharacteristic; sequential async calls via callbacks
  • Standard profiles — Battery Service, Heart Rate, Device Information — ensure compatibility between devices from different manufacturers
  • Custom services — 128-bit UUIDs for unique tasks; designed using the Command/Status pattern with correct properties (Read, Write, Notify, Indicate)

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.

Discuss the project

Read also