theonedev/onedev · error · ExplicitException

Script should return either a Map or a List

Error message

Script should return either a Map or a List

What it means

ScriptingChoices.getChoices executes a script to produce choice options and expects the script result to be a Map (value->label) or a List of strings. Any other result type throws an ExplicitException. This enforces a strict return contract for dynamic choice providers.

Source

Thrown at server-core/src/main/java/io/onedev/server/buildspecmodel/inputspec/choiceinput/choiceprovider/ScriptingChoices.java:57

	}

	@SuppressWarnings("unchecked")
	@Override
	public Map<String, String> getChoices(boolean allPossible) {
		Map<String, Object> variables = new HashMap<>();
		variables.put("allPossible", allPossible);
		
		try {
			Object result = GroovyUtils.evalScriptByName(scriptName, variables);
			if (result instanceof Map) {
				return (Map<String, String>) result;
			} else if (result instanceof List) {
				Map<String, String> choices = new HashMap<>();
				for (String item: (List<String>)result)
					choices.put(item, null);
				return choices;
			} else {
				throw new ExplicitException("Script should return either a Map or a List");
			}
		} catch (RuntimeException e) {
			if (allPossible) {
				logger.error("Error getting all possible choices", e);
				return new HashMap<>();
			} else {
				throw e;
			}
		}
	}

}

View on GitHub (pinned to d44925c47c)

Solutions

  1. Change the script to return a Map of value->label pairs, e.g. return [dev: "Development", prod: "Production"].
  2. Or return a List of strings when labels are unnecessary: return ["dev", "prod"].
  3. Ensure the script's last expression is the return value (Groovy implicit return) and no early return returns null.
  4. Wrap the result if the underlying API returns a single object: put it in a list or map before returning.

Example fix

// before (Groovy choice script)
def env = systemProps['env'];
return env;
// after
return [env: env];
// or
return [env];
Defensive patterns

Strategy: type-guard

Validate before calling

// inside the choice script (Groovy)
def result = computeChoices()
if (!(result instanceof Map) && !(result instanceof List))
    throw new IllegalStateException("Choice script must return Map or List, got " + result?.getClass())

Type guard

boolean isValidScriptResult(Object result) {
    return result instanceof Map || result instanceof List;
}

Try / catch

try {
    Map<String, String> choices = scriptingChoices.getChoices(false);
} catch (ExplicitException e) {
    logger.error("Choice script returned wrong type", e);
    choices = Collections.emptyMap();
}

Prevention

When it happens

Trigger: Running a scripting choice provider whose script returns a value that is neither Map nor List (e.g. a single String, Integer, or null produced by the script) and where the call is not allPossible (allPossible==false path throws; the allPossible path logs and returns an empty map).

Common situations: Groovy/JavaScript choice script written to print or return a plain string instead of a list/map; script refactored to return a custom object; forgetting to wrap results in a map when labels are needed.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of theonedev/onedev@d44925c47c (2026-09-06). Data as JSON: /api/errors/861546366d635eb9. Report an issue: GitHub.