NoClassDefFoundError after deploying the PDF Java action
A Java action wraps a third-party PDF library to merge invoice PDFs. The developer dropped the JAR into the project's userlib folder and wrote the action with a list of FileDocuments as parameter and String as return type. It works on the developer's laptop. After deployment to the Acceptance environment on Mendix Cloud, every call fails with java.lang.NoClassDefFoundError: com/acme/pdf/FontCache in the application log, while the UI only shows 'Could not create PDF' because the action catches Exception and returns null. The project was upgraded from Mendix 9.24 to 10.6 two weeks ago, and Acceptance was deployed from a feature branch, not the main line.
1. Give a ranked list of explanations for the NoClassDefFoundError and how you would confirm each one.
Read the worked answer
Ranked causes
- The JAR is not in the deployment. Confirm by building the deployment package for the deployed revision and inspecting its lib/userlib contents, and by comparing the revision on the deployed branch with the one where the file was added. A JAR added on a feature branch that was never merged, or a userlib folder excluded from version control, produces exactly this.
- A conflicting version of the same library is already on the classpath. NoClassDefFoundError often means the class was found but its dependency was not, or a different version of the same artifact won. Check for two JARs shipping the same package and for a library the runtime already bundles.
- Runtime/JDK mismatch after the 9.24 to 10.6 upgrade. The library may target a Java version the cloud runtime does not provide, or use APIs removed in the newer JDK. Confirm by checking the runtime's Java version for that environment and the library's required level.
- Lazy loading. The class is only touched at runtime on first PDF generation, so a broken classpath only shows up when the action actually runs, which is why the local test 'passed'.
Confirm, do not guess: the deployment package inspection plus the full stack trace in the application log distinguishes 1 from 2 and 3 in minutes.
2. The catch-and-return-null pattern hid this for a day. Rewrite the error handling for this Java action and explain how the failure should reach the user.
Read the worked answer
What is wrong with the current code
- Catching Exception and returning null converts a classpath defect into a business-level empty result: the microflow cannot tell 'no PDF needed' from 'the library is missing', and nothing is logged.
- It also discards the stack trace, which is the only evidence for the cause.
Rewrite
- Declare the action to throw CoreException (or a modelled error) and let genuine technical failures propagate.
- Log at the boundary with a named logger:
Core.getLogger("PdfMerge"), an error line with the invoice id and the exception, never the document contents or customer data. - Validate inputs explicitly and throw a message-bearing CoreException for business failures (no documents, too many pages), so the microflow can show a specific message rather than a generic one.
- Catch only the exceptions you can act on; a NoClassDefFoundError is an Error, not an Exception, so a broad catch was never going to handle it gracefully anyway.
Reaching the user Let the microflow use custom error handling to show a validation message or an error page with a reference code that maps to the log line. The user needs an actionable message; the developer needs the stack trace. Returning null gives neither.
3. What does the feature-branch deployment tell you about the team's release process, and what would you change?
Read the worked answer
What it tells you
- The environment's content does not correspond to any known release point, so 'works on my machine' and 'works in acceptance' cannot be compared. Reproducing a defect requires knowing the exact revision, which nobody can state.
- Fixes made on the feature branch and fixes made on the main line will diverge; a hotfix applied directly to acceptance is invisible to main line and will be lost at the next merge.
- The Mendix version upgrade landed at the same time as feature work, so two variables changed at once and the NoClassDefFoundError cannot be attributed cleanly.
Changes
- Deploy acceptance from a stable branch line (main line, or a release branch created for the version), and deploy feature branches only to a personal or sprint sandbox.
- Tag the revision deployed to each environment and record it on the environment page, so a defect report carries a revision id.
- Do the version upgrade as its own change, with a regression run of the Java actions and integrations, and verify the libraries against the new runtime before feature work resumes.
- Keep library additions in version control on the same commit as the Java action that needs them, so the classpath and the code can never be separated.