Skip to content
© 2026 The ThingsBoard Authors
Try for free

ThingsBoard Cloud

Choose your data region

Your data stays in the region you choose, for residency and compliance. No credit card required.

Rather run it yourself? Install on your own servers

Java REST Client

The ThingsBoard Java REST Client wraps the Edge REST API and is auto-generated from the OpenAPI spec — every REST endpoint has a typed method. It lets Java applications manage devices, assets, dashboards, customers, and other entities on your Edge node — connect it directly to the Edge node’s local REST endpoint.

Add the Maven dependency for your edition:

<dependencies>
<dependency>
<groupId>org.thingsboard.client</groupId>
<artifactId>thingsboard-ce-client</artifactId>
<version>4.3.1.6</version>
</dependency>
</dependencies>

Add the ThingsBoard repository:

<repositories>
<repository>
<id>thingsboard</id>
<url>https://repo.thingsboard.io/artifactory/libs-release-public</url>
</repository>
</repositories>

The client lives under org.thingsboard.client.* and all models under org.thingsboard.client.model.*. Snippets below assume a ThingsboardClient client from one of the Quickstart or Authentication examples.

End-to-end: authenticate with an API key, create a device, push a telemetry value, then clean up. Drop it into a main() to run.

import org.thingsboard.client.ApiException;
import org.thingsboard.client.ThingsboardClient;
import org.thingsboard.client.model.Device;
public class Quickstart {
public static void main(String[] args) throws ApiException {
ThingsboardClient client = ThingsboardClient.builder()
.url("http://localhost:8080")
.apiKey("YOUR_API_KEY_VALUE")
.build();
Device newDevice = new Device();
newDevice.setName("Quickstart Device");
newDevice.setType("default");
Device savedDevice = client.saveDevice(newDevice, null, null, null, null);
String deviceId = savedDevice.getId().getId().toString();
client.saveEntityTelemetry("DEVICE", deviceId, "ANY", """
{"temperature": 22.4}
""");
System.out.println("Pushed telemetry to " + savedDevice.getName());
client.deleteDevice(deviceId);
}
}

ThingsboardClient.builder() constructs and configures the client. .build() logs in immediately when credentials are supplied and starts a background JWT refresh; the client re-logs in transparently if the refresh token expires.

String url = "http://localhost:8080";
String apiKey = "YOUR_API_KEY_VALUE";
ThingsboardClient client = ThingsboardClient.builder()
.url(url)
.apiKey(apiKey)
.build();
System.out.println("Logged in as " + client.getUser().getEmail());
String url = "http://localhost:8080";
ThingsboardClient client = ThingsboardClient.builder()
.url(url)
.credentials("tenant@thingsboard.org", "tenant")
.build();
System.out.println("Logged in as " + client.getUser().getEmail());

The client retries 429 Too Many Requests responses with exponential backoff by default. Tune via builder methods (defaults shown below), or call .retryOnRateLimit(false) to disable:

ThingsboardClient client = ThingsboardClient.builder()
.url(url)
.apiKey(apiKey)
.maxRetries(3) // default 3
.initialRetryDelayMs(1000) // default 1 s
.maxRetryDelayMs(30_000) // default 30 s
.build();

Every entity type — devices, assets, dashboards, customers, alarms — exposes the same method-naming pattern: save<Entity>, get<Entity>ById, get<Entity>InfoById, delete<Entity>. Required fields differ per type; see the REST API reference for each model. The walkthrough below uses devices. To update an existing entity, mutate the model and call save<Entity> again.

Device newDevice = new Device();
newDevice.setName("Test Device");
newDevice.setType("default");
Device savedDevice = client.saveDevice(newDevice, null, null, null, null);
String deviceId = savedDevice.getId().getId().toString();
Device fetched = client.getDeviceById(deviceId);
System.out.println("Fetched: " + fetched.getName());
client.deleteDevice(deviceId);

Both saveEntityTelemetry(entityType, entityId, scope, body) and saveEntityAttributesV2(entityType, entityId, scope, body) take a JSON string as body, not a typed model — build it with ObjectMapper.writeValueAsString(...) or a text block.

String deviceId = "YOUR_DEVICE_ID";
String body = """
{"temperature": 26.5, "humidity": 87}
""";
client.saveEntityTelemetry("DEVICE", deviceId, "ANY", body);

This example uses an asset with a deviceCount attribute; the same calls work for any entity type — substitute "DEVICE" / deviceId for a device.

String assetId = "YOUR_ASSET_ID";
List<AttributeData> attrs = client.getAttributesByScope(
"ASSET", assetId, "SERVER_SCOPE", "deviceCount", null);
// getValue() returns Object — JSON numbers come back as Number subclasses
long current = attrs.isEmpty() ? 0L : ((Number) attrs.get(0).getValue()).longValue();
long updated = current + 1;
client.saveEntityAttributesV2("ASSET", assetId, "SERVER_SCOPE",
"{\"deviceCount\": %d}".formatted(updated));

Use getTenantDevices (and its equivalents for other entity types) to walk every entity on the tenant. The return type is PageDataDevice — the generated per-entity page type; there’s PageDataAsset, PageDataDashboard, etc.

int page = 0; // pages are zero-indexed
PageDataDevice devices;
do {
devices = client.getTenantDevices(100, page, null, null, null, null);
devices.getData().forEach(System.out::println);
page++;
} while (devices.getHasNext());

For server-side filtering on attribute or telemetry values, use the Entity Data Query API. The example below counts all devices, then counts only the active ones:

import org.thingsboard.client.model.BooleanOperation;
EntityTypeFilter typeFilter = new EntityTypeFilter();
typeFilter.setEntityType(EntityType.DEVICE);
EntityCountQuery totalQuery = new EntityCountQuery();
totalQuery.setEntityFilter(typeFilter);
System.out.println("Total devices: " + client.countEntitiesByQuery(totalQuery));
KeyFilter activeFilter = new KeyFilter();
activeFilter.setKey(new EntityKey().type(EntityKeyType.ATTRIBUTE).key("active"));
activeFilter.setValueType(EntityKeyValueType.BOOLEAN);
BooleanFilterPredicate predicate = new BooleanFilterPredicate();
predicate.setOperation(BooleanOperation.EQUAL);
predicate.setValue(new FilterPredicateValueBoolean().defaultValue(true));
activeFilter.setPredicate(predicate);
EntityCountQuery activeQuery = new EntityCountQuery();
activeQuery.setEntityFilter(typeFilter);
activeQuery.setKeyFilters(List.of(activeFilter));
System.out.println("Active devices: " + client.countEntitiesByQuery(activeQuery));

For a paginated list of matching entities (not just a count), build an EntityDataQuery with an EntityDataPageLink and call client.findEntityDataByQuery(query). See the tb-examples.md catalog for full examples.

Every generated method declares throws ApiException, but ApiException extends RuntimeException — catching is optional. There’s a single exception class with no subclass hierarchy; check e.getCode() to distinguish error kinds.

  • 401 Unauthorized — JWT auth auto-refreshes tokens and re-logs in using stored credentials, so you’ll only see this if credentials become invalid.
  • 429 Too Many Requests — already retried with exponential backoff by default (see Rate-limit handling). You only observe a 429 if retries are exhausted or disabled.
  • 404 Not Found — the common case worth catching explicitly:
import org.thingsboard.client.ApiException;
try {
Device device = client.getDeviceById("nonexistent-id");
} catch (ApiException e) {
if (e.getCode() == 404) {
System.out.println("Device not found");
} else {
System.out.printf("API error %d: %s%n", e.getCode(), e.getResponseBody());
}
}

The tb-examples.md catalog in the repository covers additional entities (assets, dashboards, customers), alarms, relations, and Entity Data Query examples with pagination and sort orders.