Skip to content

ROL-2183: Migrate from javax to Jakarta EE 10 - #154

Open
mraible wants to merge 17 commits into
apache:masterfrom
mraible:feature/jakarta-ee-10-migration
Open

ROL-2183: Migrate from javax to Jakarta EE 10#154
mraible wants to merge 17 commits into
apache:masterfrom
mraible:feature/jakarta-ee-10-migration

Conversation

@mraible

@mraible mraible commented Aug 11, 2026

Copy link
Copy Markdown

Migrates Apache Roller from Java EE 8 (javax.*) to Jakarta EE 10 (jakarta.*), upgrading all major frameworks. Each commit corresponds to a migration phase so reviewers can follow the progression. JIRA: ROL-2183

Target versions: Java 17, Servlet 6.0, Struts 7.1.1, Spring 7.0.8, Spring Security 7.0.6, EclipseLink 5.0.1, Jetty 12.0.21, Tomcat 10.1

Phase 0 — Java 17 baseline, Derby 10.16.1.1 (old version does not support Java 17), SLF4J 2.x bridge fix

Phase 1 — Remove unmaintained/obsolete dependencies while still on javax (testable incrementally):

  • OAuth 1.0a removed entirely (14 files deleted, 17 files edited)
  • spring-security-openid removed (filter, beans, OpenID URL detection in RollerUserDetailsService/RollerSession)
  • struts2-bootstrap-plugin removed (unused taglib)
  • XmlRpcServlet and AtomServlet forked into Roller codebase (xmlrpc-server and rome-propono have no Jakarta-compatible releases)

Phase 2 — The big switch (must be done as one coordinated change since Struts 7, Spring 7, and Jakarta APIs are tightly coupled):

  • 320 javax.servlet/javax.mail/javax.activation imports converted to jakarta.* across 89 Java files
  • com.opensymphony.xwork2.* imports migrated to org.apache.struts2.*
  • Deprecated Struts interfaces (ServletRequestAware, RequestAware, HttpParametersAware) replaced with ActionContext lookups
  • Struts 7 runtime fixes: parameter binding is annotation-gated by default (struts.parameters.requireAnnotations=false since Roller's actions predate @StrutsParameter), an OGNL allowlist for Roller packages, the static content path, and the prepare interceptor re-enabled ahead of params
  • Spring Security AccessDecisionManager/RoleVoter removed, migrated to expression-based access control
  • All Maven dependencies swapped to Jakarta equivalents
  • persistence.xml + 29 ORM files updated to Jakarta JPA 3.2 namespace
  • web.xml, struts.xml, tiles.xml, security.xml config updated
  • JSP error pages updated (javax.servlet.error.*jakarta.servlet.error.*)
  • Password encoder constructors updated for Spring Security 7 API changes

Phase 3 — Build and deployment tooling:

  • Jetty Maven plugin upgraded from 10 to 12 (ee10 module for Jakarta servlet support)
  • mvn jetty:run fixed for the Jetty 12 plugin (jettyXml was renamed jettyXmls and silently ignored, Jetty 12 jetty.xml syntax, a mail session built from angus-mail, commons-dbcp2) and it now comes up with a ready-to-use in-memory Derby schema
  • Dockerfile updated: Tomcat 9 → 10.1, javax.mail → angus-mail, PostgreSQL driver updated
  • docker-compose.yml updated: PostgreSQL 10 → 16
  • CI workflow updated to drop Java 11 from the build matrix (Java 17 minimum)

All 157 tests pass across all phases.

Phase 0 of the javax to Jakarta EE 10 migration (ROL-2183).

- Set compiler target from Java 11 to Java 17 (required by Spring 7, Struts 7)
- Upgrade Derby from 10.11.1.1 to 10.16.1.1 (old version doesn't support Java 17)
- Fix SLF4J bridge: log4j-slf4j-impl is for SLF4J 1.x, switch to log4j-slf4j2-impl to match the SLF4J 2.0.16 already declared in app/pom.xml
Phase 1 of the Jakarta EE 10 migration removes unmaintained and obsolete dependencies while still on the javax namespace so changes can be tested incrementally.

- Remove all OAuth 1.0a support (net.oauth.core:oauth-provider): 3 servlets, OAuthManager interface and JPA implementation, Struts actions, JSPs, ORM mappings, SQL schema, Guice bindings, and test
- Remove spring-security-openid: delete CustomOpenIDAuthenticationProcessingFilter, remove OpenID beans from security.xml, simplify RollerUserDetailsService and RollerSession to remove OpenID URL detection
- Remove struts2-bootstrap-plugin (unused taglib, never referenced in JSPs)
- Remove OAuth URL methods from URLStrategy and MultiWeblogURLStrategy
- Remove OAuth authentication path from RollerAtomHandler (keep WSSE and BASIC)
- Clean up ApplicationResources properties files (EN, JA, ZH_CN) to remove OAuth keys/authorize entries
These servlets come from xmlrpc-server and rome-propono respectively, neither of which has a Jakarta Servlet compatible release. Forking the servlet classes into Roller decouples us from their javax.servlet dependency so the imports can be converted to jakarta.servlet in Phase 2.

- Fork XmlRpcServlet and XmlRpcServletServer from Apache XML-RPC 3.1.3 into org.apache.roller.weblogger.webservices.xmlrpc package
- Fork AtomServlet from rome-propono into RollerAtomServlet, which directly creates RollerAtomHandler instead of using propono's factory lookup
- Update web.xml servlet-class references to point to the forked classes
- Both libraries remain as dependencies for their non-servlet infrastructure classes
The big switch: all javax Java EE APIs replaced with Jakarta equivalents, all major frameworks upgraded to Jakarta-compatible versions.

Dependencies: javax.servlet-api 4.0.1 → jakarta.servlet-api 6.0.0, javax.mail → jakarta.mail-api 2.1.3 + angus-mail 2.0.3, javax.activation → jakarta.activation-api 2.1.3, jaxb-api → jakarta.xml.bind-api 4.0.2 + jaxb-runtime 4.0.5, JSTL 1.2 → jakarta.servlet.jsp.jstl-api 3.0.1, Struts 2.5.29 → 7.1.1, Spring 5.3.39 → 7.0.8, Spring Security 5.8.14 → 7.0.6, EclipseLink 4.0.5 → 5.0.1

Java source: 320 javax.servlet/mail/activation imports converted to jakarta across 89 files, xwork2 imports migrated to org.apache.struts2 package, deprecated Struts interfaces (ServletRequestAware, RequestAware, HttpParametersAware) replaced with ActionContext lookups

Spring Security: AccessDecisionManager/RoleVoter removed (deprecated in 6.0), migrated to expression-based access control with hasAuthority/hasAnyAuthority, password encoder constructors updated to factory methods

Config: web.xml namespace updated to jakartaee 6.0, struts.xml DTD updated to Struts 7.0, persistence.xml and 29 ORM files updated to Jakarta JPA 3.2 namespace, tiles.xml DTD updated to 3.0, security.xml migrated to Spring Security 7 patterns

Also: forked AtomRequestImpl from rome-propono to use jakarta.servlet, deleted dead RollerAtomHandlerFactory, fixed FolderEdit ServletResponseAware removal
Jetty 10 used javax.servlet and was incompatible with the Jakarta EE 10 migration. Jetty 12's ee10 module supports jakarta.servlet natively. The Dockerfile now targets Tomcat 10.1 (Jakarta EE 10) with angus-mail replacing the old javax.mail JAR.
The Jakarta EE 10 migration sets Java 17 as the compiler target, so the JDK 11 build matrix entry is no longer valid.
Update it-selenium/pom.xml to replace javax dependencies removed from the parent pom: javax.xml.bind → jakarta.xml.bind, javax.mail → jakarta.mail + angus-mail. Switch Jetty plugin to ee10 variant and update config for Jetty 12.
Struts 7.1.1 ships with struts-6.5.dtd as the latest DTD. The non-existent struts-7.0.dtd URL causes startup failures when the XML parser cannot fetch the DTD (e.g., in Docker containers without full internet access).
Struts 7 renamed the fileUpload interceptor to actionFileUpload. This causes a startup failure when Struts initializes the interceptor stack.
Struts 7 only binds request parameters to @StrutsParameter-annotated properties and denies OGNL access to classes outside its allowlist, which silently broke every form in the application. Also re-enables the prepare interceptor before params so Preparable actions load the object that submitted values are bound onto, and converts PlanetGroupSubs to the standard Preparable contract.
The plugin renamed jettyXml to jettyXmls and silently ignored the old parameter, so jetty.xml was never loaded. That file also needed updating for Jetty 12: a DOCTYPE is now required, plus.jndi.Resource takes the server ref as scope instead of a string, and MailSessionReference no longer exists so the mail session is built from angus-mail directly. The Derby datasource moves from commons-dbcp 1.4 to commons-dbcp2, and roller-jettyrun.properties uses installation.type=ittest so the schema is created at startup and jetty:run comes up ready to log in.
The XML namespace now authorizes through AuthorizationManager, which denies any request that matches no intercept-url rule, where the old FilterSecurityInterceptor allowed them. Every unlisted URL including the login page itself redirected to the login page in an infinite loop.
…rvlet

webjars-servlet-2.x extends the javax HttpServlet, so loading it on a Jakarta container throws NoClassDefFoundError at startup and takes the whole context down with a 503. Servlet 3.0+ containers serve jar META-INF/resources content from the docroot at the same /webjars paths, so the servlet is unnecessary.
…ugin

The plugin was removed earlier in the migration as an unused taglib, but it also supplies the FreeMarker templates behind the theme="bootstrap" attribute that nearly every form JSP uses, so any page with a form failed to render with TemplateNotFoundException. Version 6.1.0 is Jakarta-native. It emits Bootstrap 5 markup while the JSPs still ship Bootstrap 3 CSS, so some form styling is off until the UI is converted to Bootstrap 5 in a follow-up. This also drops the webjars-servlet-2.x dependency whose servlet was removed from web.xml.
The plugin renamed jettyXml to jettyXmls and silently ignored the old parameter, so jetty.xml never loaded, the JNDI datasource was never bound, the Derby network server never started, and every test met a 503. The datasource in that file now uses commons-dbcp2, so the plugin classpath follows. The publish button is now located by its btn-success class because its generated id changed between form-theme versions.
@mraible

mraible commented Aug 11, 2026

Copy link
Copy Markdown
Author

The CI failures here predate the latest push: both runs failed the same way, with the it-selenium test meeting a 503 because the webapp never actually deployed. Debugging that locally turned up a chain of real migration bugs that the branch's only container test had been hiding, now fixed in the last four commits:

  • The Jetty 12 plugin renamed jettyXml to jettyXmls and silently ignored the old name, so the it-selenium harness never loaded jetty.xml, never bound the JNDI datasource, and never started the Derby network server (Nothing to bind for name jdbc/rollerdb in the earlier run's log).
  • webjars-servlet-2.x extends the javax HttpServlet, so on a Jakarta container it threw NoClassDefFoundError at startup and took the whole context down. WebJar assets are now served natively from META-INF/resources at the same /webjars paths.
  • Spring Security 7's XML config authorizes through AuthorizationManager, which denies requests that match no intercept-url rule; the old FilterSecurityInterceptor allowed them. Every unlisted URL, including the login page itself, redirected to the login page in a loop. An explicit /** permitAll rule restores the old behavior.
  • struts2-bootstrap-plugin had been removed as an unused taglib, but it also supplies the FreeMarker templates behind the theme="bootstrap" attribute that nearly every form JSP uses, so any page with a form failed with TemplateNotFoundException. The Jakarta-native 6.1.0 restores the theme; it emits Bootstrap 5 markup against the current Bootstrap 3 CSS, so some form styling is off until a follow-up converts the UI to Bootstrap 5.

The full Selenium journey (register, log in, create a weblog, publish an entry) and the 157 unit tests pass locally on this branch after these changes.

Struts 7 removed the old file upload parameter injection (File[] plus FileName/ContentType setters), so MediaFileAdd, MediaFileEdit, and BookmarksImport never received uploaded files. Each action now implements UploadedFilesAware and reads the file, original name, and content type from the UploadedFile objects supplied by the actionFileUpload interceptor. The upload fields are no longer exposed as getter/setter pairs so the params interceptor cannot collide with the form field names. Reported by Greg Huber on dev@roller.
The CI matrix moves from JDK 23 to JDK 25, the current LTS. Mockito 5.15.2 cannot instrument JDK 25 class files, which fails 19 session and filter tests, so it moves to 5.23.0. All 157 unit tests pass on 17, 21, and 25.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant