ArrayIndexOutOfBoundException on XMLDecoder.readObject()

There are scenarios in my program where i will be getting the empty object from the properties file as shown below

<?xml version="1.0" encoding="UTF-8"?> 
 <java version="1.4.1" class="java.beans.XMLDecoder"> 
 <object class="java.util.Map">
</object>
</java>

On reading the empty object in below code

public Object translateObj(
        String data,
        PropertyDefinition propDef) {
        try {
            XMLDecoder decoder =
                new XMLDecoder(new BufferedInputStream(new ByteArrayInputStream(data.getBytes("UTF-8"))));

            Object object = decoder.readObject();

            decoder.close();

            return object;
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("XmlNativeTranslator.translateFromStorage: " +
                " Unable to UTF-8 encode inputData " + data.toString());
        } catch (ArrayIndexOutOfBoundsException e) {
            StringBuffer s = new StringBuffer();
            s.append("Failed to translate property definition key [");
            s.append(propDef.getKey());
            s.append("] XML contents [");
            s.append(data.toString());
            s.append("]");
            throw new RuntimeException(s.toString(), e);
        }
    }

On trying to read the empty object in the above code in line decoder.readObject() i am facing the below error

java.lang.RuntimeException: XmlNativeTranslator.translateFromStorage: Failed to translate property definition key [FORMS_KEY] XML contents [ ] Caused by: java.lang.ArrayIndexOutOfBoundsException: 0

Need a way to handle this scenario

1 answer

  • answered 2018-07-11 12:29 Holger

    java.util.Map is an interface and you should never encounter it in a serialized form, as it is impossible to have direct instances of an interface.

    Valid expressions of empty maps would be, e.g.

    <object class="java.util.HashMap"/>
    

    or

    <object class="java.util.Collections" method="emptyMap"/>
    

    or

    <object class="java.util.Properties"/>
    

    The XMLDecoder has the questionable feature of proceeding in the exceptional case, which causes the ArrayIndexOutOfBoundsException as follow-up error.

    You can force it to throw the actual exception instead:

    XMLDecoder dec = new XMLDecoder(new InputSource(new StringReader(data)));
    dec.setExceptionListener(ex -> {
        throw ex instanceof RuntimeException? (RuntimeException)ex:
            new RuntimeException("Failed to translate property definition key"+
                " ["+propDef.getKey()+"] XML contents ["+data+"]", ex);
    });
    Object object = dec.readObject();
    return object;
    

    Note that by using a StringReader there are no encoding errors to handle. I also removed the close() step as a StringReader has no attached resources and XMLDecoder.close() causes even more follow-up errors in the exceptional case when we use try-with-resource.

    Using a StringBuffer manually is no improvement over string concatenation here; before Java 5 ,the compiler would generate almost identical code, since Java 5 it generates similar code using StringBuilder which is even more efficient, not to speak of Java 9 and newer which are even better.

    Running this code with your sample input produces

    Exception in thread "main" java.lang.RuntimeException: Failed to translate property definition key [] XML contents [<?xml version="1.0" encoding="UTF-8"?> 
     <java version="1.4.1" class="java.beans.XMLDecoder"> 
     <object class="java.util.Map">
    </object>
    </java>]
        at ListFiltersToGetMatchingRecords.lambda$main$0(ListFiltersToGetMatchingRecords.java:35)
        at com.sun.beans.decoder.DocumentHandler.handleException(DocumentHandler.java:359)
        at com.sun.beans.decoder.NewElementHandler.getValueObject(NewElementHandler.java:126)
        at com.sun.beans.decoder.ElementHandler.endElement(ElementHandler.java:169)
        at com.sun.beans.decoder.DocumentHandler.endElement(DocumentHandler.java:318)
    … shortened …
        at com.sun.beans.decoder.DocumentHandler.parse(DocumentHandler.java:372)
        at java.beans.XMLDecoder$1.run(XMLDecoder.java:201)
        at java.beans.XMLDecoder$1.run(XMLDecoder.java:199)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.beans.XMLDecoder.parsingComplete(XMLDecoder.java:199)
        at java.beans.XMLDecoder.readObject(XMLDecoder.java:250)
        at ListFiltersToGetMatchingRecords.main(ListFiltersToGetMatchingRecords.java:38)
    Caused by: java.lang.InstantiationException: java.util.Map
        at java.lang.Class.newInstance(Class.java:427)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    … shortened …
        at java.beans.Statement.invoke(Statement.java:182)
        at java.beans.Expression.getValue(Expression.java:155)
        at com.sun.beans.decoder.ObjectElementHandler.getValueObject(ObjectElementHandler.java:166)
        at com.sun.beans.decoder.NewElementHandler.getValueObject(NewElementHandler.java:123)
        ... 24 more
    Caused by: java.lang.NoSuchMethodException: java.util.Map.<init>()
        at java.lang.Class.getConstructor0(Class.java:3082)
        at java.lang.Class.newInstance(Class.java:412)
        ... 42 more