How to use the custom namespaces from the JAXB RI? Error: "org.glassfish.jaxb.namespacePrefixMapper" is not supported

122 Views Asked by At

Previously I was using the Eclipselink Moxy NAMESPACE_PREFIX_MAPPER to provide the custom namespaces to my JaxbContext and Marshaller and everything was working as expected. But I want to move away from the Moxy and use JAXB RI so changed everything.

The only problem that I am facing is related to providing the MarshallerProperties and JAXBContextProperties. I changed them to use from glassfish as per the answer mentioned here and the documentation but after the change I am facing error:

Exception in thread "main" jakarta.xml.bind.JAXBException: property "org.glassfish.jaxb.runtime.marshaller.NamespacePrefixMapper" is not supported
    at org.glassfish.jaxb.runtime.v2.ContextFactory.createContext(ContextFactory.java:126)
    at org.glassfish.jaxb.runtime.v2.ContextFactory.createContext(ContextFactory.java:246)
    at org.glassfish.jaxb.runtime.v2.JAXBContextFactory.createContext(JAXBContextFactory.java:58)
    at jakarta.xml.bind.ContextFinder.find(ContextFinder.java:324)
    at jakarta.xml.bind.JAXBContext.newInstance(JAXBContext.java:392)
    at io.test.convert.MainXML.main(MainXML.java:27)

Following is the completed code I have: Child1.class:

package io.test.convert;

import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;

@XmlRootElement(name = "Child1")
@XmlType(
        name = "Child1",
        propOrder = {
                "name",
                "age",
                "originalName"
        },
        factoryClass = ObjectFactory.class,
        factoryMethod = "createChild1")
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@XmlAccessorType(XmlAccessType.FIELD)
public class Child1 extends Parent {
    private String originalName;
}

Parent.class:

package io.test.convert;

import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlTransient;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
@XmlAccessorType(XmlAccessType.FIELD)
@XmlTransient
@Builder
public class Parent implements Serializable {
    @XmlTransient
    private String type;
    private String name;
    private String age;
}

CustomNamespacePrefixMapper.class:

package io.test.convert;

import org.glassfish.jaxb.runtime.marshaller.NamespacePrefixMapper;
import java.util.HashMap;
import java.util.Map;

public class CustomNamespacePrefixMapper extends NamespacePrefixMapper {
    public static final Map<String, String> NAMESPACE_MAP = Map.of(
            "http://www.w3.org/2001/XMLSchema-instance", "xsi",
            "https://example1.com/", "example1",
            "https://example2.com/", "example2");

    private Map<String, String> namespaceMap;
    public CustomNamespacePrefixMapper(final Map<String, String> namespaceMap) {
        this.namespaceMap = namespaceMap;
    }
    public CustomNamespacePrefixMapper() {
        this(new HashMap<>(NAMESPACE_MAP));
    }
    @Override
    public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
        return namespaceMap.getOrDefault(namespaceUri, suggestion);
    }
}

MainXML.class:

package io.test.convert;

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Marshaller;

import java.util.HashMap;
import java.util.Map;

public class MainXML {
    public static void main(String[] args) throws Exception {
        final Map<String, String> myNamespaces = new HashMap<>();
        myNamespaces.put("test", "https://test.com");
        myNamespaces.put("test2", "https://test2.com");

        final JAXBContext jaxbContext = JAXBContext.newInstance("io.test.convert", Thread.currentThread().getContextClassLoader(),
                new HashMap<>() {
                    {
                        put(
                                "org.glassfish.jaxb.namespacePrefixMapper",
                                new CustomNamespacePrefixMapper());
                    }
                });
        //final JAXBContext jaxbContext = JAXBContext.newInstance("io.test.convert", Thread.currentThread().getContextClassLoader());

        final Marshaller marshaller = jaxbContext.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
        marshaller.setProperty("org.glassfish.jaxb.namespacePrefixMapper", new CustomNamespacePrefixMapper());

        final Child1 child1 = new Child1();
        child1.setName("Batman");
        child1.setAge("30");
        child1.setType("Superhero");
        child1.setOriginalName("Bruce Wayne");

        marshaller.marshal(child1, System.out);
    }
}

Following is my sampleXML.xml:

<Child1>
    <type>ChildType</type>
    <name>Batman</name>
    <age>30</age>
    <originalName>Bruce</originalName>
</Child1>

ObjectFactory.class:

package io.test.convert;

import jakarta.xml.bind.annotation.XmlRegistry;

@XmlRegistry
public final class ObjectFactory {
    private ObjectFactory() {}
    public static Child1 createChild1() {
        return new Child1();
    }
}

When I run I get the error:

Exception in thread "main" jakarta.xml.bind.JAXBException: property "org.glassfish.jaxb.runtime.marshaller.NamespacePrefixMapper" is not supported
    

How to provide the custom namespaces to the JAXBContext/Marshaller so it can be used during the marshalling of the Java objects to XML?

Following are my dependencies:

<dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>

        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-core</artifactId>
            <version>4.0.4</version>
        </dependency>

        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-xjc</artifactId>
            <version>4.0.4</version>
        </dependency>

        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
            <version>4.0.4</version>
        </dependency>

        <dependency>
            <groupId>jakarta.xml.bind</groupId>
            <artifactId>jakarta.xml.bind-api</artifactId>
            <version>4.0.1</version>
        </dependency>
    </dependencies>
1

There are 1 best solutions below

8
Laurent Schoelens On

For jaxb-ri, the property should be org.glassfish.jaxb.namespacePrefixMapper and the value with it should be of type org.glassfish.jaxb.runtime.marshaller.NamespacePrefixMapper

If not, you'll see the exception you've seen before (property not handled or must be of type ...)

Source here

EDIT

In addition to my previous first answer, you should pass the namespaceMapper property in JAXBContext creation.

See supported properties during JAXBContext phase in the following method : org.glassfish.jaxb.runtime.v2.ContextFactory#createContext(java.lang.Class[], java.util.Map<java.lang.String,java.lang.Object>)

You should create your JAXBContext with your current commented line of code :

final JAXBContext jaxbContext = JAXBContext.newInstance("io.test.convert", Thread.currentThread().getContextClassLoader());

If doing so and adding the namespace to Child1 class as @XmlRootElement(name = "Child1", namespace = "https://example1.com/"), you'll get the following output :

<example1:Child1 xmlns:example1="https://example1.com/">
    <name>Batman</name>
    <age>30</age>
    <originalName>Bruce Wayne</originalName>
</example1:Child1>