{"record":{"id":"ab6d1cb6a43eaf74","repo":"apache/hadoop","slug":"null-io-stream-from-reopen-of","errorCode":null,"errorMessage":"Null IO stream from reopen of ({}) {}","messagePattern":"Null IO stream from reopen of \\((.+?)\\) (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"hadoop-cloud-storage-project/hadoop-huaweicloud/src/main/java/org/apache/hadoop/fs/obs/OBSInputStream.java","lineNumber":236,"sourceCode":"    if (wrappedStream != null) {\n      closeStream(\"reopen(\" + reason + \")\", contentRangeFinish);\n    }\n\n    contentRangeFinish =\n        calculateRequestLimit(targetPos, length, contentLength,\n            readAheadRange);\n\n    try {\n      GetObjectRequest request = new GetObjectRequest(bucket, key);\n      request.setRangeStart(targetPos);\n      request.setRangeEnd(contentRangeFinish);\n      if (fs.getSse().isSseCEnable()) {\n        request.setSseCHeader(fs.getSse().getSseCHeader());\n      }\n      wrappedStream = client.getObject(request).getObjectContent();\n      contentRangeStart = targetPos;\n      if (wrappedStream == null) {\n        throw new IOException(\n            \"Null IO stream from reopen of (\" + reason + \") \" + uri);\n      }\n    } catch (ObsException e) {\n      throw translateException(\"Reopen at position \" + targetPos, uri, e);\n    }\n\n    this.streamCurrentPos = targetPos;\n    long endTime = System.currentTimeMillis();\n    LOG.debug(\n        \"reopen({}) for {} range[{}-{}], length={},\"\n            + \" streamPosition={}, nextReadPosition={}, thread={}, \"\n            + \"timeUsedInMilliSec={}\",\n        uri,\n        reason,\n        targetPos,\n        contentRangeFinish,\n        length,\n        streamCurrentPos,","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-cloud-storage-project/hadoop-huaweicloud/src/main/java/org/apache/hadoop/fs/obs/OBSInputStream.java#L218-L254","documentation":"OBSInputStream.reopen (the lazy-seek/reader that issues a ranged GetObject) requires the OBS SDK to return object content; if client.getObject(request).getObjectContent() yields null, the connector throws IOException('Null IO stream from reopen of (reason) uri'). A null body after a successful call indicates a broken/edge-case SDK response rather than a normal missing object (that path raises ObsException and is translated). It is effectively a defensive assertion that the reopened HTTP stream is usable.","triggerScenarios":"Random-access reads (seek + read, positional read) that trigger reopen on every new range; OBS SDK or service-side anomalies returning 200 with no content stream; mocked/stubbed OBS clients in tests that forget to set object content; version mismatches between the connector and the OBS SDK jar.","commonSituations":"Upgrading the esdk-obs-java SDK to a version whose getObject response wrapper behaves differently; classpath conflicts mixing OBS SDK versions (shaded vs non-shaded); intermittent gateway/proxy behavior stripping response bodies; test fixtures returning partially-built ObjectMetadata.","solutions":["Align the OBS SDK version with the connector's required version (check the module's pom dependencies) and remove duplicate SDK jars from the classpath","Retry the read operation: this is not deterministic state corruption — the next reopen usually succeeds; wrap positional reads in a bounded retry","If behind a proxy/gateway, inspect whether ranged GET responses are being altered (e.g. 200-without-body instead of 206)","In tests, ensure the mocked client returns non-null content for every ranged GetObject request"],"exampleFix":"// before\nint n = in.read(buf, off, len); // positional read -> reopen -> null content -> IOException\n\n// after\nint n = retryingRead(in, buf, off, len, 3);\n\n// helper\nint retryingRead(FSDataInputStream in, byte[] b, int off, int len, int attempts) throws IOException {\n  IOException last = null;\n  for (int i = 0; i < attempts; i++) {\n    try {\n      return in.read(b, off, len);\n    } catch (IOException e) {\n      if (!(e.getMessage() != null && e.getMessage().contains(\"Null IO stream\"))) throw e;\n      last = e;\n    }\n  }\n  throw last;\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return in.read(buf, off, len);\n} catch (IOException e) {\n  if (String.valueOf(e.getMessage()).contains(\"Null IO stream from reopen\")) {\n    // anomalous SDK response: safe to retry the same positional read once or twice\n    return retryPositionalRead(in, buf, off, len, 2);\n  }\n  throw e;\n}","preventionTips":["Pin the OBS SDK version required by the connector; check for duplicate SDK jars","Cover ranged GetObject in test mocks so content is never null","Monitor frequency: recurring null-content errors indicate gateway/proxy or version problems"],"tags":["network","sdk-anomaly","read","hadoop-obs"],"backgroundTag":"null-response-body","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}