title: ImageIO - Read diferent files concurrency issue?
body: <p>I am currently writing a program, which batch processes different images. So I thought that it might be clever to do the operation (Scaling / adding watermark) in parallel.</p>
  
  <p>The problem is I get the following error:</p>
  
  <pre><code>    Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.NullPointerException
      at java.base/java.util.concurrent.ForkJoinTask.get(ForkJoinTask.java:1006)
      at eu.reisihub.soft.watermarking.Main$main$2$2$2$2.invoke(Main.kt:62)
      at eu.reisihub.soft.watermarking.Main$main$2$2$2$2.invoke(Main.kt:19)
      at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:149)
      at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:149)
      at kotlin.sequences.SequencesKt___SequencesKt.count(_Sequences.kt:1006)
      at eu.reisihub.soft.watermarking.Main.main(Main.kt:66)
  Caused by: java.lang.NullPointerException
      at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
      at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
      at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
      at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:488)
      at java.base/java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:603)
      ... 7 more
  Caused by: java.lang.NullPointerException
      at java.desktop/java.awt.color.ICC_Profile.intFromBigEndian(ICC_Profile.java:1784)
      at java.desktop/java.awt.color.ICC_Profile.getNumComponents(ICC_Profile.java:1476)
      at java.desktop/sun.java2d.cmm.lcms.LCMSTransform.&lt;init&gt;(LCMSTransform.java:93)
      at java.desktop/sun.java2d.cmm.lcms.LCMS.createTransform(LCMS.java:173)
      at java.desktop/java.awt.color.ICC_ColorSpace.fromRGB(ICC_ColorSpace.java:230)
      at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.setImageData(JPEGImageReader.java:808)
      at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.readImageHeader(Native Method)
      at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.readNativeHeader(JPEGImageReader.java:723)
      at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.checkTablesOnly(JPEGImageReader.java:347)
      at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.gotoImage(JPEGImageReader.java:493)
      at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.readHeader(JPEGImageReader.java:716)
      at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.readInternal(JPEGImageReader.java:1173)
      at java.desktop/com.sun.imageio.plugins.jpeg.JPEGImageReader.read(JPEGImageReader.java:1153)
      at java.desktop/javax.imageio.ImageIO.read(ImageIO.java:1468)
      at java.desktop/javax.imageio.ImageIO.read(ImageIO.java:1363)
      at eu.reisihub.shot.UtilsKt.readImage(Utils.kt:19)
      at eu.reisihub.soft.watermarking.WatermarkUtils$create$1.invoke(WatermarkUtils.kt:18)
      at eu.reisihub.soft.watermarking.WatermarkUtils$create$1.invoke(WatermarkUtils.kt:8)
      at eu.reisihub.soft.watermarking.Main$sam$java_util_concurrent_Callable$0.call(Main.kt)
      at java.base/java.util.concurrent.ForkJoinTask$AdaptedCallable.exec(ForkJoinTask.java:1448)
      at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
      at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1603)
      at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
  </code></pre>
  
  <p>please note, that I know that there is a NPE in <code>java.desktop/java.awt.color.ICC_Profile.intFromBigEndian(ICC_Profile.java:1784)</code>. And believe me, it is NOT the image.</p>
  
  <p>I tried 2 approaches with Java 8 and Java 10. I have a fixed size thread pool with 6 threads for 4 cores. I am using Kotlin for the implementation and created an extension function <code>fun Path.readImage(): BufferedImage</code>. For people, who don't know Kotlin, for the purpose of this example only, would correspond roughly to <code>public BufferedImage readImage(Path this)</code>.</p>
  
  <p><strong>Naive approach</strong>
  Reading the file using <code>Files.newInputStream(this, StandardOpenOption.READ).use { ImageIO.read(it) }</code>. This opens a new InputStream from the Path and tells ImageIO to read the image from this InputStream. Looks fine? Does not work ~every fourth time on Java 8 and roughly 90% on Java 10. BTW: If I wrap a synchronized block around this, it works - always. With 13 seconds to treat 17 images instead of 7 seconds.</p>
  
  <p>In the meantime I updated Gradle from 4.4 to 4.7. Before I configured IntelliJ to use Java 10 and Gradle to use Java 8. Might be the reason for the differences highlighted above. Side note. I always used <code>synchronized(System.err)</code>!</p>
  
  <p><strong>Sophisticated approach</strong>
  Knowing it works when synchronizing everything my method now looks more complicated and like this:</p>
  
  <pre><code>fun Path.readImage(): BufferedImage = Files.newInputStream(this, StandardOpenOption.READ).buffered().use {
      var nStream: ImageInputStream? = null
      var nReader: ImageReader? = null
      synchronized(System.err) {
          nStream = ImageIO.createImageInputStream(it) ?: throw IIOException("Can't create an ImageInputStream!")
  
          val iter = ImageIO.getImageReaders(nStream)
          if (!iter.hasNext()) {
              throw IIOException("No image nReader found!")
          }
          nReader = iter.next()
      }
  
      nStream!!.let { stream -&gt;
          nReader!!.let { reader -&gt;
              reader.setInput(stream, true, true)
              try {
                  println(reader)
                  return reader.read(0, null)
              } finally {
                  reader.dispose()
                  stream.close()
              }
          }
      }
  }
  </code></pre>
  
  <p>See the <code>println</code> in the code? I know that for every Image a new <code>com.sun.imageio.plugins.jpeg.JPEGImageReader</code>object is created.This improves the performance to the 7 seconds I was talking to. Usually does not work 2-4 times and then works 6-10 times.</p>
  
  <p>The stacktrace for the sophisticated version is the following:</p>
  
  <pre><code>Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.NullPointerException
      at java.util.concurrent.FutureTask.report(FutureTask.java:122)
      at java.util.concurrent.FutureTask.get(FutureTask.java:192)
      at eu.reisihub.soft.watermarking.Main$main$1$2$2$2$2.invoke(Main.kt:64)
      at eu.reisihub.soft.watermarking.Main$main$1$2$2$2$2.invoke(Main.kt:20)
      at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:149)
      at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:149)
      at kotlin.sequences.SequencesKt___SequencesKt.count(_Sequences.kt:1006)
      at eu.reisihub.soft.watermarking.Main$main$1.invoke(Main.kt:68)
      at eu.reisihub.soft.watermarking.Main$main$1.invoke(Main.kt:20)
      at eu.reisihub.shot.UtilsKt.measured(Utils.kt:54)
      at eu.reisihub.soft.watermarking.Main.main(Main.kt:24)
  Caused by: java.lang.NullPointerException
      at java.awt.color.ICC_Profile.intFromBigEndian(ICC_Profile.java:1782)
      at java.awt.color.ICC_Profile.getNumComponents(ICC_Profile.java:1474)
      at sun.java2d.cmm.lcms.LCMSTransform.&lt;init&gt;(LCMSTransform.java:98)
      at sun.java2d.cmm.lcms.LCMS.createTransform(LCMS.java:173)
      at java.awt.color.ICC_ColorSpace.fromRGB(ICC_ColorSpace.java:218)
      at com.sun.imageio.plugins.jpeg.JPEGImageReader.setImageData(JPEGImageReader.java:694)
      at com.sun.imageio.plugins.jpeg.JPEGImageReader.readImageHeader(Native Method)
      at com.sun.imageio.plugins.jpeg.JPEGImageReader.readNativeHeader(JPEGImageReader.java:609)
      at com.sun.imageio.plugins.jpeg.JPEGImageReader.checkTablesOnly(JPEGImageReader.java:347)
      at com.sun.imageio.plugins.jpeg.JPEGImageReader.gotoImage(JPEGImageReader.java:481)
      at com.sun.imageio.plugins.jpeg.JPEGImageReader.readHeader(JPEGImageReader.java:602)
      at com.sun.imageio.plugins.jpeg.JPEGImageReader.readInternal(JPEGImageReader.java:1059)
      at com.sun.imageio.plugins.jpeg.JPEGImageReader.read(JPEGImageReader.java:1039)
      at eu.reisihub.shot.UtilsKt.readImage(Utils.kt:39)
      at eu.reisihub.soft.watermarking.WatermarkUtils$create$1.invoke(WatermarkUtils.kt:16)
      at eu.reisihub.soft.watermarking.WatermarkUtils$create$1.invoke(WatermarkUtils.kt:8)
      at eu.reisihub.soft.watermarking.Main$sam$java_util_concurrent_Callable$0.call(Main.kt)
      at java.util.concurrent.FutureTask.run(FutureTask.java:266)
      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
      at java.lang.Thread.run(Thread.java:748)
  </code></pre>
  
  <p>Where <code>Main:64</code> is <code>Future#get</code>
  And <code>Main:68</code> is <code>kotlin.Sequence#count</code></p>
  
  <p>Location the Main.kt file: <a href="https://github.com/reisi007/Reisishot-Photo-Tools/blob/ea32a605657226edb6140911fd02f77d006163d3/Watermarking/src/main/kotlin/eu/reisihub/soft/watermarking/Main.kt" rel="nofollow noreferrer">https://github.com/reisi007/Reisishot-Photo-Tools/blob/ea32a605657226edb6140911fd02f77d006163d3/Watermarking/src/main/kotlin/eu/reisihub/soft/watermarking/Main.kt</a></p>
  
  <p>Location of Utils.kt file: <a href="https://github.com/reisi007/Reisishot-Photo-Tools/blob/ea32a605657226edb6140911fd02f77d006163d3/base/src/main/kotlin/eu/reisihub/shot/Utils.kt" rel="nofollow noreferrer">https://github.com/reisi007/Reisishot-Photo-Tools/blob/ea32a605657226edb6140911fd02f77d006163d3/base/src/main/kotlin/eu/reisihub/shot/Utils.kt</a></p>
  
  <p>I have tried improving the code for several hours and my head stopped spawning new ideas by now. I know that the mistake also happens with 2 pictures. As it does not always fail, the ICC profile must be correct. I am - honestly - conidering that my HDD is not sound and occasionally messes up. I also tried reading the files from my SSD. As per <a href="https://stackoverflow.com/a/26300361/1870799">https://stackoverflow.com/a/26300361/1870799</a> the read method should be thread-safe. I have no idea whats happening. Every image on disk (represented by a Path), gets it's own <code>Callable&lt;Task&gt;</code>. I am only reading those images and writing it to a different folder. So foreach task one file is read, one file is written. These two files are NOT the same, every task has its own files.</p>
  
  <p>The only thing they share is a BufferedImage in PNG format, which is drawn on the read image (watermark). But that happens later. As can be seen in the stacktrace it happens when reading a JPEG image.</p>
  
  <pre><code>at com.sun.imageio.plugins.jpeg.JPEGImageReader.read(JPEGImageReader.java:1039)
  at eu.reisihub.shot.UtilsKt.readImage(Utils.kt:39)
  </code></pre>
  
  <p>If you want to execute the program, the JSON setting, whose path the main app needs, can be created using <a href="https://github.com/reisi007/Reisishot-Photo-Tools/blob/ea32a605657226edb6140911fd02f77d006163d3/Watermarking/src/main/kotlin/eu/reisihub/soft/watermarking/SettingsCreator.kt" rel="nofollow noreferrer">https://github.com/reisi007/Reisishot-Photo-Tools/blob/ea32a605657226edb6140911fd02f77d006163d3/Watermarking/src/main/kotlin/eu/reisihub/soft/watermarking/SettingsCreator.kt</a></p>
  
  <p>I appreciate any input. At the current point I do not know what is wrong...</p>
  
  <p>BTW: For some tasks I am using: <a href="https://github.com/coobird/thumbnailator" rel="nofollow noreferrer">https://github.com/coobird/thumbnailator</a></p>