thingsboard/thingsboard · error · IllegalArgumentException

Perimeter attribute '{}' not found for Zone with id: {}

Error message

Perimeter attribute '{}' not found for Zone with id: {}

What it means

The zone's perimeter attribute was found as an AttributeKvEntry, but getValueAsString() returned null, meaning the attribute row exists with a null value (or a lookup returned an entry without a value). GeofencingZoneState rejects this because a null perimeter cannot be parsed into PerimeterDefinition. It indicates an empty/corrupt attribute rather than a missing key (a missing key normally yields an empty Optional earlier in the pipeline).

Source

Thrown at application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingZoneState.java:54

    private final EntityId zoneId;

    private long ts;
    private Long version;
    private PerimeterDefinition perimeterDefinition;

    @EqualsAndHashCode.Exclude
    private GeofencingPresenceStatus lastPresence;

    public GeofencingZoneState(EntityId zoneId, KvEntry entry) {
        this.zoneId = zoneId;
        if (!(entry instanceof AttributeKvEntry attributeKvEntry)) {
            throw new IllegalArgumentException("Invalid perimeter data source for zone with id: " + zoneId + ". Perimeter definition must be stored as attribute!");
        }
        this.ts = attributeKvEntry.getLastUpdateTs();
        this.version = attributeKvEntry.getVersion();
        if (entry.getValueAsString() == null) {
            throw new IllegalArgumentException("Perimeter attribute '" + entry.getKey() + "' not found for Zone with id: " + zoneId);
        }
        this.perimeterDefinition = JacksonUtil.fromString(entry.getValueAsString(), PerimeterDefinition.class,
                "Invalid perimeter definition format for Zone with id: " + zoneId + ". Failed to parse attribute '" + entry.getKey() + "'");
    }

    public GeofencingZoneState(GeofencingZoneProto proto) {
        this.zoneId = ProtoUtils.fromProto(proto.getZoneId());
        this.ts = proto.getTs();
        this.version = proto.getVersion();
        this.perimeterDefinition = JacksonUtil.fromString(proto.getPerimeterDefinition(), PerimeterDefinition.class);
        if (proto.hasInside()) {
            this.lastPresence = proto.getInside() ? INSIDE : OUTSIDE;
        }
    }

    public boolean update(GeofencingZoneState newZoneState) {
        if (newZoneState.getTs() <= this.ts) {
            return false;

View on GitHub (pinned to 45c30e83fa)

Solutions

  1. Re-save the perimeter attribute with a valid PerimeterDefinition JSON (circle or polygon with coordinates) for the zone entity
  2. Audit rule-engine actions or integrations that write to the perimeter attribute key and stop them from writing null/empty values
  3. If the DB row is corrupt, fix it in the DB (set a valid JSON string) or delete the row so the zone is treated as unconfigured rather than broken
  4. Add a guard in the calling code: skip evaluation of zones whose perimeter attribute is absent or null, and log the zone id

Example fix

// before
new GeofencingZoneState(zoneId, entry); // entry.getValueAsString() == null -> throws
// after
if (entry.getValueAsString() == null) {
    log.warn("[{}] Perimeter attribute '{}' is empty, skipping zone", zoneId, entry.getKey());
    return;
}
new GeofencingZoneState(zoneId, entry);
Defensive patterns

Strategy: validation

Validate before calling

String v = entry.getValueAsString();
if (v == null || v.isBlank()) {
    log.warn("[{}] perimeter attribute '{}' empty", zoneId, entry.getKey());
    continue; // skip zone
}

Type guard

boolean hasPerimeterValue(KvEntry e) { return e.getValueAsString() != null && !e.getValueAsString().isBlank(); }

Try / catch

catch (IllegalArgumentException e) if message contains "not found for Zone": treat zone as unconfigured, skip and log; alert on volume of skips.

Prevention

When it happens

Trigger: The perimeter server attribute was written with an explicit null value (e.g. via saveAttributes with a Json null, or a rule node that wrote an empty string cleared to null); a DB migration or manual DB edit left the attribute_kvs row with NULL str_value for the perimeter key; deletion races where the entry is fetched while being invalidated.

Common situations: Rule-engine chain that deletes or nulls the perimeter attribute when a device leaves a zone; direct SQL updates to attribute_kvs; partial attribute writes interrupted mid-transaction; test setups that create the attribute key without a value.

Related errors


AI-assisted analysis of thingsboard/thingsboard@45c30e83fa (2026-08-14). Data as JSON: /api/errors/f26b9541f872679f. Report an issue: GitHub.