Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8183993

ClassCastException from Collectors.groupingBy

    Details

      Description

      FULL PRODUCT VERSION :
      openjdk version "1.8.0_131"
      OpenJDK Runtime Environment (build 1.8.0_131-8u131-b11-0ubuntu1.16.04.2-b11)
      OpenJDK 64-Bit Server VM (build 25.131-b11, mixed mode)


      ADDITIONAL OS VERSION INFORMATION :
      Linux esbena-s782-L 4.4.0-83-generic #106-Ubuntu SMP Mon Jun 26 17:54:43 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux


      A DESCRIPTION OF THE PROBLEM :
      The use of java.util.stream.Collectors#groupingBy can lead to a ClassCastException at runtime for a program without casts in the user code.

      The core of the problem is that java.util.stream.Collectors$1OptionalBox is inserted into a Map<K, V>, where V is *not* java.util.stream.Collectors$1OptionalBox.
      The insertion happens during a call to java.util.Map#computeIfAbsent because the mapping function argument to that call returns a value of the wrong type.

      The provided source code example shows the problem using java.util.HashMap#computeIfAbsent, but the problem is reproducible using similar variants as subtypes of java.util.AbstractMap.

      I think the problem is in the combination of java.util.stream.Collectors#groupingBy and java.util.stream.Collectors#reducing.

      Speculation:

      In particular it seems that the mapFactory provided to java.util.stream.Collectors#groupingBy produces a map that is (mis)used to hold intermediary results of type java.util.stream.Collectors$1OptionalBox, which is supposed to be replaced with java.util.Optional during the call to java.util.Map#replaceAll in the finisher of java.util.stream.Collectors#groupingBy.

      NB:
      The problem is only exposed in the provided source code since the overriden java.util.HashMap#computeIfAbsent now performs a runtime type check of the value that was inserted into the map.


      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Compile and run the provided source code with java/javac. Notice that the provided source code does not contain any type casts.

      The problem can be reproduced for various other (older) java versions, and on other operating systems (OSX and Windows 10).

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      A successful run of the provided source code, without any output to standard out.
      ACTUAL -
      A java.lang.ClassCastException with associated stack trace. See the Error Messages field.

      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      Exception in thread "main" java.lang.ClassCastException: java.util.stream.Collectors$1OptionalBox cannot be cast to java.util.Optional
      at CollectorsReducingOptionalBoxTypeError$MyOptionalMap.computeIfAbsent(CollectorsReducingOptionalBoxTypeError.java:31)
      at CollectorsReducingOptionalBoxTypeError$MyOptionalMap.computeIfAbsent(CollectorsReducingOptionalBoxTypeError.java:27)
      at java.util.stream.Collectors.lambda$groupingBy$45(Collectors.java:908)
      at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
      at java.util.stream.Streams$StreamBuilderImpl.forEachRemaining(Streams.java:419)
      at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
      at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
      at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
      at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
      at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
      at CollectorsReducingOptionalBoxTypeError.test(CollectorsReducingOptionalBoxTypeError.java:21)
      at CollectorsReducingOptionalBoxTypeError.main(CollectorsReducingOptionalBoxTypeError.java:13)


      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      import java.util.HashMap;
      import java.util.Map;
      import java.util.Optional;
      import java.util.function.Function;
      import java.util.function.Supplier;
      import java.util.stream.Collector;
      import java.util.stream.Collectors;
      import java.util.stream.Stream;

      public class CollectorsReducingOptionalBoxTypeError {

          public static void main(String[] args) {
              test();
          }

          private static void test() {
              Function<Integer, Integer> classifier = e -> 0;
              Supplier<Map<Integer, Optional<Integer>>> mapFactory = MyOptionalMap::new;
              Collector<Integer, ?, Optional<Integer>> downstream = Collectors.reducing((e1, e2) -> 0);
              Stream.of(0)
                      .collect(Collectors.groupingBy(
                              classifier,
                              mapFactory,
                              downstream));
          }

          private static class MyOptionalMap<K, V> extends HashMap<K, Optional<V>> {

              @Override
              public Optional<V> computeIfAbsent(K key, Function<? super K, ? extends Optional<V>> mappingFunction) {
                  Optional<V> v = super.computeIfAbsent(key, mappingFunction); // force runtime type check
                  return v;
              }
          }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      c.f. the "Speculation" paragraph of the Description field above. I think a workaround is to use an intermediary map of the appropriate type.

        Attachments

          Activity

            People

            • Assignee:
              psandoz Paul Sandoz (Inactive)
              Reporter:
              webbuggrp Webbug Group
            • Votes:
              0 Vote for this issue
              Watchers:
              6 Start watching this issue

              Dates

              • Created:
                Updated: