2021-12-27

How to convert XML to String with JAXB and spring-boot?

When I run a mvn spring-boot:run on the folder that has the pom.xml file the application starts and serializes a POJO into a XML correctly, but when I do it by going to the target folder and starting it by using java -jar in the jar file I get javax.xml.bind.JAXBException: Implementation of JAXB-API has not been found on module path or classpath caused by .java.lang.ClassNotFoundException: com.sun.xml.internal.bind.v2.ContextFactory.

In my maven I have the following JAXB dependencies:

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>

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

Here's the method that serializes a POJO into XML:

private static final Pattern REMOVE_HEADER = Pattern.compile("\\<\\?xml(.+?)\\?\\>");

public static String toXML(final Object data) {
    try {
        final var jaxbMarshaller = JAXBContext.newInstance(data.getClass()).createMarshaller();
        jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        final var sw = new StringWriter();
        jaxbMarshaller.marshal(data, sw);
        return XMLUtils.REMOVE_HEADER.matcher(sw.toString()).replaceAll(StringUtils.EMPTY).strip();
    } catch (final JAXBException e) {
        XMLUtils.LOGGER.error("Error while converting POJO to XML. ERROR: {}.", e.getMessage(), e);
    }

    return "";
}

Here's the log when I start the application with java -jar:

javax.xml.bind.JAXBException: Implementation of JAXB-API has not been found on module path or classpath.
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:278)
    at javax.xml.bind.ContextFinder.find(ContextFinder.java:421)
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:721)
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:662)
    at br.com.site.system.shared.util.XMLUtils.toXML(XMLUtils.java:56)
    at br.com.site.system.shared.util.FinanTokenEncoderUtils.encodeBase64(FinanTokenEncoderUtils.java:41)
    at br.com.site.system.converter.PlanConverter.lambda$convert$0(PlanConverter.java:84)
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
    at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1654)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:952)
    at java.base/java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:926)
    at java.base/java.util.stream.AbstractTask.compute(AbstractTask.java:327)
    at java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:746)
    at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
    at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.helpCC(ForkJoinPool.java:1115)
    at java.base/java.util.concurrent.ForkJoinPool.awaitJoin(ForkJoinPool.java:1687)
    at java.base/java.util.concurrent.ForkJoinTask.doInvoke(ForkJoinTask.java:411)
    at java.base/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:736)
    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateParallel(ReduceOps.java:919)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
    at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
    at br.com.site.system.converter.PlanConverter.convert(PlanConverter.java:90)
    at br.com.site.system.converter.OperationConverter.lambda$convert$0(OperationConverter.java:59)
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
    at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1654)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.Nodes$SizedCollectorTask.compute(Nodes.java:1886)
    at java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:746)
    at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
    at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
    at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
    at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
    at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
Caused by: java.lang.ClassNotFoundException: com.sun.xml.internal.bind.v2.ContextFactory
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
    at javax.xml.bind.ServiceLoaderUtil.nullSafeLoadClass(ServiceLoaderUtil.java:122)
    at javax.xml.bind.ServiceLoaderUtil.safeLoadClass(ServiceLoaderUtil.java:155)
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:276)
    ... 35 common frames omitted

Here's an image of the generated spring-boot fat jar with the JAXB dependencies:

JAXB dependencies

And finally here's my dependency tree:

--- maven-dependency-plugin:3.2.0:tree (default-cli) @ system-infrastructure ---
br.com.site.system:system-infrastructure:jar:0.0.1-SNAPSHOT
+- br.com.site.system:system-core:jar:0.0.1-SNAPSHOT:compile
|  +- org.apache.commons:commons-lang3:jar:3.12.0:compile
|  +- javax.validation:validation-api:jar:2.0.1.Final:compile
|  +- javax.xml.bind:jaxb-api:jar:2.3.1:compile
|  |  \- javax.activation:javax.activation-api:jar:1.2.0:compile
|  \- org.glassfish.jaxb:jaxb-runtime:jar:2.3.5:runtime
|     +- org.glassfish.jaxb:txw2:jar:2.3.5:runtime
|     +- com.sun.istack:istack-commons-runtime:jar:3.0.12:runtime
|     \- com.sun.activation:jakarta.activation:jar:1.2.2:runtime
+- io.projectreactor:reactor-test:jar:3.4.12:test
|  \- io.projectreactor:reactor-core:jar:3.4.12:compile
|     \- org.reactivestreams:reactive-streams:jar:1.0.3:compile
+- org.junit.jupiter:junit-jupiter-api:jar:5.8.1:test
|  +- org.opentest4j:opentest4j:jar:1.2.0:test
|  +- org.junit.platform:junit-platform-commons:jar:1.8.1:test
|  \- org.apiguardian:apiguardian-api:jar:1.1.2:test
+- org.mockito:mockito-inline:jar:4.0.0:test
|  \- org.mockito:mockito-core:jar:4.0.0:test
|     +- net.bytebuddy:byte-buddy:jar:1.11.22:test
|     +- net.bytebuddy:byte-buddy-agent:jar:1.11.22:test
|     \- org.objenesis:objenesis:jar:3.2:test
+- org.pitest:pitest-junit5-plugin:jar:0.15:test
+- org.springframework.boot:spring-boot-starter-test:jar:2.6.1:test
|  +- org.springframework.boot:spring-boot-starter:jar:2.6.1:compile
|  |  +- org.springframework.boot:spring-boot:jar:2.6.1:compile
|  |  |  \- org.springframework:spring-context:jar:5.3.13:compile
|  |  |     +- org.springframework:spring-aop:jar:5.3.13:compile
|  |  |     \- org.springframework:spring-expression:jar:5.3.13:compile
|  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:2.6.1:compile
|  |  +- org.springframework.boot:spring-boot-starter-logging:jar:2.6.1:compile
|  |  |  +- ch.qos.logback:logback-classic:jar:1.2.7:compile
|  |  |  |  \- ch.qos.logback:logback-core:jar:1.2.7:compile
|  |  |  +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.14.1:compile
|  |  |  |  \- org.apache.logging.log4j:log4j-api:jar:2.14.1:compile
|  |  |  \- org.slf4j:jul-to-slf4j:jar:1.7.32:compile
|  |  +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
|  |  \- org.yaml:snakeyaml:jar:1.29:compile
|  +- org.springframework.boot:spring-boot-test:jar:2.6.1:test
|  +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.6.1:test
|  +- com.jayway.jsonpath:json-path:jar:2.6.0:test
|  |  +- net.minidev:json-smart:jar:2.4.7:test
|  |  |  \- net.minidev:accessors-smart:jar:2.4.7:test
|  |  |     \- org.ow2.asm:asm:jar:9.1:test
|  |  \- org.slf4j:slf4j-api:jar:1.7.32:compile
|  +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:compile
|  |  \- jakarta.activation:jakarta.activation-api:jar:1.2.2:compile
|  +- org.assertj:assertj-core:jar:3.21.0:test
|  +- org.hamcrest:hamcrest:jar:2.2:test
|  +- org.junit.jupiter:junit-jupiter:jar:5.8.1:test
|  |  +- org.junit.jupiter:junit-jupiter-params:jar:5.8.1:test
|  |  \- org.junit.jupiter:junit-jupiter-engine:jar:5.8.1:test
|  |     \- org.junit.platform:junit-platform-engine:jar:1.8.1:test
|  +- org.mockito:mockito-junit-jupiter:jar:4.0.0:test
|  +- org.skyscreamer:jsonassert:jar:1.5.0:test
|  |  \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
|  +- org.springframework:spring-core:jar:5.3.13:compile
|  |  \- org.springframework:spring-jcl:jar:5.3.13:compile
|  +- org.springframework:spring-test:jar:5.3.13:test
|  \- org.xmlunit:xmlunit-core:jar:2.8.3:test
+- org.springframework.boot:spring-boot-starter-actuator:jar:2.6.1:compile
|  +- org.springframework.boot:spring-boot-actuator-autoconfigure:jar:2.6.1:compile
|  |  +- org.springframework.boot:spring-boot-actuator:jar:2.6.1:compile
|  |  \- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.13.0:compile
|  \- io.micrometer:micrometer-core:jar:1.8.0:compile
|     +- org.hdrhistogram:HdrHistogram:jar:2.1.12:compile
|     \- org.latencyutils:LatencyUtils:jar:2.0.3:runtime
+- org.springframework.boot:spring-boot-starter-validation:jar:2.6.1:compile
|  +- org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.55:compile
|  \- org.hibernate.validator:hibernate-validator:jar:6.2.0.Final:compile
|     +- jakarta.validation:jakarta.validation-api:jar:2.0.2:compile
|     +- org.jboss.logging:jboss-logging:jar:3.4.2.Final:compile
|     \- com.fasterxml:classmate:jar:1.5.1:compile
+- org.springframework.boot:spring-boot-starter-webflux:jar:2.6.1:compile
|  +- org.springframework.boot:spring-boot-starter-json:jar:2.6.1:compile
|  |  +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.13.0:compile
|  |  \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.13.0:compile
|  +- org.springframework.boot:spring-boot-starter-reactor-netty:jar:2.6.1:compile
|  |  \- io.projectreactor.netty:reactor-netty-http:jar:1.0.13:compile
|  |     +- io.netty:netty-codec-http:jar:4.1.70.Final:compile
|  |     |  +- io.netty:netty-common:jar:4.1.70.Final:compile
|  |     |  +- io.netty:netty-buffer:jar:4.1.70.Final:compile
|  |     |  +- io.netty:netty-transport:jar:4.1.70.Final:compile
|  |     |  +- io.netty:netty-codec:jar:4.1.70.Final:compile
|  |     |  \- io.netty:netty-handler:jar:4.1.70.Final:compile
|  |     +- io.netty:netty-codec-http2:jar:4.1.70.Final:compile
|  |     +- io.netty:netty-resolver-dns:jar:4.1.70.Final:compile
|  |     |  +- io.netty:netty-resolver:jar:4.1.70.Final:compile
|  |     |  \- io.netty:netty-codec-dns:jar:4.1.70.Final:compile
|  |     +- io.netty:netty-resolver-dns-native-macos:jar:osx-x86_64:4.1.70.Final:compile
|  |     |  \- io.netty:netty-resolver-dns-classes-macos:jar:4.1.70.Final:compile
|  |     +- io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.70.Final:compile
|  |     |  +- io.netty:netty-transport-native-unix-common:jar:4.1.70.Final:compile
|  |     |  \- io.netty:netty-transport-classes-epoll:jar:4.1.70.Final:compile
|  |     \- io.projectreactor.netty:reactor-netty-core:jar:1.0.13:compile
|  |        \- io.netty:netty-handler-proxy:jar:4.1.70.Final:compile
|  |           \- io.netty:netty-codec-socks:jar:4.1.70.Final:compile
|  +- org.springframework:spring-web:jar:5.3.13:compile
|  |  \- org.springframework:spring-beans:jar:5.3.13:compile
|  \- org.springframework:spring-webflux:jar:5.3.13:compile
+- net.logstash.logback:logstash-logback-encoder:jar:7.0.1:compile
|  \- com.fasterxml.jackson.core:jackson-databind:jar:2.13.0:compile
|     +- com.fasterxml.jackson.core:jackson-annotations:jar:2.13.0:compile
|     \- com.fasterxml.jackson.core:jackson-core:jar:2.13.0:compile
+- org.springdoc:springdoc-openapi-webflux-ui:jar:1.6.1:compile
|  +- org.springdoc:springdoc-openapi-webflux-core:jar:1.6.1:compile
|  |  \- org.springdoc:springdoc-openapi-common:jar:1.6.1:compile
|  |     +- io.swagger.core.v3:swagger-models:jar:2.1.11:compile
|  |     +- io.swagger.core.v3:swagger-annotations:jar:2.1.11:compile
|  |     +- io.swagger.core.v3:swagger-integration:jar:2.1.11:compile
|  |     |  \- io.swagger.core.v3:swagger-core:jar:2.1.11:compile
|  |     |     \- com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:jar:2.13.0:compile
|  |     \- io.github.classgraph:classgraph:jar:4.8.116:compile
|  +- org.webjars:swagger-ui:jar:4.1.3:compile
|  \- org.webjars:webjars-locator-core:jar:0.48:compile
\- org.mapstruct:mapstruct:jar:1.4.2.Final:compile

And finally, here are my model classes:

import java.util.Objects;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "finans")
@XmlAccessorType(XmlAccessType.FIELD)
public class FinanTokenDTO {

    @XmlAttribute
    private String pln;

    @XmlAttribute
    private String ope;

    @XmlAttribute
    private String mod;

    @XmlAttribute
    private String mis;

    @XmlAttribute
    private String val;

    @XmlAttribute
    private String car;

    @XmlAttribute
    private String dti;

    @XmlAttribute
    private String dtf;

    @XmlAttribute
    private String ota;

    @XmlElement
    private FinanDTO finan;

    public FinanTokenDTO() {
        super();
    }

    public FinanTokenDTO(final String plnParam, final String opeParam, final String modParam, final String misParam,
            final String valParam, final String carParam, final String dtiParam, final String dtfParam,
            final String otaParam, final FinanDTO finanParam) {
        this();
        this.pln = plnParam;
        this.ope = opeParam;
        this.mod = modParam;
        this.mis = misParam;
        this.val = valParam;
        this.car = carParam;
        this.dti = dtiParam;
        this.dtf = dtfParam;
        this.ota = otaParam;
        this.finan = finanParam;
    }

    public String getPln() {
        return this.pln;
    }

    public void setPln(final String plnParam) {
        this.pln = plnParam;
    }

    public String getOpe() {
        return this.ope;
    }

    public void setOpe(final String opeParam) {
        this.ope = opeParam;
    }

    public String getMod() {
        return this.mod;
    }

    public void setMod(final String modParam) {
        this.mod = modParam;
    }

    public String getMis() {
        return this.mis;
    }

    public void setMis(final String misParam) {
        this.mis = misParam;
    }

    public String getVal() {
        return this.val;
    }

    public void setVal(final String valParam) {
        this.val = valParam;
    }

    public String getCar() {
        return this.car;
    }

    public void setCar(final String carParam) {
        this.car = carParam;
    }

    public String getDti() {
        return this.dti;
    }

    public void setDti(final String dtiParam) {
        this.dti = dtiParam;
    }

    public String getDtf() {
        return this.dtf;
    }

    public void setDtf(final String dtfParam) {
        this.dtf = dtfParam;
    }

    public String getOta() {
        return this.ota;
    }

    public void setOta(final String otaParam) {
        this.ota = otaParam;
    }

    public FinanDTO getFinan() {
        return this.finan;
    }

    public void setFinan(final FinanDTO finanParam) {
        this.finan = finanParam;
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.car, this.dtf, this.dti, this.finan, this.mis, this.mod, this.ope, this.ota, this.pln,
                this.val);
    }

    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }

        if (obj == null || this.getClass() != obj.getClass()) {
            return false;
        }

        final var other = (FinanTokenDTO) obj;
        return Objects.equals(this.car, other.car) && Objects.equals(this.dtf, other.dtf)
                && Objects.equals(this.dti, other.dti) && Objects.equals(this.finan, other.finan)
                && Objects.equals(this.mis, other.mis) && Objects.equals(this.mod, other.mod)
                && Objects.equals(this.ope, other.ope) && Objects.equals(this.ota, other.ota)
                && Objects.equals(this.pln, other.pln) && Objects.equals(this.val, other.val);
    }

    @Override
    public String toString() {
        return new StringBuilder().append("FinanTokenDTO [pln=").append(this.pln).append(", ope=").append(this.ope)
                .append(", mod=").append(this.mod).append(", mis=").append(this.mis).append(", val=").append(this.val)
                .append(", car=").append(this.car).append(", dti=").append(this.dti).append(", dtf=").append(this.dtf)
                .append(", ota=").append(this.ota).append(", finan=").append(this.finan)
                .append('}').toString();
    }

import java.util.Objects;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "finan")
@XmlAccessorType(XmlAccessType.FIELD)
public class FinanDTO {

    @XmlAttribute
    private String prd;

    @XmlAttribute
    private String pkg;

    @XmlAttribute
    private String val;

    @XmlAttribute
    private String fty;

    @XmlAttribute
    private String safetyPay;

    public FinanDTO() {
        super();
    }

    public FinanDTO(final String prdParam, final String pkgParam, final String valParam, final String ftyParam,
            final String safetypayParam) {
        this();
        this.prd = prdParam;
        this.pkg = pkgParam;
        this.val = valParam;
        this.fty = ftyParam;
        this.safetyPay = safetypayParam;
    }

    public String getPrd() {
        return this.prd;
    }

    public void setPrd(final String prdParam) {
        this.prd = prdParam;
    }

    public String getPkg() {
        return this.pkg;
    }

    public void setPkg(final String pkgParam) {
        this.pkg = pkgParam;
    }

    public String getVal() {
        return this.val;
    }

    public void setVal(final String valParam) {
        this.val = valParam;
    }

    public String getFty() {
        return this.fty;
    }

    public void setFty(final String ftyParam) {
        this.fty = ftyParam;
    }

    public String getSafetyPay() {
        return this.safetyPay;
    }

    public void setSafetyPay(final String safetyPayParam) {
        this.safetyPay = safetyPayParam;
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.fty, this.pkg, this.prd, this.safetyPay, this.val);
    }

    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }

        if (obj == null || this.getClass() != obj.getClass()) {
            return false;
        }

        final var other = (FinanDTO) obj;
        return Objects.equals(this.fty, other.fty) && Objects.equals(this.pkg, other.pkg)
                && Objects.equals(this.prd, other.prd) && Objects.equals(this.safetyPay, other.safetyPay)
                && Objects.equals(this.val, other.val);
    }

    @Override
    public String toString() {
        return new StringBuilder().append("FinanDTO [prd=").append(this.prd).append(", pkg=").append(this.pkg)
                .append(", val=").append(this.val).append(", fty=").append(this.fty).append(", safetyPay=")
                .append(this.safetyPay).append('}').toString();
    }
}

I am using java 11 and I am aware that in java 11 JAXB was removed from the SE JDK because it is considered as a EE feature.

I am not able to execute it using mvn spring-boot:run in production environment because of the size of the docker image and security related issues using docker container.

Since Spring Boot generates a fat jar, shouldn't the application run with java -jar applied to the spring boot fat jar generated file the same way as it does with mvn spring-boot:run inside the folder with pom.xml?

EDIT:

After a lot of digging and testing I found that the problem occurs when we try to marshall several valid POJOs using parallel stream instead of stream on the POJOs list. I uploaded the code in java-eleven-jaxb-hell to better understanding, unfortunatelly I cannot change parallelStream to stream because of performance issues. Just to remember, for the problem to happen you have to run java -jar against spring-boot generated fat jar in target folder.



from Recent Questions - Stack Overflow https://ift.tt/3mzDBuz
https://ift.tt/3pul0SH

No comments:

Post a Comment