floci-io/floci · error · AwsException

ValidationException

ValidationException

Error message

Min and max capacity are required when registering a new scalable target.

What it means

Thrown by ApplicationAutoScalingService.registerScalableTarget() when no existing target exists for the (region, namespace, resourceId, dimension) key and the request does not supply both MinCapacity and MaxCapacity. New registrations need the capacity bounds (AWS requires them on create), while updates of an existing target may omit them and keep prior values. HTTP 400 ValidationException, raised only on the create branch.

Source

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

        this.regionResolver = regionResolver;
        this.cloudWatchMetricsService = cloudWatchMetricsService;
    }

    // ---------------------------------------------------------------- scalable targets

    public ScalableTarget registerScalableTarget(String serviceNamespace, String resourceId,
                                                 String scalableDimension, Integer minCapacity,
                                                 Integer maxCapacity, String roleArn,
                                                 SuspendedState suspendedState,
                                                 Map<String, String> tags, String region) {
        validateTriple(serviceNamespace, resourceId, scalableDimension);

        String key = targetKey(region, serviceNamespace, resourceId, scalableDimension);
        ScalableTarget existing = targets.get(key).orElse(null);

        if (existing == null) {
            if (minCapacity == null || maxCapacity == null) {
                throw new AwsException("ValidationException",
                        "Min and max capacity are required when registering a new scalable target.", 400);
            }
            ScalableTarget target = new ScalableTarget();
            target.setServiceNamespace(serviceNamespace);
            target.setResourceId(resourceId);
            target.setScalableDimension(scalableDimension);
            target.setMinCapacity(minCapacity);
            target.setMaxCapacity(maxCapacity);
            target.setRoleArn(roleArn != null ? roleArn : serviceLinkedRoleArn(serviceNamespace));
            target.setScalableTargetArn(buildScalableTargetArn(region));
            target.setCreationTime(nowEpochSeconds());
            target.setSuspendedState(suspendedState != null ? suspendedState : new SuspendedState());
            if (tags != null) {
                target.setTags(tags);
            }
            targets.put(key, target);
            LOG.infov("RegisterScalableTarget: {0} {1} {2} in {3}",
                    serviceNamespace, resourceId, scalableDimension, region);

View on GitHub (pinned to 62ff490619)

Solutions

  1. Always send both MinCapacity and MaxCapacity (0/0 is valid if you want no capacity) on any call that may create the target.
  2. If you intended an update, first confirm the target exists (DescribeScalableTargets); if it was deregistered, re-register with full parameters.
  3. In IaC, make min_capacity and max_capacity required attributes of the scalable target resource so omissions fail at plan time.
  4. For pure suspension, still include the current capacities — there is no capacity-free register in AWS semantics.

Example fix

// before
RegisterScalableTargetRequest.builder()
    .serviceNamespace("dynamodb")
    .resourceId("table/mytable")
    .scalableDimension("dynamodb:table:ReadCapacityUnits")
    .suspendedState(susp)
    .build();

// after
RegisterScalableTargetRequest.builder()
    .serviceNamespace("dynamodb")
    .resourceId("table/mytable")
    .scalableDimension("dynamodb:table:ReadCapacityUnits")
    .minCapacity(5)
    .maxCapacity(100)
    .suspendedState(susp)
    .build();
Defensive patterns

Strategy: validation

Validate before calling

// Always carry both bounds when a target may be created
Integer min = request.minCapacity() != null ? request.minCapacity() : 0;
Integer max = request.maxCapacity() != null ? request.maxCapacity() : min;
client.registerScalableTarget(RegisterScalableTargetRequest.builder()
    .serviceNamespace(ns).resourceId(rid).scalableDimension(dim)
    .minCapacity(min).maxCapacity(max)
    .build());

Prevention

When it happens

Trigger: Calling RegisterScalableTarget for a brand-new target with MinCapacity or MaxCapacity (or both) null — e.g. an SDK call whose builder only sets one of them, or none because the intent was 'just suspend scaling'. Re-running an update-style call after the target was deleted (now takes the create path and fails).

Common situations: Idempotent deployment scripts that issue the same register call with only SuspendedState on later runs; they work while the target exists but fail after a DeregisterScalableTarget or state reset. Terraform plans that set min but leave max as null. Test teardown between runs removing targets so 'update' calls become 'create' calls.

Related errors


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