{"record":{"id":"bdd5db42f8825499","repo":"apolloconfig/apollo","slug":"ex-getmessage","errorCode":null,"errorMessage":"{ex.getMessage()}","messagePattern":"\\{ex\\.getMessage\\(\\)\\}","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/util/NamespaceTextSyntaxChecker.java","lineNumber":55,"sourceCode":"  private NamespaceTextSyntaxChecker() {}\n\n  public static void check(NamespaceTextModel model) {\n    if (StringUtils.isBlank(model.getConfigText())) {\n      return;\n    }\n\n    if (model.getFormat() != ConfigFileFormat.YAML && model.getFormat() != ConfigFileFormat.YML) {\n      return;\n    }\n\n    TypeLimitedYamlPropertiesFactoryBean yamlPropertiesFactoryBean =\n        new TypeLimitedYamlPropertiesFactoryBean();\n    yamlPropertiesFactoryBean.setResources(\n        new ByteArrayResource(model.getConfigText().getBytes(StandardCharsets.UTF_8)));\n    try {\n      yamlPropertiesFactoryBean.getObject();\n    } catch (Exception ex) {\n      throw new BadRequestException(ex.getMessage());\n    }\n  }\n\n  private static class TypeLimitedYamlPropertiesFactoryBean extends YamlPropertiesFactoryBean {\n\n    @Override\n    protected Yaml createYaml() {\n      LoaderOptions loaderOptions = new LoaderOptions();\n      loaderOptions.setAllowDuplicateKeys(false);\n      DumperOptions dumperOptions = new DumperOptions();\n      return new Yaml(new SafeConstructor(loaderOptions), new Representer(dumperOptions),\n          dumperOptions, loaderOptions);\n    }\n  }\n}\n","sourceCodeStart":37,"sourceCodeEnd":71,"githubUrl":"https://github.com/apolloconfig/apollo/blob/d95fc18d112589efc09ddcbe1507047584d55251/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/util/NamespaceTextSyntaxChecker.java#L37-L71","documentation":"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').","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Run `yamllint` (Python) or an IDE YAML validator on the configText to catch indentation/tab/syntax issues before submission.","Remove duplicate keys: search the document for repeated keys at the same nesting level and rename or nest them.","Strip any custom YAML tags (the `!!` or `!` constructs) and replace custom types with plain scalars/maps, because SafeConstructor intentionally refuses them.","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).","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."],"exampleFix":"// before (invalid: tab indentation + duplicate key)\nserver:\n\tport: 8080\nserver:\n  port: 9090\n\n// after (2-space indent, single key)\nserver:\n  port: 8080","handlingStrategy":"validation","validationCode":"// Validate YAML client-side with the same rules the server enforces\n// (allowDuplicateKeys=false, SafeConstructor) before submitting.\nimport org.yaml.snakeyaml.LoaderOptions;\nimport org.yaml.snakeyaml.Yaml;\nimport org.yaml.snakeyaml.constructor.SafeConstructor;\n\npublic static void validateApolloYaml(String configText) {\n  if (configText == null || configText.isBlank()) return;\n  LoaderOptions opts = new LoaderOptions();\n  opts.setAllowDuplicateKeys(false);\n  Yaml yaml = new Yaml(new SafeConstructor(opts));\n  try {\n    // Mirror Spring's expectation: it flattens into Properties, so a flat\n    // map-shaped document is what parses cleanly.\n    yaml.loadAll(configText).forEach(o -> { /* no-op */ });\n  } catch (Exception e) {\n    throw new IllegalArgumentException(\"YAML rejected locally: \" + e.getMessage(), e);\n  }\n}","typeGuard":null,"tryCatchPattern":"// Server-side: NamespaceTextSyntaxChecker already wraps the parse failure\n// in BadRequestException. Callers (controllers) should let it propagate so\n// Spring maps it to HTTP 400, and only add context at the edge:\ntry {\n  NamespaceTextSyntaxChecker.check(model);\n} catch (BadRequestException e) {\n  // surface the parser message to the API client as-is; do not swallow\n  throw e;\n}","preventionTips":["Lint YAML in CI (yamllint or a SnakeYAML-based check) for any config pushed to Apollo.","Standardize on 2-space indentation and forbid tabs via editorconfig/.editorconfig in config repos.","When templating YAML (Helm, Jinja, envsubst), assert the output parses before committing.","Avoid custom YAML tags in Apollo namespace text — SafeConstructor will reject them by design.","Keep a unit test that round-trips your base YAML through new Yaml(new SafeConstructor(opts)) with setAllowDuplicateKeys(false) to mirror the server's exact strictness."],"tags":["yaml","validation","apollo","snakeyaml","bad-request","config"],"backgroundTag":null,"analyzedSha":"d95fc18d112589efc09ddcbe1507047584d55251","analyzedAt":"2026-08-14T04:00:05.477Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}