Search code examples
javaxml-parsingjaxbmarshallingunmarshalling

XML annotations (JAXB) suggestions for unmarshalling and marshalling


I have a sample XML as shown below:

<?xml version="1.0" encoding="UTF-8"?>
<Education>
    <School name="ABC" location="Mangalore">
        <Student gender="M" housepincode="575020">
            <FirstName>VJ</FirstName>
            <LastName>K</LastName>
        </Student>
        <Student gender="M" housepincode="575002">
            <FirstName>S</FirstName>
            <LastName>K</LastName>
        </Student>
    </School>
</Education>

This has been unmarshalled with the help of JAXB using the following approach. I have classes for Education,School and Student.

Education.java

import lombok.Data;
@Data
@XmlRootElement(name = "Education")
public class Education {    
    public School School;
}

School.java

import lombok.Data;
@Data
@XmlAccessorType(XmlAccessType.FIELD)
public class School {   
    @XmlAttribute
    private String name;    
    @XmlAttribute
    private String location;    
    @XmlElement(name ="Student")
    private List<Student> Student;
}

Student.java

@Data
@XmlAccessorType(XmlAccessType.FIELD)
public class Student {      
    @XmlAttribute
    private String gender;  
    @XmlAttribute
    private String housepincode;    
    @XmlElement(name = "FirstName")
    private String FirstName;
    @XmlElement(name = "LastName")
    private String LastName;
}

I have some concerns over the output I receive. First of all there are some warnings. I believe it is because i have missed some annotations which should be present. Any suggestions or guidance here please?

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.sun.xml.bind.v2.runtime.reflect.opt.Injector (file:/C:/Users/R.Premsagar/.m2/repository/org/glassfish/jaxb/jaxb-runtime/2.3.0-b170127.1453/jaxb-runtime-2.3.0-b170127.1453.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int)
WARNING: Please consider reporting this to the maintainers of com.sun.xml.bind.v2.runtime.reflect.opt.Injector
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

Education(School=School(name=ABC, location=Mangalore, Student=[Student(gender=M, housepincode=575020, FirstName=VJ, LastName=K), Student(gender=M, housepincode=575002, FirstName=S, LastName=K)]))

Also, the heirarchy of output does not seem correct to me, Eg: School = School tag? I want to print this output to a separate XML to verify the result. However the script fails

Main program:

List<Education> Entries = new ArrayList<Education>();
        try {
            File xmlFile = new File("Withattributes.xml");
            JAXBContext jaxbContext;
            jaxbContext = JAXBContext.newInstance(Education.class);                          
            Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();        
            Education entries = (Education) jaxbUnmarshaller.unmarshal(xmlFile);    
            Entries.add(entries); //storing objects in a list for later use
            
            
            //--------------------------------------------------------------
            JAXBContext jaxbContext_w = JAXBContext.newInstance(Education.class);           
            Marshaller jaxbMarshaller = jaxbContext_w.createMarshaller();
            jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            jaxbMarshaller.marshal(jaxbContext_w, System.out);
}
        catch (JAXBException e) 
        {
            e.printStackTrace();
        } catch (FactoryConfigurationError e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
}

I get the following error message:

Neither the class com.sun.xml.bind.v2.runtime.JAXBContextImpl nor one of the associated superclasses is known to this context.
at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getBeanInfo(JAXBContextImpl.java:593)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:482)
    at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:328)
    at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:256)
    at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:110)
    at JaxbExample.main(JaxbExample.java:39)

Please let me know what could be done here..


UPDATE:

If the XML is updated to have a structure as below:

<?xml version="1.0" encoding="UTF-8"?>
<Education>
    <School name="ABC" location="Mangalore">
        <Student gender="M" housepincode="575020">
            <FirstName>VJ</FirstName>
            <LastName>K</LastName>
            <Hobbies>
                  <Hobby time = "M">Novels</Hobby>
                  <Hobby time = "A">Gaming</Hobby>
            </Hobbies>
        </Student>
    </School>
</Education>

Now the class of Student.java would change to

@Data
@XmlAccessorType(XmlAccessType.FIELD)
public class Student {      
    @XmlAttribute
    private String gender;  
    @XmlAttribute
    private String housepincode;    
    @XmlElement(name = "FirstName")
    private String FirstName;
    @XmlElement(name = "LastName")
    private String LastName;
    @XmlElementWrapper(name ="Hobbies")
    @XmlElement(name="Hobby")
    private List<Hobby> Hobbies;
}

and the Hobby.java

@Data
@XmlAccessorType(XmlAccessType.FIELD)
public class Hobby{
    
    @XmlAttribute(name="Time")
    private String Time;
    
    @XmlElement
    private String Hobby;

}

With this modification I do not get the value of the Hobby tag. I get the list of Hobbies and values something like this:

            Hobbies>
                  <Hobby time = "M"/>
                  <Hobby time = "A"/>
            </Hobbies>

Could you correct me here please?


Solution

  • First issue: I am no expert on JAXB so I do not have much idea on what could cause the warnings specified here but I tried following the code and it seems to work fine for me without any warnings. If you want to get more understanding then the best thing to do is search about it and read the doc to get more information. But my guess is that it's happening due to your second issue.

    Also, I am using Moxy which is an extension of JAXB developed by Eclipse-link. It has many additional benefits compared to simple JAXB

    Second issue: obvious error is in this line:

    jaxbMarshaller.marshal(jaxbContext_w, System.out);
    

    why are you providing jaxbContext_w as an input to it. It should be the unmarshalled values. In your case, it is entries.

    Following is the complete code: Education:

    @Data
    @XmlRootElement(name = "Education")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Education {
        public School School;
    }
    

    School:

    @Data
    @XmlAccessorType(XmlAccessType.FIELD)
    public class School {
        @XmlAttribute
        private String name;
    
        @XmlAttribute
        private String location;
    
        @XmlElement
        private List<Student> Student;
    }
    

    Student:

    @Data
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Student {
        @XmlAttribute
        private String gender;
    
        @XmlAttribute
        private String housepincode;
    
        @XmlElement
        private String FirstName;
    
        @XmlElement
        private String LastName;
    }
    

    Main:

    
    public class Main {
        public static void main(String[] args) throws JAXBException, XMLStreamException {
            final InputStream inputStream = Main.class.getClassLoader().getResourceAsStream("students.xml");
            final XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream);
            final Unmarshaller unmarshaller = JAXBContext.newInstance(Education.class).createUnmarshaller();
            final Education education = unmarshaller.unmarshal(xmlStreamReader, Education.class).getValue();
            System.out.println(education.toString());
    
            Marshaller marshaller = JAXBContext.newInstance(Education.class).createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            marshaller.marshal(education, System.out);
        }
    }
    
    

    Output (Without any error or warnings):

    
    Education(School=School(name=ABC, location=Mangalore, Student=[Student(gender=M, housepincode=575020, FirstName=VJ, LastName=K), Student(gender=M, housepincode=575002, FirstName=S, LastName=K)]))
    <Education>
       <School name="ABC" location="Mangalore">
          <Student gender="M" housepincode="575020">
             <FirstName>VJ</FirstName>
             <LastName>K</LastName>
          </Student>
          <Student gender="M" housepincode="575002">
             <FirstName>S</FirstName>
             <LastName>K</LastName>
          </Student>
       </School>
    </Education>