apache/iceberg · error · AnalysisException

Duplicate parameter names

Error message

Duplicate parameter names: ${duplicateParamNames.mkString("[", ",", "]")}

What it means

Thrown by ResolveProcedures when a procedure's parameter list contains two or more parameters sharing the same name. Duplicate names make named-argument invocation ambiguous, so procedure resolution rejects it at analysis time.

Solutions

  1. Rename one of the duplicated procedure parameters so every parameter name is unique
  2. If parameters are generated from schema fields, deduplicate or prefix field names before building ProcedureParameter instances

Example fix

// before
ProcedureParameter.required("table"), ProcedureParameter.optional("table", StringType)
// after
ProcedureParameter.required("table"), ProcedureParameter.optional("where", StringType)
Defensive patterns

Strategy: validation

Validate before calling

val names = params.map(_.name)
require(names.distinct.size == names.size, s"duplicate param names: ${names.diff(names.distinct).mkString(",")}")

Try / catch

try { spark.sql(callSql) } catch { case e: AnalysisException if e.getMessage.startsWith("Duplicate parameter names") => // fix procedure definition }

Prevention

When it happens

Trigger: Registering/defining a procedure whose ProcedureParameter list has repeated names, then resolving a CALL against it.

Common situations: Programmatic procedure definitions where parameters are generated from a schema and two columns share a (case-insensitively equal) name; hand-rolled procedure catalogs with copy-pasted parameter declarations.

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 apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/f396c0086e431301. Report an issue: GitHub.

Appendix: source

Thrown at spark/v3.5/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveProcedures.scala:63

    case CallStatement(CatalogAndIdentifier(catalog, ident), args) =>
      val procedure = catalog.asProcedureCatalog.loadProcedure(ident)

      val params = procedure.parameters
      val normalizedParams = normalizeParams(params)
      validateParams(normalizedParams)

      val normalizedArgs = normalizeArgs(args)
      Call(procedure, args = buildArgExprs(normalizedParams, normalizedArgs).toSeq)
  }

  private def validateParams(params: Seq[ProcedureParameter]): Unit = {
    // should not be any duplicate param names
    val duplicateParamNames = params.groupBy(_.name).collect {
      case (name, matchingParams) if matchingParams.length > 1 => name
    }

    if (duplicateParamNames.nonEmpty) {
      throw new AnalysisException(
        s"Duplicate parameter names: ${duplicateParamNames.mkString("[", ",", "]")}")
    }

    // optional params should be at the end
    params.sliding(2).foreach {
      case Seq(previousParam, currentParam) if !previousParam.required && currentParam.required =>
        throw new AnalysisException(
          s"Optional parameters must be after required ones but $currentParam is after $previousParam")
      case _ =>
    }
  }

  private def buildArgExprs(
      params: Seq[ProcedureParameter],
      args: Seq[CallArgument]): Seq[Expression] = {

    // build a map of declared parameter names to their positions
    val nameToPositionMap = params.map(_.name).zipWithIndex.toMap

View on GitHub (pinned to 86d9c8fc54)