{"record":{"id":"cae2d34f71e32a85","repo":"apache/hadoop","slug":"gcs-path-must-not-have-consecutive-characters","errorCode":null,"errorMessage":"GCS path must not have consecutive '/' characters: '%s'","messagePattern":"GCS path must not have consecutive '/' characters: '(.+?)'","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"hadoop-cloud-storage-project/hadoop-gcp/src/main/java/org/apache/hadoop/fs/gs/StringPaths.java","lineNumber":97,"sourceCode":"   */\n  static String validateObjectName(String objectName, boolean allowEmptyObjectName) {\n    LOG.trace(\"validateObjectName('{}', {})\", objectName, allowEmptyObjectName);\n\n    if (isNullOrEmpty(objectName) || objectName.equals(PATH_DELIMITER)) {\n      if (allowEmptyObjectName) {\n        objectName = \"\";\n      } else {\n        throw new IllegalArgumentException(String.format(\n            \"GCS path must include non-empty object name [objectName='%s',\"\n                + \" allowEmptyObjectName=%s]\", objectName, allowEmptyObjectName));\n      }\n    }\n\n    // We want objectName to look like a traditional file system path,\n    // therefore, disallow objectName with consecutive '/' chars.\n    for (int i = 0; i < (objectName.length() - 1); i++) {\n      if (objectName.charAt(i) == '/' && objectName.charAt(i + 1) == '/') {\n        throw new IllegalArgumentException(\n            String.format(\"GCS path must not have consecutive '/' characters: '%s'\", objectName));\n      }\n    }\n\n    // Remove leading '/' if it exists.\n    if (objectName.startsWith(PATH_DELIMITER)) {\n      objectName = objectName.substring(1);\n    }\n\n    LOG.trace(\"validateObjectName -> '{}'\", objectName);\n    return objectName;\n  }\n\n  /**\n   * Helper for standardizing the way various human-readable messages in logs/exceptions that refer\n   * to a bucket/object pair.\n   */\n  public static String fromComponents(String bucketName, String objectName) {","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-cloud-storage-project/hadoop-gcp/src/main/java/org/apache/hadoop/fs/gs/StringPaths.java#L79-L115","documentation":"Thrown by StringPaths.validateObjectName (hadoop-gcp GoogleHadoopFileSystem) when the object-name part of a gs:// path contains two or more consecutive '/' characters. The connector deliberately makes object names look like traditional filesystem paths, so 'a//b' is rejected with an IllegalArgumentException during path decoding, before any GCS request is made. It is a client-side validation error, not a server response.","triggerScenarios":"Calling GHFS APIs with a Path whose decoded object name contains '//': e.g. new Path(\"gs://bucket/dir//file\") passed through UriPaths/StringPaths decoding; building keys by string concatenation (dir + \"/\" + \"/\" + name); passing raw strings from config or user input as object names; using Path.toString()+\"/\"+x instead of Path construction.","commonSituations":"Spark/Hive jobs assembling partition paths with string concat (\"/year=2026/\" + \"/part-0\"); paths derived from URLs that keep empty segments; renaming/copying data whose source keys contain '//' into gs://; custom InputFormat splitters producing empty path segments.","solutions":["Normalize the key before use: replace runs of '/' with a single '/', e.g. key.replaceAll(\"/+\", \"/\") and trim leading '/'.","Build paths with Hadoop Path API (new Path(parent, name)) or GoogleCloudStorageFileSystem helper methods instead of string concatenation.","Validate external/config-supplied paths with a regex like .*(//).* and reject early with a clear message.","If double slashes are genuinely required in the object name, GCS object names must be built through the raw GoogleCloudStorage API, not the filesystem layer."],"exampleFix":"// before\nString key = dir + \"/\" + \"/\" + fileName; // dir ends with '/'\ngfs.open(new Path(\"gs://\" + bucket + \"/\" + key));\n\n// after\nString key = (dir + \"/\" + fileName).replaceAll(\"/+\", \"/\");\ngfs.open(new Path(\"gs://\" + bucket + \"/\" + key));","handlingStrategy":"validation","validationCode":"static boolean hasConsecutiveSlashes(String objectName) {\n  for (int i = 0; i + 1 < objectName.length(); i++) {\n    if (objectName.charAt(i) == '/' && objectName.charAt(i + 1) == '/') return true;\n  }\n  return false;\n}\n// before any GHFS call:\nString key = raw.replaceAll(\"/+\", \"/\");\nif (hasConsecutiveSlashes(key)) throw new IllegalArgumentException(\"bad key: \" + raw);","typeGuard":"// Java idiom: guard returning a sanitized value\nstatic String asGcsObjectName(String raw) {\n  String k = raw.replaceAll(\"/+\", \"/\");\n  if (k.startsWith(\"/\")) k = k.substring(1);\n  if (k.contains(\"//\")) throw new IllegalArgumentException(\"Unfixable key: \" + raw);\n  return k;\n}","tryCatchPattern":"try {\n  fs.open(new Path(\"gs://bucket/\" + key));\n} catch (IllegalArgumentException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"consecutive '/'\")) {\n    throw new ConfigException(\"Path normalization bug for key: \" + key, e);\n  }\n  throw e;\n}","preventionTips":["Always join paths with new Path(parent, child), never string concatenation.","Run key = key.replaceAll(\"/+\", \"/\") at system boundaries (user input, config).","Add a unit test asserting generated keys never match //."],"tags":["hadoop-gcp","gcs","path-validation","illegal-argument","hadoop-cloud-storage"],"backgroundTag":"invalid-path-format","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}