microsoft/aspire · error · IllegalArgumentException

Cannot use null in a reference expression

Error message

Cannot use null in a reference expression

What it means

DistributedApplicationPipeline.AddStep enforces that pipeline step names are unique. Before creating the PipelineStep it scans _steps for the given name and throws an InvalidOperationException if a step with that name already exists, because steps are identified and ordered by name (dependency edges reference names).

Solutions

  1. Use a unique name per AddStep call — prefix or suffix with the resource name in loops (e.g. $"build-{resource.Name}").
  2. Check for an existing step before adding: skip registration if pipeline already contains the name, or update the existing step instead.
  3. If an integration adds the duplicate, gate it behind a flag or add it only once at the app level.
  4. Rename one of the colliding steps so dependency references (dependsOn/requiredBy) still point at the intended step.

Example fix

// before
pipeline.AddStep("build", context => ...);
pipeline.AddStep("build", context => ...); // throws
// after
pipeline.AddStep("build", context => ...);
pipeline.AddStep("test", context => ...);  // unique names
Defensive patterns

Strategy: validation

Validate before calling

// C#: assert uniqueness before calling AddStep
var names = new HashSet<string>(StringComparer.Ordinal);
void SafeAddStep(DistributedApplicationPipeline pipeline, string name, Func<PipelineStepContext, Task> action)
{
    if (!names.Add(name)) throw new InvalidOperationException($"Duplicate pipeline step '{name}'");
    pipeline.AddStep(name, action);
}

Try / catch

try
{
    pipeline.AddStep(name, action);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("has already been added to the pipeline"))
{
    logger.LogWarning(ex, "Step '{StepName}' already registered; skipping.", name);
}

Prevention

When it happens

Trigger: Calling AddStep twice with the same name value on one pipeline instance; a custom pipeline extension adding a well-known step (e.g. 'build' or 'publish') that the app already registered; tests like ExecuteAsync_WithComplexDependencyGraph_ExecutesInCorrectOrder that exercise AddStep collisions.

Common situations: Registering the same custom step in both a global callback and per-resource callbacks; library integrations that unconditionally AddStep on every builder call while the user also adds a step of that name; loop-based step registration without name suffixes.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/f4318a833d8d5cd2. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting.CodeGeneration.Java/Resources/Base.java:255

        reqArgs.put("context", AspireClient.serializeValue(handle));
        if (cancellationToken != null) {
            reqArgs.put("cancellationToken", cancellationToken);
        }

        return (String) client.invokeCapability("Aspire.Hosting.ApplicationModel/getValue", reqArgs);
    }

    public static ReferenceExpression refExpr(String format, Object... valueProviders) {
        return new ReferenceExpression(format, valueProviders);
    }

    public static ReferenceExpression createConditional(Object condition, String matchValue, ReferenceExpression whenTrue, ReferenceExpression whenFalse) {
        return new ReferenceExpression(condition, matchValue, whenTrue, whenFalse);
    }

    private static Object extractValueProvider(Object value) {
        if (value == null) {
            throw new IllegalArgumentException("Cannot use null in a reference expression");
        }

        if (value instanceof String || value instanceof Number || value instanceof Boolean) {
            return value;
        }

        return AspireClient.serializeValue(value);
    }
}

/**
 * AspireList is a handle-backed list with lazy handle resolution.
 */
class AspireList<T> extends HandleWrapperBase {
    private final String getterCapabilityId;
    private Handle resolvedHandle;

    AspireList(Handle handle, AspireClient client) {

View on GitHub (pinned to 25830f84bd)