Java REST Client
The ThingsBoard Java REST Client wraps the REST API and is auto-generated from the OpenAPI spec — every REST endpoint has a typed method. Edition-specific artifacts ensure type-safe access to the right endpoints for your tenant.
Installation
Section titled “Installation”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.
Quickstart
Section titled “Quickstart”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); }}Authentication
Section titled “Authentication”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.
API key (recommended)
Section titled “API key (recommended)”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());Username and password (JWT)
Section titled “Username and password (JWT)”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());Rate-limit handling
Section titled “Rate-limit handling”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();Working with entities
Section titled “Working with entities”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);Telemetry and attributes
Section titled “Telemetry and attributes”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.
Push telemetry
Section titled “Push telemetry”String deviceId = "YOUR_DEVICE_ID";String body = """ {"temperature": 26.5, "humidity": 87} """;client.saveEntityTelemetry("DEVICE", deviceId, "ANY", body);Read and write attributes
Section titled “Read and write attributes”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 subclasseslong current = attrs.isEmpty() ? 0L : ((Number) attrs.get(0).getValue()).longValue();long updated = current + 1;
client.saveEntityAttributesV2("ASSET", assetId, "SERVER_SCOPE", "{\"deviceCount\": %d}".formatted(updated));Listing and querying entities
Section titled “Listing and querying entities”Paginated tenant list
Section titled “Paginated tenant list”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-indexedPageDataDevice devices;do { devices = client.getTenantDevices(100, page, null, null, null, null); devices.getData().forEach(System.out::println); page++;} while (devices.getHasNext());Filtered query with Entity Data Query API
Section titled “Filtered query with Entity Data Query API”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 permissionsCreate and populate a group
Section titled Create and populate a groupEntityGroup 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 stringsclient.addEntitiesToEntityGroup(deviceGroupId, List.of("YOUR_DEVICE_ID"));Share with a customer
Section titled Share with a customerString 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);Error handling
Section titled “Error handling”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()); }}More examples
Section titled “More examples”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.