{"record":{"id":"78a191f814532c3d","repo":"alibaba/spring-cloud-alibaba","slug":"4000","errorCode":"4000","errorMessage":"AbstractConfigChangeListener only support basic java data type for yaml. If you want to listen key changes for custom classes, please use `Listener` to listener whole yaml configuration and parse it by yourself.","messagePattern":"AbstractConfigChangeListener only support basic java data type for yaml\\. If you want to listen key changes for custom classes, please use `Listener` to listener whole yaml configuration and parse it by yourself\\.","errorType":"exception","errorClass":"NacosRuntimeException","httpStatus":null,"severity":"error","filePath":"spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/ScaYamlConfigChangeParser.java","lineNumber":68,"sourceCode":"\t\t\t\toldMap = yaml.load(oldContent);\n\t\t\t\toldMap = getFlattenedMap(oldMap);\n\t\t\t}\n\t\t\tif (StringUtils.isNotBlank(newContent)) {\n\t\t\t\tnewMap = yaml.load(newContent);\n\t\t\t\tnewMap = getFlattenedMap(newMap);\n\t\t\t}\n\t\t}\n\t\tcatch (MarkedYAMLException e) {\n\t\t\thandleYamlException(e);\n\t\t}\n\n\t\treturn filterChangeData(oldMap, newMap);\n\t}\n\n\tprivate void handleYamlException(MarkedYAMLException e) {\n\t\tString message = e.getMessage();\n\t\tif ((message != null && message.startsWith(INVALID_CONSTRUCTOR_ERROR_INFO)) || e instanceof ComposerException) {\n\t\t\tthrow new NacosRuntimeException(NacosException.INVALID_PARAM,\n\t\t\t\t\t\"AbstractConfigChangeListener only support basic java data type for yaml. If you want to listen \"\n\t\t\t\t\t\t\t+ \"key changes for custom classes, please use `Listener` to listener whole yaml configuration and parse it by yourself.\",\n\t\t\t\t\te);\n\t\t}\n\t\tthrow e;\n\t}\n\n\tprivate Map<String, Object> getFlattenedMap(Map<String, Object> source) {\n\t\tMap<String, Object> result = new LinkedHashMap<>(128);\n\t\tbuildFlattenedMap(result, source, null);\n\t\treturn result;\n\t}\n\n\tprivate void buildFlattenedMap(Map<String, Object> result, Map<String, Object> source, @Nullable String path) {\n\n\t\tfor (Map.Entry<String, Object> e : source.entrySet()) {\n\t\t\tString key = e.getKey();\n\t\t\tif (StringUtils.isNotBlank(path)) {","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/alibaba/spring-cloud-alibaba/blob/115d5901102009492e05d5ec18c3f79cad4077d0/spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/ScaYamlConfigChangeParser.java#L50-L86","documentation":"Thrown by ScaYamlConfigChangeParser (a Nacos YAML change parser) when SnakeYAML's SafeConstructor cannot materialize a value during config-change diffing — specifically when the YAMLException message starts with 'could not determine a constructor for the tag' or the exception is a ComposerException. SafeConstructor only accepts plain Java scalars/maps/lists, so custom YAML tags or non-basic types cannot be diffed; the parser raises NacosRuntimeException with code 4000 (NacosException.INVALID_PARAM) guiding the user to listen on the whole config instead. It occurs at config-refresh time when AbstractConfigChangeListener key-level diffing runs.","triggerScenarios":"A Nacos YAML config that contains a custom/complex tag (e.g., !!com.example.MyType {...}), an anchor/alias structure SnakeYAML's SafeConstructor rejects, or any non-basic Java type, is changed while a key-level config-change listener (AbstractConfigChangeListener / @NacosConfigKeysChangeListener on yaml) is registered. On the next publish, the diff parser fails.","commonSituations":"Publishing YAML with custom Java object tags expected to bind to @ConfigurationProperties; using anchors/merge keys that the safe parser can't flatten; binding custom classes from YAML while relying on key-level change notifications.","solutions":["Restrict YAML listened via key-level change listeners to basic Java data types (scalars, maps, lists).","For custom classes, register a whole-config Listener, receive the raw YAML, and parse/diff it yourself (as the message instructs).","Remove custom `!!tag` constructs from the published YAML or replace them with plain maps."],"exampleFix":"// before: key-level listener on YAML with a custom tag\ndataId=app.yml  content:  service: !!com.example.Endpoint { host: x }\n// after: plain basic types, or use a whole-config Listener\nservice:\n  host: x\n// (and parse/diff custom classes yourself via com.alibaba.nacos.api.config.listener.Listener)","handlingStrategy":"try-catch","validationCode":"// Before relying on key-level YAML diff, validate the YAML loads under SafeConstructor with no custom tags.\nYaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));\ntry {\n    yaml.load(publishedYaml);\n} catch (MarkedYAMLException e) {\n    // do not use key-level change listener for this content\n}","typeGuard":"static boolean isSafeBasicYaml(String content) {\n    try {\n        Object o = new Yaml(new SafeConstructor(new LoaderOptions())).load(content);\n        return o == null || o instanceof Map;\n    } catch (RuntimeException e) {\n        return false;\n    }\n}","tryCatchPattern":"try {\n    // key-level change handling\n} catch (NacosRuntimeException e) {\n    if (e.getCode() == NacosException.INVALID_PARAM) {\n        // fall back to whole-config Listener and parse/diff yourself\n    }\n}","preventionTips":["Keep key-level-listened YAML to scalars/maps/lists only.","For custom classes, listen to the whole config and parse it yourself.","Avoid custom !!tags and unresolvable anchors in published YAML."],"tags":["nacos","config","yaml","snakeyaml","config-refresh","nacos-exception-4000"],"backgroundTag":null,"analyzedSha":"115d5901102009492e05d5ec18c3f79cad4077d0","analyzedAt":"2026-08-14T04:47:13.900Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}