Skip to content

Add JPMS module descriptor and clean up the package structure - Breaking changes - #42

Open
t2gran wants to merge 11 commits into
masterfrom
java-module
Open

t2gran wants to merge 11 commits into
masterfrom
java-module

Conversation

@t2gran

@t2gran t2gran commented Oct 15, 2026 •

Copy link
Copy Markdown

Add JPMS module descriptor and clean up the package structure

Closes #37

This adds a module-info.java (open module org.entur.siri) and removes the things that blocked it. This is a breaking change and needs a major release. I have not changed the version. That is left to the maintainer.

Breaking changes

1. NamespacePrefixMapper is removed from SiriXml.
toXml(Siri, NamespacePrefixMapper) and toXml(Siri, NamespacePrefixMapper, OutputStream) now take a Consumer<Marshaller> in both SiriXml classes. The JAXBNamespacePrefixMapper dependency is removed.

Why: that stub jar puts classes in com.sun.xml.bind.marshaller, the same package as the real JAXB RI. Two modules cannot share a package, so the stub cannot be used on the module path together with a real JAXB runtime. Also, the RI and MOXy each need their own mapper class, so a library-owned replacement type cannot work. A customizer lets the caller set the property for the JAXB implementation they use.

// before
SiriXml.toXml(siri, myNamespacePrefixMapper);

// after (property name for JAXB RI 3.x; use the one your implementation documents)
SiriXml.toXml(siri, marshaller -> {
    try {
        marshaller.setProperty("org.glassfish.jaxb.namespacePrefixMapper", myNamespacePrefixMapper);
    } catch (PropertyException e) {
        throw new IllegalStateException(e);
    }
});

Callers whose own code imports com.sun.xml.bind.marshaller.NamespacePrefixMapper must add the dependency that provides it. This library no longer brings it in.

2. org.rutebanken.siri20.util is now org.entur.siri20.util.
SiriXml and SiriJson moved, to match org.entur.siri21.util. There are no forwarders in the old package, because a forwarder would keep that package exported.

3. The module name is now explicit: org.entur.siri.
Before, it was taken from the jar file name (siri.java.model). Anyone who used the old automatic name on the module path must change it.

4. The generated packages are renamed to org.entur.siri.schema.v20.* and org.entur.siri.schema.v21.*.
The XML namespaces and the wire format are unchanged. Only the Java names change.

Before After
uk.org.siri.siri20 / siri21 org.entur.siri.schema.v20.siri / v21.siri
uk.org.ifopt.siri20 / siri21 org.entur.siri.schema.v20.ifopt / v21.ifopt
uk.org.acbs.siri20 / siri21 org.entur.siri.schema.v20.acsb / v21.acsb
eu.datex2.siri20.schema._2_0rc1._2_0 / siri21... org.entur.siri.schema.v20.datex2 / v21.datex2
net.opengis.gml.siri org.entur.siri.schema.v21.gml

Why: the default XJC names come from the XML namespaces. Other generated models (netex-java-model, ojp-java-model) generate types for the same foreign namespaces, and two modules cannot share a package: the module path then refuses to boot. All generated types now live under the module root, one sibling package per source schema. The v20/v21 level is needed because this one jar contains both SIRI versions.

The adapters that XJC generated into org.w3._2001.xmlschema are gone for the same reason. The xsd:dateTime and xsd:time bindings now use the handwritten org.entur.siri.adapter.ZonedDateTimeXmlAdapter and ZonedTimeXmlAdapter. They parse and print exactly as before.

What changed

  • module-info.java: open module org.entur.siri. It is open because the consumer's JAXB runtime needs reflective access to the generated classes, and we cannot name that module. It exports the 5 handwritten and 9 generated packages. It requires jakarta.xml.bind, com.fasterxml.jackson.databind, com.fasterxml.jackson.annotation and com.fasterxml.jackson.module.jakarta.xmlbind.
  • pom.xml:
    • uses <release>11</release> instead of <source>/<target>
    • removes JAXBNamespacePrefixMapper
    • removes the unused slf4j-api and slf4j-simple, and the duplicated slf4j.version property
    • removes a duplicate jakarta.activation module: jackson-module-jakarta-xmlbind-annotations brought in jakarta.activation-api 2.1.0, while jakarta.xml.bind-api brought in com.sun.activation:jakarta.activation 2.0.1. Both use the module name jakarta.activation.
  • New tests for the Consumer<Marshaller> overloads for SIRI 2.0 and 2.1, including one that sets the real JAXB RI NamespacePrefixMapper.
  • README: a JPMS section and an "Upgrading to 3.0" section. CLAUDE.md is updated.

The move is in its own commit ((git-mv)), so git log --follow and blame keep working. The reference updates are in the (code) commit after it.

How it was checked

  • mvn clean test on JDK 11 and on JDK 25 (as CI does): 34 tests pass, including the ones already on master. Surefire runs them on the module path (module-info.java makes it do so), with the JAXB RI 3.0.2 on that path, and there is no split-package error.
  • I built the jar and ran a separate module (requires org.entur.siri) against it on the module path with the real JAXB RI. jar --describe-module shows open module org.entur.siri and 14 exports. SiriXml.class.getModule() is the named module, and parsing, the customizer overload and SiriJson all work.

Notes for the reviewer

  • The error messages quoted below use the package names from before the rename.

  • The checks above were run again after the rename: mvn clean package passes, and JAXBContext.newInstance works for all 9 generated packages on the module path (with org.glassfish.jaxb.runtime 3.0.2). No package is shared with ojp-java-model.

  • open module is deliberate. I tested the alternatives against a separate modular consumer with the JAXB RI 3.0.2:

    • exports only fails: JAXBException: Package uk.org.siri.siri21 ... must be open to at least jakarta.xml.bind module.
    • exports plus opens <pkg> to jakarta.xml.bind, com.fasterxml.jackson.databind passes that check but still fails, because the RI reflects on private fields from its own module (com.sun.xml.bind): InaccessibleObjectException ... does not "opens uk.org.ifopt.siri21" to module com.sun.xml.bind.
    • A qualified opens would therefore need the implementation module name too, and that depends on the consumer's JAXB runtime (com.sun.xml.bind for 3.0.x, org.glassfish.jaxb.runtime for 4.x, MOXy has its own). Listing them all is brittle. A plain opens <pkg> on the generated packages only (and exports for the handwritten ones) would work and is slightly narrower than open module.
  • The test for the real JAXB RI mapper uses the property name org.glassfish.jaxb.namespacePrefixMapper, which I checked in the jaxb-impl 3.0.2 jar.

…verloads

Add module-info.java (open module org.entur.siri) exporting the handwritten
and generated packages.

BREAKING CHANGE: SiriXml.toXml(Siri, NamespacePrefixMapper[, OutputStream])
in both org.entur.siri21.util and org.rutebanken.siri20.util now take a
Consumer<Marshaller> customizer instead. The JAXBNamespacePrefixMapper
dependency, which shares a package with the JAXB RI and cannot coexist with
it on the module path, is removed. Callers set their own JAXB
implementation's namespace prefix mapper property on the Marshaller.
Add tests for both the SIRI 2.0 and 2.1 SiriXml classes: customizer is
applied to the marshaller, null customizer matches the default output, the
OutputStream overload applies the customizer, and a customizer does not
leak into later calls.
- Use <release> instead of <source>/<target> so builds on newer JDKs cannot
  use APIs newer than Java 11.
- Remove slf4j-api and slf4j-simple. Nothing in the project uses slf4j.
- Remove the now-unused (and previously duplicated) slf4j.version property.
SiriJson imports com.fasterxml.jackson.annotation.JsonInclude directly, so
the module must require com.fasterxml.jackson.annotation. This only surfaced
when compiling with --release.
jackson-module-jakarta-xmlbind-annotations pulled in jakarta.activation-api
2.1.0 while jakarta.xml.bind-api pulled in com.sun.activation:jakarta.activation
2.0.1. Both declare the module name jakarta.activation, which made surefire log
"Module 'jakarta.activation' is already on the module path". Exclude the API
jar from the Jackson module and drop the test-scope pin of the same jar.
Add a README section on the org.entur.siri module and an "Upgrading to 3.0"
note with a before/after example. Update CLAUDE.md for module-info.java, the
release flag, the Consumer<Marshaller> API and the JDK 11 build requirement.
… to org.entur.siri20.util (code)

BREAKING CHANGE: org.rutebanken.siri20.util.SiriXml and SiriJson are now in
org.entur.siri20.util, matching org.entur.siri21.util. There are no
forwarders in the old package.
BREAKING CHANGE: generated packages are renamed. XML namespaces and the
wire format are unchanged.

  uk.org.siri.siri2x                    -> org.entur.siri.schema.v2x.siri
  uk.org.ifopt.siri2x                   -> org.entur.siri.schema.v2x.ifopt
  uk.org.acbs.siri2x                    -> org.entur.siri.schema.v2x.acsb
  eu.datex2.siri2x.schema._2_0rc1._2_0  -> org.entur.siri.schema.v2x.datex2
  net.opengis.gml.siri                  -> org.entur.siri.schema.v21.gml

Everything now lives under the module root, so it cannot form a split
package with other generated models (netex-java-model, ojp-java-model).

XJC no longer generates adapters into org.w3._2001.xmlschema. The
dateTime/time bindings use the hand-written ZonedDateTimeXmlAdapter and
ZonedTimeXmlAdapter instead. module-info.java exports the new packages.
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.

Cleanup package structure and add support for Java Platform Module System

1 participant