apolloconfig/apollo · error · BadRequestException

{ex.getMessage()}

Error message

{ex.getMessage()}

What it means

This is a server-side validation error raised by Apollo Portal when a user submits YAML/YML namespace config text that SnakeYAML cannot parse. NamespaceTextSyntaxChecker.check() feeds the text into a YamlPropertiesFactoryBean whose createYaml() enables strict mode (allowDuplicateKeys=false) and a SafeConstructor, so any malformed YAML, duplicate keys, or disallowed types surface as a generic Exception whose message is re-thrown as a Spring BadRequestException (HTTP 400). Because the original exception type is erased and only getMessage() is forwarded, the client sees the raw SnakeYAML parser message (often prefixed with 'while scanning... / found... / mapping values are not allowed here').

Source

Thrown at apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/util/NamespaceTextSyntaxChecker.java:55

  private NamespaceTextSyntaxChecker() {}

  public static void check(NamespaceTextModel model) {
    if (StringUtils.isBlank(model.getConfigText())) {
      return;
    }

    if (model.getFormat() != ConfigFileFormat.YAML && model.getFormat() != ConfigFileFormat.YML) {
      return;
    }

    TypeLimitedYamlPropertiesFactoryBean yamlPropertiesFactoryBean =
        new TypeLimitedYamlPropertiesFactoryBean();
    yamlPropertiesFactoryBean.setResources(
        new ByteArrayResource(model.getConfigText().getBytes(StandardCharsets.UTF_8)));
    try {
      yamlPropertiesFactoryBean.getObject();
    } catch (Exception ex) {
      throw new BadRequestException(ex.getMessage());
    }
  }

  private static class TypeLimitedYamlPropertiesFactoryBean extends YamlPropertiesFactoryBean {

    @Override
    protected Yaml createYaml() {
      LoaderOptions loaderOptions = new LoaderOptions();
      loaderOptions.setAllowDuplicateKeys(false);
      DumperOptions dumperOptions = new DumperOptions();
      return new Yaml(new SafeConstructor(loaderOptions), new Representer(dumperOptions),
          dumperOptions, loaderOptions);
    }
  }
}

View on GitHub (pinned to d95fc18d11)

Solutions

  1. Run the submitted YAML through a local parser with the same settings (allowDuplicateKeys=false, SafeConstructor) before POSTing — e.g. `new Yaml(new SafeConstructor(new LoaderOptions())).loadAll(text)` in a scratch test — and fix whatever message comes back.
  2. Run `yamllint` (Python) or an IDE YAML validator on the configText to catch indentation/tab/syntax issues before submission.
  3. Remove duplicate keys: search the document for repeated keys at the same nesting level and rename or nest them.
  4. Strip any custom YAML tags (the `!!` or `!` constructs) and replace custom types with plain scalars/maps, because SafeConstructor intentionally refuses them.
  5. Normalize whitespace: convert tabs to 2-space indentation, replace smart quotes with ASCII quotes, ensure UTF-8 (the checker already re-encodes with UTF-8 but invisible characters can still break parsing).
  6. If the error message is ambiguous, reproduce locally by instantiating TypeLimitedYamlPropertiesFactoryBean (or just new Yaml(new SafeConstructor(new LoaderOptions(){{setAllowDuplicateKeys(false);}})) ) against the exact configText to see the full stack trace.

Example fix

// before (invalid: tab indentation + duplicate key)
server:
	port: 8080
server:
  port: 9090

// after (2-space indent, single key)
server:
  port: 8080
Defensive patterns

Strategy: validation

Validate before calling

// Validate YAML client-side with the same rules the server enforces
// (allowDuplicateKeys=false, SafeConstructor) before submitting.
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;

public static void validateApolloYaml(String configText) {
  if (configText == null || configText.isBlank()) return;
  LoaderOptions opts = new LoaderOptions();
  opts.setAllowDuplicateKeys(false);
  Yaml yaml = new Yaml(new SafeConstructor(opts));
  try {
    // Mirror Spring's expectation: it flattens into Properties, so a flat
    // map-shaped document is what parses cleanly.
    yaml.loadAll(configText).forEach(o -> { /* no-op */ });
  } catch (Exception e) {
    throw new IllegalArgumentException("YAML rejected locally: " + e.getMessage(), e);
  }
}

Try / catch

// Server-side: NamespaceTextSyntaxChecker already wraps the parse failure
// in BadRequestException. Callers (controllers) should let it propagate so
// Spring maps it to HTTP 400, and only add context at the edge:
try {
  NamespaceTextSyntaxChecker.check(model);
} catch (BadRequestException e) {
  // surface the parser message to the API client as-is; do not swallow
  throw e;
}

Prevention

When it happens

Trigger: Calling the Portal WebAPI or OpenAPI endpoint that creates/updates a namespace whose model.format is ConfigFileFormat.YAML or YML with configText that: (a) is syntactically invalid YAML (tabs, bad indentation, unquoted colons); (b) contains duplicate top-level or nested keys — rejected because LoaderOptions.setAllowDuplicateKeys(false); (c) contains constructs the SafeConstructor refuses (e.g. !!python/object, unparseable merge keys, custom tags); (d) embeds a YAML scalar where a mapping is expected, or a document that does not resolve into a flat Properties-style map.

Common situations: Developers editing Apollo YAML config in the portal UI and accidentally using tabs instead of spaces; copy-pasting a YAML block that has Windows line endings or smart quotes; defining the same key twice across merged documents; pasting an application.yml that uses Spring-specific tags (e.g. !!timestamp, custom constructors) that SafeConstructor rejects; CI pipelines pushing YAML generated by templating tools that emit a stray '{{placeholder}}'; upgrading SnakeYAML across versions where DuplicateKeyException or new security restrictions on SafeConstructor start firing.

Related errors


AI-assisted analysis of apolloconfig/apollo@d95fc18d11 (2026-08-14). Data as JSON: /api/errors/bdd5db42f8825499. Report an issue: GitHub.