4

After updating to Java 8 update 101, I am getting exception in following code. It was working fine with Java 8 update 91.

Accessing keystore:

        KeyStore ks = KeyStore.getInstance("WINDOWS-MY");
        ks.load(null, null);

        Field field =  ks.getClass().getDeclaredField("keyStoreSpi");
        field.setAccessible(true);

        KeyStoreSpi kss = (KeyStoreSpi) field.get(ks);

        Collection entries;

        field = kss.getClass().getEnclosingClass().getDeclaredField("entries");
        field.setAccessible(true);

        // This is where the exception happens
        entries = (Collection) field.get(kss);

        // I then have to loop on these entries, something like this:

        for (Object entry : entries) { //code }

Type casting, exception is thrown:

java.util.HashMap cannot be cast to java.util.Collection

Any recent changes in Java 8 update 101? How to solve it?

Lii
  • 11,553
  • 8
  • 64
  • 88
tarunk
  • 59
  • 1
  • 4
  • 16
    You solve it by not fiddling with private data members of other classes. – chrylis -cautiouslyoptimistic- Jul 21 '16 at 13:15
  • 4
    Because whilst `Map` is part of the collections framework, it doesn't extend `Collection`. – Andy Turner Jul 21 '16 at 13:17
  • try to cast to Collection, if it fails then cast to `Map` and `entries = map.entrySet()` – EpicPandaForce Jul 21 '16 at 13:26
  • 6
    As chrylis noted, you are messing with the internals of class `KeyStore`, by accessing a private field via reflection. You're not supposed to do that, and now you're suffering the consequences. The implementation of classes might change between Java versions, as has probably happened here from 8u91 to 8u101. Only use the [publicly documented API](http://docs.oracle.com/javase/8/docs/api/java/security/KeyStore.html) of classes such as `KeyStore`, don't hack by accessing private fields via reflection. Why do you need to do this? – Jesper Jul 21 '16 at 13:31
  • 4
    @tarunk you are messing around with non-public implementation details of the class. The JDK developers are free to change those details because they've not promised any particular behaviour. – Andy Turner Jul 21 '16 at 13:31
  • @Jesper I need to get certificate details from keystore. Hence after getting 'entries' i need to loop over it. – tarunk Jul 21 '16 at 13:36
  • 2
    @tarunk What you mean by recent changes? `Map` never was castable to `Collection`. If it would than `Collection` interface have for instance `add(E e)` element method. What that mean in context of `Map`'s key, value? – abc Jul 21 '16 at 14:03
  • The user Arno1d posted what seems to be the underlying cause of your problem and [posted an answer](https://stackoverflow.com/a/41117197/452775) about it. I think you should consider making their answer as the accepted one. The information in the answer also implies that it should be safe for you to remove all reflection code and use the normal `KeyStore` methods. – Lii Jul 26 '17 at 13:47

4 Answers4

10

How to solve it?

As pointed out in the comments, what your codebase is doing is nasty. It should not be messing around with the internal implementation details of library classes. They are liable to change, and your code will then break ... as it has done here.

The best solution is to restrict yourself to using the public API methods for KeyStore; for example, call aliases() and then iterate the resulting Enumeration<String> looking up the entries for each alias.

If you can't do that, then you will need to modify your code to work with all of the different implementations of KeyStore that you and other users of your code might encounter.


It has been pointed out that this was (probably) a workaround for an old bug in the Java codebase:

If that is that case, then the fault is with person who put the workaround into your codebase without commenting it properly. They should have left a clear explanation for future developers ... ideally including the link to the bug report ... which they should have found. You would then been able to look up the bug report, see that it had been fixed, and remove the (now unnecessary) nasty workaround to solve your porting problem.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • There is also a nice explaination in this JDK modules talk which references this question https://youtu.be/rfOjch4p0Po?t=2704 – brass monkey Nov 06 '21 at 14:41
4

Before Java 8u101, you needed to access this private field to work around bug http://bugs.openjdk.java.net/browse/JDK-6483657. This bug has been fixed in Java 8u101, so you can go back to using the official keystore API.

Arno1d
  • 41
  • 1
  • 1
    Interesting! You seem to be the first person that actually tries to understand what the poster is doing and why. Do you have any reference to the source of the fact that the code in the question is a workaround for this bug? – Lii Jul 26 '17 at 10:44
  • In the [diff with the fix](http://hg.openjdk.java.net/jdk9/jdk9/jdk/rev/68f8be44b6a6) for this bug it is indeed visible that the `entries` field of the `JavaKeyStore` class has changed its type in a way that would give the problem described in the original post. – Lii Jul 26 '17 at 13:35
2

I confirm it does not work using the following as test code

import java.util.HashMap;
import java.util.Collection;

public class HelloWorld {
   public static void main(String[] args) {
      HashMap map = new HashMap();
      Collection c;
      c = (Collection) map;
   }
}

The outcome is Exception in thread "main" java.lang.ClassCastException: java.util.HashMap cannot be cast to java.util.Collection at HelloWorld.main(HelloWorld.java:8)

You can override it by using the values() method like this

import java.util.HashMap;
import java.util.Collection;

public class HelloWorld {
   public static void main(String[] args) {
      HashMap map = new HashMap();
      Collection c;
      c = map.values();
   }
}

So your code should be like this

import java.util.HashMap;
import java.util.Collection;
import java.security.*;
import java.lang.reflect.Field;

public class HelloWorld {
   public static void main(String[] args) {
    try{
      KeyStore ks = KeyStore.getInstance("WINDOWS-MY");
        ks.load(null, null);

        Field field =  ks.getClass().getDeclaredField("keyStoreSpi");
        field.setAccessible(true);

        KeyStoreSpi kss = (KeyStoreSpi) field.get(ks);

        Collection entries;

        field =kss.getClass().getEnclosingClass().getDeclaredField("entries");
        field.setAccessible(true);
        entries = ((HashMap) field.get(kss)).values();
    }catch(Exception e){
        e.printStackTrace();
    }
   }
}
Alexius DIAKOGIANNIS
  • 2,465
  • 2
  • 21
  • 32
0

The following change will make it work with 8u101, but it does not take away the fact that you are messing with the internals of class KeyStore which you should really not mess with.

The problem is that some object changed from being a Collection to being a HashMap in Java 8u101, and a HashMap is not a Collection, so trying to cast it to Collection causes a ClassCastException.

KeyStore ks = KeyStore.getInstance("WINDOWS-MY");
ks.load(null, null);

Field field =  ks.getClass().getDeclaredField("keyStoreSpi");
field.setAccessible(true);

KeyStoreSpi kss = (KeyStoreSpi) field.get(ks);

Collection entries;

field =kss.getClass().getEnclosingClass().getDeclaredField("entries");
field.setAccessible(true);
entries = (HashMap)field.get(kss);

for (Map.Entry entry : entries) {
    Object key = entry.getKey();
    Object value = entry.getValue();
    System.out.println(key + " = " + value);
}
Jesper
  • 202,709
  • 46
  • 318
  • 350