floci-io/floci · error · AwsException

ObjectNotFoundException

ObjectNotFoundException

Error message

No scalable target found for service namespace: {serviceNamespace}, resource ID: {resourceId}, scalable dimension: {scalableDimension}

What it means

Thrown by DeregisterScalableTarget when no target exists for the exact (namespace, resourceId, dimension) triple in that region. Per AWS semantics mirrored here, deregistration also deletes attached scaling policies and their CloudWatch alarms — but only after the existence check passes. Note the code uses HTTP 400 with code ObjectNotFoundException, which matches AWS's Application Auto Scaling behavior of signaling object-not-found as a 400-class ValidationException-family error rather than 404.

Source

Thrown at src/main/java/io/github/hectorvent/floci/services/applicationautoscaling/ApplicationAutoScalingService.java:189

        String prefix = region + "::" + serviceNamespace + "::";
        return targets.scan(k -> k.startsWith(prefix)).stream()
                .filter(t -> resourceIds == null || resourceIds.isEmpty() || resourceIds.contains(t.getResourceId()))
                .filter(t -> scalableDimension == null || scalableDimension.equals(t.getScalableDimension()))
                .sorted(Comparator.comparing(ScalableTarget::getResourceId)
                        .thenComparing(ScalableTarget::getScalableDimension))
                .toList();
    }

    /**
     * Deregisters a scalable target and, per AWS semantics, deletes every scaling policy
     * attached to it along with the CloudWatch alarms those policies own.
     */
    public void deregisterScalableTarget(String serviceNamespace, String resourceId,
                                         String scalableDimension, String region) {
        validateTriple(serviceNamespace, resourceId, scalableDimension);
        String key = targetKey(region, serviceNamespace, resourceId, scalableDimension);
        if (targets.get(key).isEmpty()) {
            throw new AwsException("ObjectNotFoundException",
                    "No scalable target found for service namespace: " + serviceNamespace
                            + ", resource ID: " + resourceId
                            + ", scalable dimension: " + scalableDimension, 400);
        }
        for (ScalingPolicy policy : findPolicies(region, serviceNamespace, resourceId, scalableDimension)) {
            deleteAlarms(policy, region);
            policies.delete(policyKey(region, serviceNamespace, resourceId, scalableDimension, policy.getPolicyName()));
        }
        targets.delete(key);
        LOG.infov("DeregisterScalableTarget: {0} {1} {2} in {3}",
                serviceNamespace, resourceId, scalableDimension, region);
    }

    // ---------------------------------------------------------------- scaling policies

    public ScalingPolicy putScalingPolicy(String policyName, String policyType, String serviceNamespace,
                                          String resourceId, String scalableDimension,
                                          TargetTrackingConfiguration targetTracking,

View on GitHub (pinned to 62ff490619)

Solutions

  1. Describe first and deregister only on hit: DescribeScalableTargets with the same triple, then DeregisterScalableTarget if a target is returned.
  2. Verify resourceId and dimension byte-for-byte against the original RegisterScalableTarget call (check casing and the service prefix).
  3. Confirm the region of the call matches the region where the target was registered.
  4. In teardown code, treat ObjectNotFoundException as the benign 'already gone' outcome and continue.

Example fix

// before
client.deregisterScalableTarget(DeregisterScalableTargetRequest.builder()
    .serviceNamespace(ns).resourceId(rid).scalableDimension(dim).build());

// after
boolean exists = !client.describeScalableTargets(DescribeScalableTargetsRequest.builder()
        .serviceNamespace(ns).resourceIds(rid).scalableDimension(dim).build()
        .scalableTargets().isEmpty();
if (exists) {
    client.deregisterScalableTarget(DeregisterScalableTargetRequest.builder()
        .serviceNamespace(ns).resourceId(rid).scalableDimension(dim).build());
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Existence gate before deregister
boolean exists = !client.describeScalableTargets(DescribeScalableTargetsRequest.builder()
        .serviceNamespace(ns).resourceIds(rid).scalableDimension(dim).build()
        .scalableTargets().isEmpty();
if (exists) {
    client.deregisterScalableTarget(DeregisterScalableTargetRequest.builder()
        .serviceNamespace(ns).resourceId(rid).scalableDimension(dim).build());
}

Try / catch

catch (AwsException e) {
    if ("ObjectNotFoundException".equals(e.getCode())) {
        // already gone — benign for cleanup flows
        log.debug("Scalable target already deregistered: {} {} {}", ns, rid, dim);
        return;
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling DeregisterScalableTarget for a target already deregistered, a resourceId with a typo or different casing (e.g. 'table/MyTable' vs 'table/mytable'), a dimension string mismatch (service prefix differences like 'dynamodb:table:...' vs 'dynamoDB:table:...'), or the wrong region. Any of these make the key lookup miss.

Common situations: Cleanup scripts run twice. Drift between regions (target registered in us-east-1, cleanup pointed at us-west-2). Resource ids rebuilt by string concat with subtle differences from what was registered. Tests that deregister in teardown but never registered in setup.

Related errors


AI-assisted analysis of floci-io/floci@62ff490619 (2026-08-14). Data as JSON: /api/errors/fa6dcd5933ffe661. Report an issue: GitHub.