joniles / mpxj

Primary repository for MPXJ library

Home Page:http://www.mpxj.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Problems using mpxj with spring boot 3

fhuitfeldt-shoreline opened this issue · comments

Basically, it worked before (spring boot 2.7.2), but after upgrading to spring boot 3, I get this error message when instantiating an "MSPDIWriter": java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException

The issue is that the JAXBException is no longer available on that location. It can be added to the project via a maven/gradle dependency but then the location is: jakarta.xml.bind

I apapreciate that this is open source software, so I am not complaining, just asking for your thoughts on this :-)

Hi @fhuitfeldt-shoreline thanks for the note.

Not sure what's happening with Spring Boot, the necessary JAXB libraries are included as an explicit dependency in the MPXJ POM as they're no longer available by default as part of the JRE after Java 8. Here's a screenshot from IntelliJ IDEA showing the dependencies for a sample project using MPXJ. This is using Java 20:

image

As I understand it, the bind-api package defines the interfaces, and the org.glassfish.jaxb package provides the implementation.

Unfortunately I don't use Spring Boot, but hopefully it's just a configuration issue to ensure MPXJ's dependencies are being used.

Let me know how you get on!

Hi Jon,

thank you very much for your time and your suggestions. After further digging, we have determined that our issue is that mpxj has a dependency on the bind-api in version 2.3.2, whereas the new hibernate (6.2.2) which is required by spring boot 3 brings a dependency for version 4.0.2.

Normally this would not necessarily present a problem, but since the package names were changed inside the bind-api package, it does :-)

Ah! It's a shame the package name changed. Ordinarily I'd just be able to update the dependency in the pom, and assuming no compatibility issues, release a new version. There are a few more moving parts with MPXJ due to the use of IKVM to create a .Net version. Bear with me, I'll take a look at this once I can set aside some time.

Thank you for your kind response. We considered offering to lend a hand with the update, but we determined that it would either be a slam dunk (in which case our help would not mean much) or rather complicated (in which case we would not be able to help much).

Thank you for your efforts.

We have the same problem here and it would be solvable with the following steps:

  • Change jakarta.xml.bind:jakarta.xml.bind-api to version 4.0.0 in pom.xml
  • Replace usages of javax.xml.bind with jakarta.xml.bind in all .java files.

Unfortunately this seems to require Java 11 now, which would therefore affect the compatibility of the MPXJ library.
Also, I had some errors with creation of the javadoc, but I could create a working jar with mvn clean package -Dmaven.javadoc.skip=true.

Is this something that is acceptable for a future version of MPXJ? To me it looks like the only way to update the jakarta.xml.bind dependency is to drop the support of Java 8.

I created a draft PR, in case it might be helpful:
#557

This is certainly an interesting problem, and will require a strategic solution which will support existing users of both the Java and .Net versions of MPXJ.

I'm not a Spring Boot or Hibernate user, so @fhuitfeldt-shoreline or @stophi-dev would one of you be able to put together a simple "Hello World" Maven or IDEA project I can work with which illustrates the issue? It'll save me some time - thank you!

@stophi-dev @joniles thank you very much for your support. @stophi-dev this is exactly what would be needed for us, but as you say, the main decision here is to determine if the product can drop java 8 support.

Dont stress on our behalf, we have some time to make a decision here, but thank you very much for your support.

For us, this is important, because we want to keep our 3rd-party dependencies up to date.

I created a sample project. You can start it with mvn spring-boot:run and then go to localhost:8080 with your browser.
To reproduce the issue, you need to update spring-boot-starter-parent in the pom to 3.1.2. If you have any further questions, or if it doesn't work, please let me know.

SpringBootWithMPXJ.zip

@stophi-dev thanks for creating the example for me, I will look at this as soon as I can.

Hello! I've had a look at this and it appears that a workable solution is to update MPXJ to use JAXB 3. This will maintain compatibility with Java 1.8, and is also forward compatible with JAXB 4 as used by the more recent Spring Boot releases.

I have a branch with the necessary changes, but I won't be in a position to complete testing on this until late next week, at which point I will release a new version of MPXJ.

One gotcha which I assume is due to the way dependencies are handled by Maven is that if your project is using the 2.x version of Spring Boot you'll need to explicitly add these to you dependencies to force the new version of JAXB to be included:

<dependency>
  <groupId>com.sun.activation</groupId>
  <artifactId>jakarta.activation</artifactId>
  <version>2.0.1</version>
</dependency>

<dependency>
  <groupId>jakarta.xml.bind</groupId>
  <artifactId>jakarta.xml.bind-api</artifactId>
  <version>3.0.1</version>
</dependency>

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

There may be a better way of doing this, but when you just have MPXJ and Spring Boot in the dependencies Maven appears to ignore the newer versions of JAXB from the MPXJ dependencies.

Also, this is a limited test case so I'm assuming Spring Boot's own use of JAXB isn't affected by adding these added dependencies.

As noted above this is only required if you were to continue using Spring Boot 2.x, once you get to Spring Boot 3.x everything should just work without having to add any new dependencies.

Also, this is a limited test case so I'm assuming Spring Boot's own use of JAXB isn't affected by adding these added dependencies.

I somehow suspect that this is problematic, because it affects usages of older jakarta.xml.bind-api versions, as the classes moved to a different namespace. I expect every attempt to access the old classes to fail.

It would work to add both jakarta.xml.bind-ap library versions, as they use a distinct namespaces, but Maven does not allow to use the same artifact with two different versions. Jakarta should have changed the artifactId as well, then there would be no problem, I guess.

However, for us, your approach sounds fine. We don't plan to use Spring Boot 2.x with the newest MPXJ version. We already switched to Spring Boot 3 and currently use the workaround to add the old jakarta.xml.bind-ap library as a file based dependency. This tricks the build tool (in our case Gradle) to think that those are distinct artifacts, so we can have both on the classpath. This seems to work fine, as the namespaces don't overlap.

I've had a bit of a rethink on this one. Updating to JAXB3 is a fairly big upheaval at the moment, with the potential to break code unexpectedly for existing applications (for example, Spring Boot 2.x users who are not in a position to update to Spring Boot 3.x immediately). What I think would be better would be a workaround for people updating their dependencies to use Spring Boot 3.x (or creating a new application with Spring Boot 3.x as their starting point). We can review the workaround in due course and make JAXB3 the default in MPXJ when enough users are likely to have updated their dependencies (or the workaround starts to cause its own problems).

I did some more digging and found documentation on the dependencyManagement section in the POM, which exists to allow your application POM to force the use of specific dependency versions... which is exactly what we need. In this case, adding the following entry to your example POM just before the parent tag ensures that the correct versions of JAXB are available for MPXJ to work with Spring Boot 3.x:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>jakarta.activation</groupId>
      <artifactId>jakarta.activation-api</artifactId>
      <version>1.2.1</version>
    </dependency>
    <dependency>
      <groupId>jakarta.xml.bind</groupId>
      <artifactId>jakarta.xml.bind-api</artifactId>
      <version>2.3.2</version>
    </dependency>
    <dependency>
      <groupId>org.glassfish.jaxb</groupId>
      <artifactId>jaxb-runtime</artifactId>
      <version>2.3.2</version>
    </dependency>
  </dependencies>
</dependencyManagement>

This suffers from the same potential issue as the migration to JAXB3 noted in the previous comment: if something you are using within Spring Boot (or indeed your own code) relies on the JAXB3 API or later, this workaround will probably cause your application to fail. The attraction of this workaround is that we've moved the problem from potentially causing all existing Spring Boot 2.x code to fail unexpectedly when the MPXJ version is updated, to only affecting users who are explicitly updating to Spring Boot 3.x AND are making use of JAXB elsewhere in their application.

I hope that long description makes sense - let me know what you think.

So did I understand correctly that you don't want to merge #561?
But instead you are suggesting this workaround? If I understood your comment correctly, you acknowledged that it does not work with Spring Boot 3 and JAXB3? So it does not solve the problem for me.
And also, I would be locked in to old versions of those libraries and run into trouble if a critical CVE pops up.

If I understood your comment correctly, you acknowledged that it does not work with Spring Boot 3 and JAXB3? So it does not solve the problem for me.

The workaround I've suggested works with Spring Boot 3, as tested with the sample project.

And also, I would be locked in to old versions of those libraries and run into trouble if a critical CVE pops up.

No, that's not the intention. The difficulty we have is that there is no ideal solution to this. If I migrate MPXJ to JAXB3, I will potentially need to explain to a subset of existing users why their code stops working when they update to the latest version of MPXJ and try to work out how to fix it (e.g. Spring Boot 2.x users, but there may be others, and the fix may involve making these users update their own code to JAXB3). If we move forward with the workaround, most existing users will be unaffected, but Spring Boot 3.x users will need to add the extra configuration. The workaround will only remain useful if it ensures that Spring Boot 3.x users code continues to work (i.e. it doesn't cause issues for other parts of the Spring Boot framework or the applications built on it) and these older libraries are still "safe" to use (no CVEs or other major bugs). If at any point the workaround is no longer tenable, then the only remaining option would be to update to JAXB3.

So did I understand correctly that you don't want to merge #561?

I haven't decided either way yet. I'm trying to find the "least worst" option, as both approaches have disadvantages. To help make the decision it would be useful to get your feedback from trying the workaround with your code, as at the moment I've only seen it working with the sample application. If it throws up other problems then clearly the workaround is not tenable.

So in my project, I need jakarta.xml.bind with version 4 on the classpath. A workaround would only be feasable if I can have version 2 and version 4 on the classpath. (They don't colide as they use different package names). But I don't prefer this solution because I'd like to get rid of the old version.
So at least for me the proposed workaround doesn't work. What works for me is #561, so I am clearly in favor of that solution. 🙂

I understand that updating JAXB is a breaking change to all users and this needs to be carried out very thoughtfully. So maybe it is too early and you need to hear more opinions about it.
However, you can do this now or you can do it later, but ultimately the breaking change will come and there is no way to avoid it. And over time there will be more and more users that use the new jakarta.xml.bind version and therefore can't use MPXJ.

I created another example based on this project. This now contains a SOAP endpoint which itself uses JAXB and so you can't pin the dependency to the old JAXB version anymore.

You can send a request to the SOAP endpoint with

curl --header "content-type: text/xml" -d '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:gs="http://www.javatechie.com/spring/soap/api/loanEligibility"><soapenv:Header/><soapenv:Body><gs:CustomerRequest><gs:customerName>John Doe</gs:customerName><gs:age>40</gs:age><gs:cibilScore>700</gs:cibilScore><gs:employmentMode>employed</gs:employmentMode><gs:yearlyIncome>300000</gs:yearlyIncome></gs:CustomerRequest></soapenv:Body></soapenv:Envelope>' http://localhost:8080/ws

spring-boot-soap-ws.zip

@stophi-dev I may have a way forward with this. In my local testing I've updated MPXJ to use the javax.xml and com.sun.xml versions of the JAXB 2 packages rather than the jakarta.xml and org.glassfish packages, so MPXJ's POM now looks like this:

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

<dependency>
	<groupId>com.sun.xml.bind</groupId>
	<artifactId>jaxb-impl</artifactId>
	<version>2.3.8</version>
</dependency>

This gets us around the initial problem of the collision between the two different jakarta versions, so your original Spring Boot demo works fine.

There is a minor issue with the SOAP demo you recently provided, in that for some reason Maven wants to bump the com.sun.xml.bind:jaxb-impl package up to the latest 4.x version, which you can see in this output from mvn dependency:tree:

[INFO] |  +- com.sun.xml.bind:jaxb-impl:jar:4.0.3:compile (version managed from 2.3.8)

So I've had to add this to the SOAP example POM file to tell Maven not to do this:

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>com.sun.xml.bind</groupId>
			<artifactId>jaxb-impl</artifactId>
			<version>2.3.8</version>
		</dependency>
	</dependencies>
</dependencyManagement>

which ensures that MPXJ's dependency isn't automatically updated. (I tried adding a strict dependency to the MPXJ POM, but that seems to be ignored too, so I'm not sure how MPXJ itself can force Maven to respect the dependency version it is asking for).

With this in place the SOAP demo works, and I get this response from curl:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
	<SOAP-ENV:Header/>
	<SOAP-ENV:Body>
		<ns2:Acknowledgement xmlns:ns2="http://www.javatechie.com/spring/soap/api/loanEligibility">
			<ns2:isEligible>true</ns2:isEligible>
			<ns2:approvedAmount>500000</ns2:approvedAmount>
		</ns2:Acknowledgement>
	</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

So it appears that the "out of the box" JAXB 4 API and implementation works fine alongside the JAXB 2 API and implementation used by MPXJ. I still have some testing to do before I can release this, but it seems like a much better way forward than the other approaches we're considering.

It requests version 4.0.3 because non-mpxj-parts could be built with version 4.0.3 and declare this as a dependency. To force your project to an older version can cause nasty runtime exceptions if it tries to access code that does exist in version 4.0.3, but not in version 2.3.8. Even if it works for a small sample project, doesn't mean that it works reliably in larger projects.

Also, as I said, I want to get rid of old verisons in my dependencies. Pinning your project to an outdated library version is never a good idea to my experience.

Currently we include both versions (4.0.3 and 2.3.8) in our project, which works, because the package names are different. But in the end, to get rid of version 2.3.8, we might be forced to use a forked version of MPXJ that includes #561 then.

The change I've just made to switch MPXJ to use javax.xml.bind:jaxb-api and com.sun.xml.bind:jaxb-impl ensures that MPXJ is using dependencies with completely different Maven coordinates than the current Jakarta JAXB 3 and JAXB 4 implementations, and the package namespaces they use do not overlap with the Jakarta implementations. Your code and the frameworks you are using should have no knowledge of the presence of JAXB 2, and should be completely unaffected by it.

Also, as I said, I want to get rid of old verisons in my dependencies.

This is a laudable aspiration, but where you have transitive dependencies (for example MPXJ's dependencies: Jackcess, POI, sqlite-jdbc, jgoodies, jsoup etc), it is likely that an update across major versions (e.g. 1.x to 2.x) of any of these transitive dependencies will carry a risk of failure as major version bumps are likely to include breaking changes for the parent dependency. By all means you should be able to update between minor or patch versions and should expect no issues, but updates across major versions for any of these dependencies is less certain to work.

Pinning your project to an outdated library version is never a good idea to my experience.

I agree. However, there are constraints I need to work within. One is the use of IKVM to produce a .Net version of MPXJ. This will only support Java 8 for the foreseeable future, so even if I do move MPXJ to JAXB 3, I would not be able to move the dependency beyond that. Secondly, the move to JAXB 3 has the potential be a breaking change for some of my downstream users. Spring Boot 2.x users would be forced to migrate to Spring Boot 3.x, and in the worst case MPXJ users who have built their own XML handling using JAXB 2 would need to regenerate their schema code as the old JAXB 2 code is not compatible with the JAXB 3 runtime.

I'm hoping that the change I've suggested will be the "least worst" option for the moment: we can still use IKVM, Spring Boot 2.x and 3.x will work, and existing JAXB 2 code does not need to be regenerated.

Regarding the requirement to include a dependencyManagement section in your POM to prevent the Sun 2.x version of JAXB from being update o the 4.x version:

It requests version 4.0.3 because non-mpxj-parts could be built with version 4.0.3 and declare this as a dependency. To force your project to an older version can cause nasty runtime exceptions if it tries to access code that does exist in version 4.0.3, but not in version 2.3.8. Even if it works for a small sample project, doesn't mean that it works reliably in larger projects.

The Sun JAXB 2 implementation and the new Jakarta JAXB implementations are completely independent, so code that depends on JAXB 2 and JAXB 3 or 4 can coexist, as you've already found with your workaround. That doesn't explain why com.sun.xml.bind:jaxb-impl is being bumped to 4.0.3, so I did some more digging, and with the help of mvn help:effective-pom -Dverbose I can see that org.glassfish.jaxb:jaxb-bom:4.0.3 has its own dependencyManagement entry to force com.sun.xml.bind:jaxb-impl to version 4.0.3. I think I understand what they are trying to achieve but in this case it is forcing an otherwise working dependency to an incompatible version. This entry in the BOM is, as far as I can see, the only linkage between the Jakarta JAXB 3 and 4 implementations and the Sun JAXB 2 implementation I'm proposing to use.

I can understand you reasoning, although I personally would prefer a different solution. Thank you for your efforts.
Just a few last words:

if I do move MPXJ to JAXB 3, I would not be able to move the dependency beyond that.

Well, at least this wouldn't force users of MPXJ to include an old JAXB version. They can then just go with version 4.

In my project, the old JAXB version is ONLY required because of MPXJ. All other dependencies are totally fine with version 4. The update across the major version of JAXB is working, only MPXJ is lagging behind.
I know that major version updates contain a risk, but it is a risk you have to take sooner or later anyway. And staying on old versions add additional risks and it makes the transition usually even a little bit harder. You then have to regenerate even more schemas etc. I don't say that you should jump on each new major update immediately. But JAXB 4 is already more than a year old, so it should already be beyond the teething problems.