JAXB unmarshalling ignoring namespace turns element attributes into null

58,320

Solution 1

Thank you for this post and your code snippet. It definitely put me on the right path as I was also going nuts trying to deal with some vendor-provided XML that had xmlns="http://vendor.com/foo" all over the place.

My first solution (before I read your post) was to take the XML in a String, then xmlString.replaceAll(" xmlns=", " ylmns="); (the horror, the horror). Besides offending my sensibility, in was a pain when processing XML from an InputStream.

My second solution, after looking at your code snippet: (I'm using Java7)

// given an InputStream inputStream:
String packageName = docClass.getPackage().getName();
JAXBContext jc = JAXBContext.newInstance(packageName);
Unmarshaller u = jc.createUnmarshaller();

InputSource is = new InputSource(inputStream);
final SAXParserFactory sax = SAXParserFactory.newInstance();
sax.setNamespaceAware(false);
final XMLReader reader;
try {
    reader = sax.newSAXParser().getXMLReader();
} catch (SAXException | ParserConfigurationException e) {
    throw new RuntimeException(e);
}
SAXSource source = new SAXSource(reader, is);
@SuppressWarnings("unchecked")
JAXBElement<T> doc = (JAXBElement<T>)u.unmarshal(source);
return doc.getValue();

But now, I found a third solution which I like much better, and hopefully that might be useful to others: How to define properly the expected namespace in the schema:

<xsd:schema jxb:version="2.0"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
  xmlns="http://vendor.com/foo"
  targetNamespace="http://vendor.com/foo"
  elementFormDefault="unqualified"
  attributeFormDefault="unqualified">

With that, we can now remove the sax.setNamespaceAware(false); line (update: actually, if we keep the unmarshal(SAXSource) call, then we need to sax.setNamespaceAware(true). But the simpler way is to not bother with SAXSource and the code surrounding its creation and instead unmarshal(InputStream) which by default is namespace-aware. And the ouput of a marshal() also has the proper namespace too.

Yeh. Only about 4 hours down the drain.

Solution 2

How to ignore the namespaces

You can use an XMLStreamReader that is non-namespace aware, it will basically trim out all namespaces from the xml file that you're parsing:

// configure the stream reader factory
XMLInputFactory xif = XMLInputFactory.newFactory();
xif.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, false); // this is the magic line

// create xml stream reader using our configured factory
StreamSource source = new StreamSource(someFile);
XMLStreamReader xsr = xif.createXMLStreamReader(source);

// unmarshall, note that it's better to reuse JAXBContext, as newInstance()
// calls are pretty expensive
JAXBContext jc = JAXBContext.newInstance(your.ObjectFactory.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
Object unmarshal = unmarshaller.unmarshal(xsr);

Now the actual xml that gets fed into JAXB doesn't have any namespace info.


Important note (xjc)

If you generated java classes from an xsd schema using xjc and the schema had a namespace defined, then the generated annotations will have that namespace, so delete it manually! Otherwise JAXB won't recognize such data.

Places where the annotations should be changed:

  • ObjectFactory.java

     // change this line
     private final static QName _SomeType_QNAME = new QName("some-weird-namespace", "SomeType");
     // to something like
     private final static QName _SomeType_QNAME = new QName("", "SomeType", "");
    
     // and this annotation
     @XmlElementDecl(namespace = "some-weird-namespace", name = "SomeType")
     // to this
     @XmlElementDecl(namespace = "", name = "SomeType")
    
  • package-info.java

     // change this annotation
     @javax.xml.bind.annotation.XmlSchema(namespace = "some-weird-namespace", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
     // to something like this
     @javax.xml.bind.annotation.XmlSchema(namespace = "", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
    

Now your JAXB code will expect to see everything without any namespaces and the XMLStreamReader that we created supplies just that.

Solution 3

Here is my solution for this Namespace related issue. We can trick JAXB by implementing our own XMLFilter and Attribute.

class MyAttr extends  AttributesImpl {

    MyAttr(Attributes atts) {
        super(atts);
    }

    @Override
    public String getLocalName(int index) {
        return super.getQName(index);
    }

}

class MyFilter extends XMLFilterImpl {

    @Override
    public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
        super.startElement(uri, localName, qName, new VersAttr(atts));
    }

}

public SomeObject testFromXML(InputStream input) {

    try {
        // Create the JAXBContext
        JAXBContext jc = JAXBContext.newInstance(SomeObject.class);

        // Create the XMLFilter
        XMLFilter filter = new VersFilter();

        // Set the parent XMLReader on the XMLFilter
        SAXParserFactory spf = SAXParserFactory.newInstance();
        //spf.setNamespaceAware(false);

        SAXParser sp = spf.newSAXParser();
        XMLReader xr = sp.getXMLReader();
        filter.setParent(xr);

        // Set UnmarshallerHandler as ContentHandler on XMLFilter
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        UnmarshallerHandler unmarshallerHandler = unmarshaller
                .getUnmarshallerHandler();
        filter.setContentHandler(unmarshallerHandler);

        // Parse the XML
        InputSource is = new InputSource(input);
        filter.parse(is);
        return (SomeObject) unmarshallerHandler.getResult();

    }catch (Exception e) {
        logger.debug(ExceptionUtils.getFullStackTrace(e));
    }

    return null;
}

Solution 4

There is a workaround for this issue explained in this post: JAXB: How to ignore namespace during unmarshalling XML document?. It explains how to dynamically add/remove xmlns entries from XML using a SAX Filter. Handles marshalling and unmarshalling alike.

Share:
58,320

Related videos on Youtube

user227614
Author by

user227614

Updated on July 09, 2022

Comments

  • user227614
    user227614 almost 2 years

    I'm trying to use JAXB to unmarshal an xml file into objects but have come across a few difficulties. The actual project has a few thousand lines in the xml file so i've reproduced the error on a smaller scale as follows:

    The XML file:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <catalogue title="some catalogue title" 
               publisher="some publishing house" 
               xmlns="x-schema:TamsDataSchema.xml"/>
    

    The XSD file for producing JAXB classes

    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
     <xsd:element name="catalogue" type="catalogueType"/>
    
     <xsd:complexType name="catalogueType">
      <xsd:sequence>
       <xsd:element ref="journal"  minOccurs="0" maxOccurs="unbounded"/>
      </xsd:sequence>
      <xsd:attribute name="title" type="xsd:string"/>
      <xsd:attribute name="publisher" type="xsd:string"/>
     </xsd:complexType>
    </xsd:schema>
    

    Code snippet 1:

    final JAXBContext context = JAXBContext.newInstance(CatalogueType.class);
    um = context.createUnmarshaller();
    CatalogueType ct = (CatalogueType)um.unmarshal(new File("file output address"));
    

    Which throws the error:

    javax.xml.bind.UnmarshalException: unexpected element (uri:"x-schema:TamsDataSchema.xml", local:"catalogue"). Expected elements are <{}catalogue>
     at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext.handleEvent(UnmarshallingContext.java:642)
     at com.sun.xml.bind.v2.runtime.unmarshaller.Loader.reportError(Loader.java:247)
     at com.sun.xml.bind.v2.runtime.unmarshaller.Loader.reportError(Loader.java:242)
     at com.sun.xml.bind.v2.runtime.unmarshaller.Loader.reportUnexpectedChildElement(Loader.java:116)
     at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext$DefaultRootLoader.childElement(UnmarshallingContext.java:1049)
     at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext._startElement(UnmarshallingContext.java:478)
     at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext.startElement(UnmarshallingContext.java:459)
     at com.sun.xml.bind.v2.runtime.unmarshaller.SAXConnector.startElement(SAXConnector.java:148)
     at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.startElement(Unknown Source)
     at com.sun.org.apache.xerces.internal.parsers.AbstractXMLDocumentParser.emptyElement(Unknown Source)
     at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(Unknown Source)
     at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl$NSContentDispatcher.scanRootElementHook(Unknown Source)
     at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(Unknown Source)
     at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)
     at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)
     at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)
     at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(Unknown Source)
        ...etc
    

    So the namespace in the XML document is causing issues, unfortunately if it's removed it works fine, but as the file is supplied by the client we're stuck with it. I've attempted numerous ways of specifying it in the XSD but none of the permutations seem to work.

    I also attempted to unmarshal ignoring namespace using the following code:

    Unmarshaller um = context.createUnmarshaller();
    final SAXParserFactory sax = SAXParserFactory.newInstance();
    sax.setNamespaceAware(false);
    final XMLReader reader = sax.newSAXParser().getXMLReader();
    final Source er = new SAXSource(reader, new InputSource(new FileReader("file location")));
    CatalogueType ct = (CatalogueType)um.unmarshal(er);
    System.out.println(ct.getPublisher());
    System.out.println(ct.getTitle());
    

    which works fine but fails to unmarshal element attributes and prints

    null
    null
    

    Due to reasons beyond our control we're limited to using Java 1.5 and we're using JAXB 2.0 which is unfortunate because the second code block works as desired using Java 1.6.

    any suggestions would be greatly appreciated, the alternative is cutting the namespace declaration out of the file before parsing it which seems inelegant.

    • Artem
      Artem over 14 years
      Why don't you just make the schema describe the namespace?
    • Kissaki
      Kissaki over 13 years
      Although not defined in the XSD, one can extend the JAXB annotated classes with namespace (annotation option) to make it work. I’d like to know a way to specify this in the XSD as well though … So these annotation options are actually generated automatically and don’t have to be set manually.
  • BradleyDotNET
    BradleyDotNET almost 10 years
    Thanks for your answer! A few tips: (1) don't start with "I had the same problem". It isn't relevant and is a great way to get thrown into the Low Quality Review queue. (2) Remove that part from your answer (which is pretty much the whole first paragraph) and you have a code-only answer. While it may be correct, it is preferable that you explain why the answer is correct in English, so that we all learn (instead of just having magic working code). If you notify me after you make your edit, I would consider upvoting this. Welcome to Stack Overflow!
  • Arundev
    Arundev over 7 years
    This is the best solution i found so far in internet. thanks alot. Cheers.
  • LINGS
    LINGS almost 5 years
    How could I use this code to intercept the requests coming to my Spring application to ignore namespaces of the request body before it reaches JAXB?
  • Raymond Chenon
    Raymond Chenon almost 3 years
    I didn't understand your 3rd solution, you make namespace aware which was the cause of this exception.
  • Mzzl
    Mzzl over 2 years
    Dear @Dmitry-Avtonomov from many years ago, my day was ruined by badly behaved legacy code, but you saved my day
  • ThomasG
    ThomasG over 2 years
    Dear @Mzzl from 2021, I'm very pleased that this answer was useful to somebody 8 years down the road. It's also sad that the same issues can still be encountered in 2021.