Toolset aimed at providing some added ease-of-use to the XML APIs of the JDK.
Digipost XML consists of the following three libraries:
-
digipost-xml-fundamentals : offers some ergonomics on top of the standard XML APIs in the JDK. The baseline JDK requirement for this is Java 8, and there are not additional dependencies.
-
digipost-xml-bind-jakarta : JAXB facilities, inspired by Spring OXM, and in addition contains a library of some useful adapters and other JAXB-related stuff.
-
digipost-xml-bind-javax : This is identical to the above, but for the legacy
javax.xml.bind
namespace.
This library is primarily intended for other libraries, where you may not wish to introduce a dependency to Spring. There is nothing wrong about Spring, and this library makes no effort to hide its heavy inspiration by Spring’s own Jaxb2Marshaller
, but to use that you would need to depend on spring-oxm
, which further depends on both spring-beans
and spring-core
, and as a library author you would want to keep your dependencies as clean and focused as possible to avoid introducing unnecessary artifacts into your library consumer’s dependencies.
The library is of course also applicable for applications/servers if you use frameworks/libraries not already offering support for JAXB, or you need to interact somewhat directly with the XML APIs in the JDK.
There is also the a bit specific case where you need to support JAXB marshalling using both the new Jakarta EE XML Bind API, and the legacy javax.xml.bind
namespace. It is, contrary to popular belief, possible to use these two JAXB variants side-by-side, should you need to, thanks to the almighty jaxb-resolver-com.sun.xml.bind.
The short-term plan at this point is to release a Release Candidate version for use by a service offered at Posten Bring, and followingly do a final 1.0 release when we are able to "validate" its usability across different use-cases in different applications. You are most welcome to try it out, and if it seems to be applicable for your use-case, we would love to hear about it, and if you have some suggestions to potentially make it a better fit for your and/or other use cases!
Declare the following in your dependencyManagement
section
<dependencyManagement>
<dependency>
<groupId>no.digipost.xml</groupId>
<artifactId>digipost-xml-bom</artifactId>
<version>[latest-version]</version>
<type>pom</type>
<scope>import</scope>
</dependency>
...
</dependencyManagement>
And depend on the part(s) you need. E.g:
<dependency>
<groupId>no.digipost.xml</groupId>
<artifactId>digipost-xml-fundamentals</artifactId>
</dependency>
<dependency>
<groupId>no.digipost.xml</groupId>
<artifactId>digipost-xml-bind-jakarta</artifactId>
</dependency>
Substitute "jakarta" with "javax" to digipost-xml-bind-javax
to use the legacy javax.xml.bind
variant, or even include both if you need to support both variants in your application, as they can happily coexist.
The XML APIs of the JDK is admittedly very low-level, and offer flexible access to the excellent XML facilities in Java. Usually you do not access these APIs directly, but they are used implicitly by libraries or frameworks. Should you need to interact directly with these APIs, this library may offer some ergonomics.
SAX parsers can not be shared across threads, because they modify their internal state per parsing task. So you obtain a parser each from a SAXParserFactory
each time you are going to parse som XML. Both initializing the factory and using it to create a parser are pestered with checked exceptions.
SaxParserProvider
is just the same factory pattern for obtaining a SAX parser, but without being forced to handle a smörgåsbord of checked exceptions. In addition, a ready-made secure SAX parser provider is offered through SaxParserProvider.createSecuredProvider()
, which yields parsers configured to align with OWASP recommendations.
Creating instances of javax.xml.validation.Schema
is something you usually do with static resources you already own. Yet, just pointing to some XSDs you have on classpath to parse and create a Schema
to use for validating XML you either create or consume, is ridiculously hard.
SchemaHelper.createW3cXmlSchema(Collection<String> schemaResourceNames)
takes a collection of classpath resource names (commonly XSD files you bundle with your application), and gives back an instance of Schema
.
The XML Bind modules are offered as two variants: one for the current Jakarta namespace, and one for the legacy javax.xml.bind
namespace of JAXB. If you have the need to e.g support older JAXB-generated classes as well as the current JAXB version using jakarta.xml.bind
, these two libraries should be able to happily coexist and operate within the same application.
The main purpose of this library is to offer the JaxbMarshaller
class which is inspired by Jaxb2Marshaller
in Spring OXM. It offers thread-safe access to marshalling and unmarshalling facilities without having to worry about which component of the XML Bind (JAXB) is thread-safe, i.e. which instances that should be shared, and which should be created on each use. A JaxbMarshaller
can be shared across threads, as one would expect.
JaxbMarshaller
offers an API for typical configuration use cases:
- which classes which are bound to the JAXB context
- schemas used for validation, and if used for either unmarshalling, marshalling, or both
- other arbitrary custom configuration of Marshaller and Unmarshaller instances
Validate against schema bundled with your application on both marshalling and unmarshalling, and set a custom property for marshalling:
var marshaller = new JaxbMarshaller(
MarshallingCustomization
.validateUsingSchemaResources(Set.of("/xsd/my-schema-on-classpath.xsd"))
.andThenOnMarshalling(marshaller -> marshaller.setProperty("jaxb.formatted.output", true)),
ABoundClass.class, AnotherBoundClass.class);
Alternatively, often it is advisable to not do a formal schema validation on XML consumed from an API response, as long as the unmarshaller is able to parse and map to your classes, as this enables the service to introduce changes to the responses in a backwards compatible manner. Say to introduce new error codes in a schema-defined enumeration, which clients may or may not support spesific handling for, or introduce new elements which clients are strictly not required to consume. A client making requests to a server with a server-defined schema, should most of the times validate the marshalled XML before sending it to the server.
var marshaller = new JaxbMarshaller(
MarshallingCustomization
.onMarshalling(MarshallerCustomizer
.validateUsingSchemaResources(Set.of("/xsd/my-schema-on-classpath.xsd"))
.andThen(marshaller -> marshaller.setProperty("jaxb.formatted.output", true)))
.andThenOnUnmarshalling(unmarshaller -> {
// anything you want to do on the unmarshaller?
// You can also supply UnmarshallerCustomizer.NO_CUSTOMIZATION to be
// explicit, or just omit invocation of .andThenOnUnmarshalling(..)
}),
ABoundClass.class, AnotherBoundClass.class);
The JaxbMarshaller
instance offers methods to either marshal (generate XML from Java objects) or unmarshal (parse XML and map contents to a Java object).