Skip to content
Stand with Ukraine flag

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-pe-client</artifactId>
<version>4.3.1.2</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, 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, 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.

Entity groups and permissions

Section titled Entity groups and permissions

Create and populate a group

Section titled Create and populate a group
EntityGroup deviceGroup = new EntityGroup();
deviceGroup.setName("Acme Devices");
deviceGroup.setType(EntityGroup.TypeEnum.DEVICE);
EntityGroupInfo savedGroup = client.saveEntityGroup(deviceGroup);
String deviceGroupId = savedGroup.getId().getId().toString();
// addEntitiesToEntityGroup takes a List<String> of raw UUID strings
client.addEntitiesToEntityGroup(deviceGroupId, List.of("YOUR_DEVICE_ID"));
Create a role, look up the customer's auto-generated "All" user group, then bind them via `shareEntityGroupToChildOwnerUserGroup`:
String customerId = "YOUR_CUSTOMER_ID";
String deviceGroupId = "YOUR_DEVICE_GROUP_ID";
Role role = new Role();
role.setName("Acme Device Readers");
role.setType(RoleType.GROUP);
Role savedRole = client.saveRole(role);
String roleId = savedRole.getId().getId().toString();
EntityGroupInfo allUsers = client.getEntityGroupAllByOwnerAndType(
"CUSTOMER", customerId, "USER");
String userGroupId = allUsers.getId().getId().toString();
client.shareEntityGroupToChildOwnerUserGroup(deviceGroupId, userGroupId, roleId);

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.