Of course! Here is a comprehensive guide to using Bluetooth in Java, covering both the classic Bluetooth (RFCOMM) and the newer Bluetooth Low Energy (BLE) APIs.

Key Concepts
First, let's understand the two main ways to use Bluetooth in Java:
- Classic Bluetooth (RFCOMM): This is the traditional, well-established Bluetooth. It's great for streaming audio (A2DP), file transfer (OBEX), and creating simple, point-to-point connections (like a serial port). It's typically used for devices like headsets, speakers, and older printers.
- Bluetooth Low Energy (BLE / Bluetooth Smart): This is a modern, low-power version of Bluetooth. It's designed for small data transmissions and is used by fitness trackers, smart home sensors, heart rate monitors, and beacons. It's more complex but much more power-efficient.
The available Java API depends on the platform you're targeting:
- Java SE (Desktop - Windows, macOS, Linux): You can use the JSR-82 (Java API for Bluetooth) specification. This is part of the standard Java library (
javax.bluetooth), but it's often considered legacy and can be tricky to set up. - Android: Google provides its own, much more powerful and modern
android.bluetoothandandroid.bluetooth.lepackages. This is the recommended approach for Android development. - Java ME (Embedded/Mobile): Also uses JSR-82, similar to Java SE.
Part 1: Classic Bluetooth (RFCOMM) using JSR-82 (Java SE / Android Legacy)
This example demonstrates how to create a simple server that waits for a client to connect over an RFCOMM channel.
Step 1: Check if Bluetooth is Available and Enabled
import javax.bluetooth.*;
// This check is crucial. The LocalDevice might not be available.
LocalDevice localDevice = LocalDevice.getLocalDevice();
if (localDevice == null) {
System.out.println("Bluetooth is not available.");
// Exit or handle error
} else {
System.out.println("Bluetooth is available.");
System.out.println("Address: " + localDevice.getBluetoothAddress());
System.out.println("Name: " + localDevice.getFriendlyName());
}
Step 2: Create a Server Connection
The server needs to listen for incoming connections on a specific Service UUID. A UUID is a unique identifier for your service. You can generate your own or use a standard one.

import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class BluetoothServer implements Runnable {
// A standard UUID for a serial port service. You can generate your own.
private static final String SERVER_UUID = "0000110100001000800000805F9B34FB";
@Override
public void run() {
// The URL for the server connection
String connectionString = "btspp://localhost:" + SERVER_UUID + ";name=BluetoothServer";
try (StreamConnectionNotifier streamConnNotifier = (StreamConnectionNotifier) Connector.open(connectionString)) {
System.out.println("Server started. Waiting for a client to connect...");
// Wait for a client to connect
StreamConnection connection = streamConnNotifier.acceptAndOpen();
System.out.println("Client connected!");
// Get input and output streams
InputStream inputStream = connection.openInputStream();
OutputStream outputStream = connection.openOutputStream();
// Read data from the client
byte[] buffer = new byte[1024];
int bytesRead = inputStream.read(buffer);
String receivedMessage = new String(buffer, 0, bytesRead);
System.out.println("Received: " + receivedMessage);
// Send a response back to the client
String responseMessage = "Hello from the server!";
outputStream.write(responseMessage.getBytes());
System.out.println("Sent: " + responseMessage);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// Note: On Java SE, you need to enable Bluetooth visibility first.
// On Android, you would use the BluetoothAdapter.
new Thread(new BluetoothServer()).start();
}
}
Step 3: Create a Client Connection
The client needs to know the server's Bluetooth address and the same Service UUID to connect.
import javax.bluetooth.*;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class BluetoothClient {
private static final String SERVER_UUID = "0000110100001000800000805F9B34FB";
public static void main(String[] args) {
// Replace with the actual address of your server device
String serverAddress = "00:11:22:33:44:55";
String connectionString = "btspp://" + serverAddress + ":" + SERVER_UUID + ";authenticate=true;encrypt=false";
try (StreamConnection connection = (StreamConnection) Connector.open(connectionString)) {
System.out.println("Connected to server.");
// Get input and output streams
OutputStream outputStream = connection.openOutputStream();
InputStream inputStream = connection.openInputStream();
// Send data to the server
String message = "Hello from the client!";
outputStream.write(message.getBytes());
System.out.println("Sent: " + message);
// Read the server's response
byte[] buffer = new byte[1024];
int bytesRead = inputStream.read(buffer);
String response = new String(buffer, 0, bytesRead);
System.out.println("Received: " + response);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Part 2: Bluetooth Low Energy (BLE) on Android
Modern Android development for BLE uses the android.bluetooth.le package. The process is more involved and revolves around a central (your app) and peripherals (the BLE devices).
Key Android Components:
BluetoothAdapter: Represents the device's Bluetooth adapter.BluetoothManager: A system service that provides access to theBluetoothAdapter.BluetoothGatt: Represents a GATT (Generic Attribute Profile) connection to a BLE device.BluetoothGattCallback: A callback for GATT client events (like connection state changes, data read, data received).BluetoothGattService: A collection of data and behaviors (services) offered by a BLE device.BluetoothGattCharacteristic: A single data point within a service (e.g., a temperature reading).ScanCallback: Used for scanning for nearby BLE devices.
Example: Scanning for a Device and Connecting
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.ParcelUuid;
import android.util.Log;
import androidx.annotation.RequiresApi;
import androidx.core.app.ActivityCompat;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class BleScanner {
private static final String TAG = "BleScanner";
private BluetoothLeScanner scanner;
private ScanCallback scanCallback;
// The UUID of the service you are looking for
private static final UUID TARGET_SERVICE_UUID = UUID.fromString("0000180d-0000-1000-8000-00805f9b34fb"); // Example: Heart Rate Service
public void startScan(Context context) {
BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager == null) {
Log.e(TAG, "Unable to get BluetoothManager.");
return;
}
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
if (bluetoothAdapter == null) {
Log.e(TAG, "Unable to get BluetoothAdapter.");
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.e(TAG, "Location permission is not granted.");
// You should request the permission here
return;
}
}
scanner = bluetoothAdapter.getBluetoothLeScanner();
if (scanner == null) {
Log.e(TAG, "BluetoothLeScanner is null. Is Bluetooth on?");
return;
}
// Create a scan filter for the specific service
ScanFilter filter = new Scan.Builder()
.setServiceUuid(new ParcelUuid(TARGET_SERVICE_UUID))
.build();
// Scan settings
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) // High power, short scans
.build();
scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
BluetoothDevice device = result.getDevice();
Log.i(TAG, "Found device: " + device.getName() + " " + device.getAddress());
// Stop scanning once you find a device
stopScan();
// Now you can connect to the device
connectToDevice(device);
}
@Override
public void onScanFailed(int errorCode) {
Log.e(TAG, "Scan failed with error code: " + errorCode);
}
};
Log.i(TAG, "Starting BLE scan for service: " + TARGET_SERVICE_UUID);
scanner.startScan(List.of(filter), settings, scanCallback);
}
public void stopScan() {
if (scanner != null && scanCallback != null) {
scanner.stopScan(scanCallback);
Log.i(TAG, "Scan stopped.");
}
}
private void connectToDevice(BluetoothDevice device) {
// This is where you would initiate the GATT connection
// You need a BluetoothGattCallback to handle the connection process
Log.i(TAG, "Connecting to " + device.getAddress());
// device.connectGatt(context, false, gattCallback);
}
}
Example: Handling a GATT Connection
You would create a class that extends BluetoothGattCallback to handle events after connecting.
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.util.Log;
public class MyGattCallback extends BluetoothGattCallback {
private static final String TAG = "MyGattCallback";
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (newState == BluetoothGatt.STATE_CONNECTED) {
Log.i(TAG, "Connected to GATT server.");
// Discover services after connecting
gatt.discoverServices();
} else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
Log.i(TAG, "Disconnected from GATT server.");
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.i(TAG, "Services discovered.");
// Find the characteristic you are interested in
BluetoothGattCharacteristic characteristic = gatt.getService(TARGET_SERVICE_UUID)
.getCharacteristic(UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb")); // Heart Rate Measurement Char
// Enable notifications for this characteristic
gatt.setCharacteristicNotification(characteristic, true);
// Write the client characteristic configuration descriptor (CCCD)
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
gatt.writeDescriptor(descriptor);
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
// This is where you receive the data that was being notified
byte[] value = characteristic.getValue();
Log.i(TAG, "Received characteristic update: " + bytesToHex(value));
}
// Helper method to convert bytes to hex string for logging
private String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X ", b));
}
return sb.toString();
}
}
Summary and Best Practices
| Feature | Classic Bluetooth (JSR-82) | Bluetooth Low Energy (Android) |
|---|---|---|
| Use Case | Audio, file transfer, simple serial-like connections | IoT, sensors, beacons, health devices |
| Complexity | Relatively simple for point-to-point | More complex (client-server model with services/characteristics) |
| Power Usage | Higher | Very Low |
| Java API | javax.bluetooth (JSR-82) |
android.bluetooth.le |
| Platform | Java SE, Android Legacy | Modern Android (API 18+) |
| Permissions | Varies by OS | BLUETOOTH, BLUETOOTH_ADMIN, ACCESS_FINE_LOCATION (for scanning) |
Key Takeaways:

- Choose the Right Technology: Use Classic Bluetooth for audio and legacy devices. Use BLE for new, battery-powered IoT devices.
- Permissions are Crucial: On mobile platforms, especially Android, you must request the necessary Bluetooth and location permissions from the user.
- Asynchronous Operations: Both JSR-82 and Android BLE APIs are asynchronous. Use callbacks or listeners to handle events like connection, data reception, and errors.
- UUIDs are Everything: Services and characteristics are identified by UUIDs. You must know the UUIDs of the services you want to interact with on the target device.
- Error Handling: Always check for nulls, handle
IOExceptions, and listen for callback failure codes to make your application robust.
