title: Java: Reading the same file from different threads sometimes returns null content in some of the threads
body: <p>First of all, I am pretty new in coding, so I apologise for any argot-mistake I am probably making</p>
  
  <p>I am working on the development of a backend server using <strong>Java (openJDK11)</strong> and <strong>Spring boot</strong>:</p>
  
  <p>The application is composed by many panels and sub-panels, which are opened from the web browser. When clicking a sub-panel, three different GET requests are performed in front-end side.</p>
  
  <p>These three requests expect different responses (json schema, json data, etc). Each request starts a different thread that access <strong>the same config file</strong> (there is one config file per sub-panel), which is parsed. After reading the config file, each thread does different operations, they only have in common the config-reader part.</p>
  
  <ul>
  <li><p>Sometimes (this makes me think of concurrency), the read-operation in one/some of these threads cannot be performed, since <code>bufferedReader.readLine()</code> returns <strong>null</strong> without reading any line.</p></li>
  <li><p>Also, sometimes happens that after reading correctly some lines, suddenly <code>bufferedReader.readLine()</code> returns <strong>null</strong>, but the file has not been completely read yet. </p></li>
  </ul>
  
  <p>Each thread creates a local <code>InputStream</code> to open the file, and a local <code>BufferedReader</code> to parse it.</p>
  
  <p>I have tried making <strong>synchronized</strong> the <code>parseFile</code> method (although I feel this is not right, since I don't want other threads that use this method -to read other files- to wait).</p>
  
  <p>Below are included the pieces of code were the file is accessed and read (line by line). <strong>This is a "reduced" example</strong>. As commented below by some users, Exceptions are not handled here, but <strong>are</strong> in the real code. This is only to show the part that is causing trouble. </p>
  
  <pre class="lang-java prettyprint-override"><code>
  // REST CONTROLLER
  
  @GetMapping(value = "/schema/{panel}/{subpanel}")
  public PanelSchemaEntity getSchema(String panel, String subpanel)
  {
    //Retrieves the config-file name associated to the given panel+subpanel
    String fileName = getConfig(panel, subpanel);
    // fileName = "target/config/panelABC1.txt"
  
    InputStream input = new FileInputStream(fileName);
    PanelSchemaEntity schema = new PanelSchemaEntity();
    parseFile(schema, input);
  
    return schema;
  }
  
  @GetMapping(value = "/data/{panel}/{subpanel}")
  public PanelDataEntity get(String panel, String subpanel)
  {
    //Retrieves the config-file name associated to the given panel+subpanel
    String fileName = getConfig(panel, subpanel);
    // fileName = "target/config/panelABC1.txt"
  
    InputStream input = new FileInputStream(fileName);
    PanelSchemaEntity schema = new PanelSchemaEntity();
    parseFile(schema, input);
  
    String dataFileName = getDataFile(panel, subpanel);
    // dataFileName = "target/config/panelABC1.dat"
    InputStream data = new FileInputStream(dataFileName);
  
    return new PanelDataEntity(schema, data);
  }
  
  </code></pre>
  
  <pre class="lang-java prettyprint-override"><code>// PLACED IN SOME UTILS PACKAGE
  
  // Fills the PanelSchemaEntity with the content read from input
  public PanelSchemaEntity parseFile(PanelSchemaEntity schema, InputStream input)
  {
    BufferedReader reader = new BufferedReader(new InputStreamReader(input));
    String nextLine = reader.readLine();
  
    // The data file is read and used to complete panel schema entity
    while(nextLine != null)
    {
      // Here goes the code that uses each line's content to 
      // fill some schema's attributes
    }
    reader.close();
    return schema;
  }
  
  </code></pre>
  
  <p>Sorry again for any mistake I am possibly making, and thank you all :)</p>
  
  <h3>EDIT</h3>
  
  <ul>
  <li><p>The file to be read multiple times is small but <strong>cannot be stored in cache</strong>, since it is related to a panel which may not be opened again soon. Also, there are too many config files (one per sub-panel) to be cached and each file may change</p></li>
  <li><p>I strongly prefer not to include new libraries, since I have not permission to do this, I only need to fix this behaviour by including minor changes in the code</p></li>
  <li><p>Also, when <strong>debugging</strong>, the 3 threads (each one opens its own <code>InputStream</code> and a <code>BufferedReader</code> for the same config file) work perfectly</p></li>
  </ul>
  
  <hr>
  
  <h3>ANSWER</h3>
  
  <p><strong>Effectively, <code>InputStreams</code> can be created in different threads, all pointing to the same file, and then be read using a <code>BufferedReader</code> without synchronizing anything.</strong></p>
  
  <p>My first mistake was showing here a too reduced version of the original code. I focused on showing what I thought that was the problem. It is my first post, I'll do better next time. </p>
  
  <p>The error in my code was in the <code>getConfig</code> method, which builds the <code>fileName</code> using the panel and subpanel parameters. This method, before returning this <code>fileName</code> variable, downloads the file from a server (only if it has changed) to the <code>target/</code> dir to be accessed locally. What was working <strong>wrong</strong> is that the file was being downloaded <strong>always</strong>, so it was being re-downloaded (overwritten) by another thread while being read in current thread.</p>
  
  <p>Find below the code I should have placed in the post originally:</p>
  
  <pre class="lang-java prettyprint-override"><code>
  // REST CONTROLLER
  
  @GetMapping(value = "/schema/{panel}/{subpanel}")
  public PanelSchemaEntity getSchema(String panel, String subpanel)
  {
    //Retrieves the config-file name associated to the given panel+subpanel
    ConfigFile configFile = getConfig(panel, subpanel);
    // configFile.getPath() = "target/config/panelABC1.txt"
  
    InputStream input = new FileInputStream(configFile.getPath());
    PanelSchemaEntity schema = new PanelSchemaEntity();
    parseFile(schema, input);
  
    return schema;
  }
  
  @GetMapping(value = "/data/{panel}/{subpanel}")
  public PanelDataEntity get(String panel, String subpanel)
  {
    //Retrieves the config-file name associated to the given panel+subpanel
    ConfigFile configFile = getConfig(panel, subpanel);
    // configFile.getPath() = "target/config/panelABC1.txt"
  
    InputStream input = new FileInputStream(configFile.getPath());
    PanelSchemaEntity schema = new PanelSchemaEntity();
    parseFile(schema, input);
  
    String dataFileName = getDataFile(panel, subpanel);
    // dataFileName = "target/config/panelABC1.dat"
    InputStream data = new FileInputStream(dataFileName);
  
    return new PanelDataEntity(schema, data);
  }
  
  </code></pre>
  
  <pre class="lang-java prettyprint-override"><code>// PLACED IN SOME UTILS PACKAGE
  
  // Creates fileName and downloads file (if changed)
  public ConfigFile getConfig(String panel, String subpanel)
  {
    String filePathInServer = findFilePathInServer(panel, subpanel);
  
    // ERROR here: the download was happening always
    String localFilePath = donwloadIfChanged(filePathInServer); 
  
    ConfigFile configFile = new ConfigFile(localFilePath);
  
    return configFile;
  }
  
  </code></pre>
  
  <pre class="lang-java prettyprint-override"><code>// PLACED IN SOME UTILS PACKAGE
  
  // Fills the PanelSchemaEntity with the content read from input
  public PanelSchemaEntity parseFile(PanelSchemaEntity schema, InputStream input)
  {
    BufferedReader reader = new BufferedReader(new InputStreamReader(input));
    String nextLine = reader.readLine();
  
    // The data file is read and used to complete panel schema entity
    while(nextLine != null)
    {
      // Here goes the code that uses each line's content to 
      // fill some schema's attributes
    }
    reader.close();
    return schema;
  }
  
  </code></pre>
  
  <p>When I moved the InputStream creation within the <code>getInputStream</code> method, I also included there the download of the file. This is why synchronizing the whole <code>getInputStream = download file + create and return InputStream</code> worked for me. </p>
  
  <p>This requires to fix things in different places:
  * I need to download file only when it has changed (as expected)
  * I would also synchronize the whole <code>download + InputStream creation</code> for the case when the file is the same (not using the <code>fileName</code> string)</p>