Usage
Build and use the SimpleJavaBLE Java binding.
SimpleJavaBLE exposes SimpleBLE to JVM applications through a Java API backed by the native SimpleBLE library.
Requirements
- JDK 17 or newer.
- CMake 3.21 or newer.
- The platform requirements from Installation.
- A native library artifact for the platform and architecture where the JVM runs.
Install from Maven Central
Add SimpleJavaBLE to your Gradle dependencies:
dependencies {
implementation("org.simpleble:simplejavable:<version>")
}The Maven artifact bundles the supported Windows, macOS, and Linux native libraries in the JAR.
Build from source
From a local checkout:
cd simplejavable/java
../../utils/gradle/gradlew build -PbuildFromCMakeThis builds the Java classes and asks Gradle to build the native library through the CMake project under simplejavable/cpp.
The generated JAR is written under:
simplejavable/java/build/libsNative library loading
SimpleJavaBLE loads the simplejavable native library at runtime. The Gradle build can package native libraries into the JAR, or you can provide native libraries separately and make them visible to the JVM.
If you already built native libraries elsewhere, pass their location to Gradle:
../../utils/gradle/gradlew build -PnativeLibPath=/path/to/native/libsFirst scan
import org.simplejavable.Adapter;
import org.simplejavable.Peripheral;
import java.util.List;
public class ScanExample {
public static void main(String[] args) throws Exception {
List<Adapter> adapters = Adapter.getAdapters();
if (adapters.isEmpty()) {
System.err.println("No Bluetooth adapters found.");
return;
}
Adapter adapter = adapters.get(0);
adapter.scanFor(5000);
for (Peripheral peripheral : adapter.scanGetResults()) {
System.out.println(peripheral.getIdentifier() + " [" + peripheral.getAddress() + "]");
}
}
}For a fuller walkthrough, see Quickstart and the SimpleJavaBLE Examples.
Choose an adapter
Adapter.getAdapters() returns the adapters of every active backend. To choose
a backend, enumerate backends and use their adapters:
for (Backend backend : Backend.getBackends()) {
System.out.println(backend.getIdentifier() + ": Bluetooth enabled = " + backend.isBluetoothEnabled());
for (Adapter adapter : backend.getAdapters()) {
System.out.println(" " + adapter.getIdentifier() + " [" + adapter.getAddress() + "]");
}
}Backend identifiers are SimpleBluez, WinRT, CoreBluetooth, and Dongl.
Every enumeration returns the same Adapter object for the same adapter, whether
it comes from Adapter.getAdapters() or from a backend, so event listeners stay
attached across enumerations.
Dongl
The Dongl USB adapter has its own backend, which is on by default.
Backend.getDongl() returns it:
List<Adapter> dongls = Backend.getDongl().getAdapters();With the backend enabled, Adapter.getAdapters() also includes Dongl adapters.
Each Dongl is a separate adapter, and getAddress() tells them apart.
For pairing through a Dongl, register the callbacks your device needs on the peripheral before connecting, since pairing can start as soon as the link is up:
Advanced.Dongl.setNumericComparisonCallback(peripheral, passkey -> {
System.out.print("Does the device show " + passkey + "? [y/N] ");
return new Scanner(System.in).nextLine().trim().equals("y");
});
peripheral.connect();setPasskeyRequestCallback takes a Supplier<String> that returns the six digits
the device shows, or null to reject. setPasskeyDisplayCallback takes a
Consumer<String> that shows a passkey for the user to type on the device. These
callbacks run on a worker thread while connect() blocks, so they can wait for the
user's answer. A request without a callback, or whose callback throws, is rejected.
Pass null to unregister a callback. Peripherals of other backends throw
UnsupportedOperationException.
Callbacks and threading
Event listeners, notification callbacks, read handlers, and pairing callbacks run on native threads. Return promptly and hand longer work to your own executor. Exceptions thrown by a callback are logged. A read handler that throws serves the stored value instead.
Errors
Native failures throw RuntimeException with the native message. Operations that
the backend or platform does not support throw UnsupportedOperationException.
Host a GATT service
Adapter.createLocalPeripheral() creates a local GATT host. Configure services,
characteristics, and advertised UUIDs before calling start():
BluetoothUUID serviceUuid = new BluetoothUUID("12345678-1234-5678-1234-56789abcdef0");
LocalPeripheral host = adapter.createLocalPeripheral();
host.addAdvertisedService(serviceUuid);
LocalService service = host.addService(serviceUuid);
LocalCharacteristic characteristic = service.addCharacteristic(
new BluetoothUUID("12345678-1234-5678-1234-56789abcdef1"),
LocalCharacteristicCapability.READ, LocalCharacteristicCapability.WRITE_REQUEST,
LocalCharacteristicCapability.NOTIFY);
characteristic.setValue("ready".getBytes(StandardCharsets.UTF_8));
characteristic.setEventListener(new LocalCharacteristic.EventListener() {
@Override
public void onWrite(byte[] value) {
characteristic.setValue(value); // Echo to subscribers.
}
});
host.start();
// ...
host.stop();setValue() stores the bytes and publishes them to notify or indicate subscribers
on a best-effort basis. onWrite runs after a client write has updated the stored
value. onSubscribed reports the first subscriber and onUnsubscribed the last.
LocalPeripheral.EventListener reports clients connecting and disconnecting;
CoreBluetooth does not report connections, so on macOS these events follow a
client's first and last subscription instead.
setReadHandler() takes a Supplier<byte[]> that computes each read on demand. It
runs synchronously on a native thread, so return quickly. If it returns null or
throws, the client reads the stored value. Pass null to serve the stored value
again.
setAdvertisedManufacturerData(companyId, data) adds manufacturer specific data to
the advertisement, and setConnectable(false) makes it non-connectable, for example
to advertise a beacon. Configure both
before start().
isStarted() and isAdvertising() report the host state. The configuration cannot
change while started. After stop(), removeAllServices() clears the layout so you
can rebuild it.
Platform-specific APIs
Advanced holds APIs for a single platform. They throw
UnsupportedOperationException on other platforms.
Advanced.Linux.setAdvertisementLocalName()andAdvanced.MacOS.setAdvertisementLocalName()set the name in a local peripheral's advertisement. Call them beforestart(), and passnullto remove the name.Advanced.MacOS.retrieveCachedPeripherals()returns peripherals that CoreBluetooth already knows, by identifier, without scanning.
Configuration
Config holds process-wide backend options. Set them before the first enumeration.
For example, Config.SimpleBluez.setConnectionTimeout(Duration.ofSeconds(5))
changes how long a Linux connection attempt waits, and Config.resetAll() restores
the defaults.
