{"record":{"id":"f26b9541f872679f","repo":"thingsboard/thingsboard","slug":"perimeter-attribute-not-found-for-zone-with-i","errorCode":null,"errorMessage":"Perimeter attribute '{}' not found for Zone with id: {}","messagePattern":"Perimeter attribute '(.+?)' not found for Zone with id: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingZoneState.java","lineNumber":54,"sourceCode":"\n    private final EntityId zoneId;\n\n    private long ts;\n    private Long version;\n    private PerimeterDefinition perimeterDefinition;\n\n    @EqualsAndHashCode.Exclude\n    private GeofencingPresenceStatus lastPresence;\n\n    public GeofencingZoneState(EntityId zoneId, KvEntry entry) {\n        this.zoneId = zoneId;\n        if (!(entry instanceof AttributeKvEntry attributeKvEntry)) {\n            throw new IllegalArgumentException(\"Invalid perimeter data source for zone with id: \" + zoneId + \". Perimeter definition must be stored as attribute!\");\n        }\n        this.ts = attributeKvEntry.getLastUpdateTs();\n        this.version = attributeKvEntry.getVersion();\n        if (entry.getValueAsString() == null) {\n            throw new IllegalArgumentException(\"Perimeter attribute '\" + entry.getKey() + \"' not found for Zone with id: \" + zoneId);\n        }\n        this.perimeterDefinition = JacksonUtil.fromString(entry.getValueAsString(), PerimeterDefinition.class,\n                \"Invalid perimeter definition format for Zone with id: \" + zoneId + \". Failed to parse attribute '\" + entry.getKey() + \"'\");\n    }\n\n    public GeofencingZoneState(GeofencingZoneProto proto) {\n        this.zoneId = ProtoUtils.fromProto(proto.getZoneId());\n        this.ts = proto.getTs();\n        this.version = proto.getVersion();\n        this.perimeterDefinition = JacksonUtil.fromString(proto.getPerimeterDefinition(), PerimeterDefinition.class);\n        if (proto.hasInside()) {\n            this.lastPresence = proto.getInside() ? INSIDE : OUTSIDE;\n        }\n    }\n\n    public boolean update(GeofencingZoneState newZoneState) {\n        if (newZoneState.getTs() <= this.ts) {\n            return false;","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/thingsboard/thingsboard/blob/45c30e83fa57356840d5f25d894002d94222d524/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingZoneState.java#L36-L72","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-save the perimeter attribute with a valid PerimeterDefinition JSON (circle or polygon with coordinates) for the zone entity","Audit rule-engine actions or integrations that write to the perimeter attribute key and stop them from writing null/empty values","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","Add a guard in the calling code: skip evaluation of zones whose perimeter attribute is absent or null, and log the zone id"],"exampleFix":"// before\nnew GeofencingZoneState(zoneId, entry); // entry.getValueAsString() == null -> throws\n// after\nif (entry.getValueAsString() == null) {\n    log.warn(\"[{}] Perimeter attribute '{}' is empty, skipping zone\", zoneId, entry.getKey());\n    return;\n}\nnew GeofencingZoneState(zoneId, entry);","handlingStrategy":"validation","validationCode":"String v = entry.getValueAsString();\nif (v == null || v.isBlank()) {\n    log.warn(\"[{}] perimeter attribute '{}' empty\", zoneId, entry.getKey());\n    continue; // skip zone\n}","typeGuard":"boolean hasPerimeterValue(KvEntry e) { return e.getValueAsString() != null && !e.getValueAsString().isBlank(); }","tryCatchPattern":"catch (IllegalArgumentException e) if message contains \"not found for Zone\": treat zone as unconfigured, skip and log; alert on volume of skips.","preventionTips":["Guard rule chains that delete/null perimeter attributes","Validate PerimeterDefinition JSON with a schema check before saving the attribute","Monitor for null-valued attributes on zone entities"],"tags":["geofencing","attributes","null-value","data-integrity"],"backgroundTag":null,"analyzedSha":"45c30e83fa57356840d5f25d894002d94222d524","analyzedAt":"2026-08-14T11:45:36.599Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}