GLOBAL_GROUP = Set.of("id", "version", "modelBaseVersion");
diff --git a/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/MultilingualStringJson.java b/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/MultilingualStringJson.java
index f9b6d13..a4dfca0 100644
--- a/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/MultilingualStringJson.java
+++ b/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/MultilingualStringJson.java
@@ -38,7 +38,7 @@
*
* {@code {"values":[{"lang":"fi","value":"Kärkitie 4"}]}}
*/
-public final class MultilingualStringJson {
+final class MultilingualStringJson {
private MultilingualStringJson() {}
diff --git a/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/SubstitutionJson.java b/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/SubstitutionJson.java
index e2213f6..e1beff0 100644
--- a/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/SubstitutionJson.java
+++ b/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/SubstitutionJson.java
@@ -29,7 +29,7 @@
* fixture ({@code
* datex4j-json/src/test/resources/datex-json/finland-afir-messagecontainer.v3_6.json}).
*/
-public final class SubstitutionJson {
+final class SubstitutionJson {
private SubstitutionJson() {}
diff --git a/datex4j-location/pom.xml b/datex4j-location/pom.xml
index 1e56a79..42c3eb4 100644
--- a/datex4j-location/pom.xml
+++ b/datex4j-location/pom.xml
@@ -7,7 +7,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
datex4j-location
diff --git a/datex4j-model-spi/pom.xml b/datex4j-model-spi/pom.xml
index da81ca5..bc7258e 100644
--- a/datex4j-model-spi/pom.xml
+++ b/datex4j-model-spi/pom.xml
@@ -7,7 +7,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
datex4j-model-spi
diff --git a/datex4j-model-v2_0/pom.xml b/datex4j-model-v2_0/pom.xml
index 49742b1..22cc5f5 100644
--- a/datex4j-model-v2_0/pom.xml
+++ b/datex4j-model-v2_0/pom.xml
@@ -7,7 +7,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
datex4j-model-v2_0
diff --git a/datex4j-model-v2_0/src/main/java/dev/juherr/datex4j/model/v2_0/spi/package-info.java b/datex4j-model-v2_0/src/main/java/dev/juherr/datex4j/model/v2_0/spi/package-info.java
new file mode 100644
index 0000000..bb26c8e
--- /dev/null
+++ b/datex4j-model-v2_0/src/main/java/dev/juherr/datex4j/model/v2_0/spi/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * DATEX II 2.0 provider implementation loaded through {@link java.util.ServiceLoader}.
+ *
+ * This package is an implementation detail. Consumers should use {@link
+ * dev.juherr.datex4j.model.spi.DatexModelProvider} instead.
+ */
+package dev.juherr.datex4j.model.v2_0.spi;
diff --git a/datex4j-model-v2_1/pom.xml b/datex4j-model-v2_1/pom.xml
index 46172dc..12f0e49 100644
--- a/datex4j-model-v2_1/pom.xml
+++ b/datex4j-model-v2_1/pom.xml
@@ -7,7 +7,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
datex4j-model-v2_1
diff --git a/datex4j-model-v2_1/src/main/java/dev/juherr/datex4j/model/v2_1/spi/package-info.java b/datex4j-model-v2_1/src/main/java/dev/juherr/datex4j/model/v2_1/spi/package-info.java
new file mode 100644
index 0000000..3e10a30
--- /dev/null
+++ b/datex4j-model-v2_1/src/main/java/dev/juherr/datex4j/model/v2_1/spi/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * DATEX II 2.1 provider implementation loaded through {@link java.util.ServiceLoader}.
+ *
+ *
This package is an implementation detail. Consumers should use {@link
+ * dev.juherr.datex4j.model.spi.DatexModelProvider} instead.
+ */
+package dev.juherr.datex4j.model.v2_1.spi;
diff --git a/datex4j-model-v2_2/pom.xml b/datex4j-model-v2_2/pom.xml
index 3125470..a6cac66 100644
--- a/datex4j-model-v2_2/pom.xml
+++ b/datex4j-model-v2_2/pom.xml
@@ -7,7 +7,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
datex4j-model-v2_2
diff --git a/datex4j-model-v2_2/src/main/java/dev/juherr/datex4j/model/v2_2/spi/package-info.java b/datex4j-model-v2_2/src/main/java/dev/juherr/datex4j/model/v2_2/spi/package-info.java
new file mode 100644
index 0000000..0bec818
--- /dev/null
+++ b/datex4j-model-v2_2/src/main/java/dev/juherr/datex4j/model/v2_2/spi/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * DATEX II 2.2 provider implementation loaded through {@link java.util.ServiceLoader}.
+ *
+ *
This package is an implementation detail. Consumers should use {@link
+ * dev.juherr.datex4j.model.spi.DatexModelProvider} instead.
+ */
+package dev.juherr.datex4j.model.v2_2.spi;
diff --git a/datex4j-model-v2_3/pom.xml b/datex4j-model-v2_3/pom.xml
index 4deae7a..607399f 100644
--- a/datex4j-model-v2_3/pom.xml
+++ b/datex4j-model-v2_3/pom.xml
@@ -7,7 +7,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
datex4j-model-v2_3
diff --git a/datex4j-model-v2_3/src/main/java/dev/juherr/datex4j/model/v2_3/spi/package-info.java b/datex4j-model-v2_3/src/main/java/dev/juherr/datex4j/model/v2_3/spi/package-info.java
new file mode 100644
index 0000000..96880ca
--- /dev/null
+++ b/datex4j-model-v2_3/src/main/java/dev/juherr/datex4j/model/v2_3/spi/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * DATEX II 2.3 provider implementation loaded through {@link java.util.ServiceLoader}.
+ *
+ *
This package is an implementation detail. Consumers should use {@link
+ * dev.juherr.datex4j.model.spi.DatexModelProvider} instead.
+ */
+package dev.juherr.datex4j.model.v2_3.spi;
diff --git a/datex4j-model-v3_0/pom.xml b/datex4j-model-v3_0/pom.xml
index 7d1f6d8..9a4449d 100644
--- a/datex4j-model-v3_0/pom.xml
+++ b/datex4j-model-v3_0/pom.xml
@@ -7,7 +7,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
datex4j-model-v3_0
diff --git a/datex4j-model-v3_0/src/main/java/dev/juherr/datex4j/model/v3_0/spi/package-info.java b/datex4j-model-v3_0/src/main/java/dev/juherr/datex4j/model/v3_0/spi/package-info.java
new file mode 100644
index 0000000..3b18875
--- /dev/null
+++ b/datex4j-model-v3_0/src/main/java/dev/juherr/datex4j/model/v3_0/spi/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * DATEX II 3.0 provider implementation loaded through {@link java.util.ServiceLoader}.
+ *
+ *
This package is an implementation detail. Consumers should use {@link
+ * dev.juherr.datex4j.model.spi.DatexModelProvider} instead.
+ */
+package dev.juherr.datex4j.model.v3_0.spi;
diff --git a/datex4j-model-v3_1/pom.xml b/datex4j-model-v3_1/pom.xml
index e733844..b37866d 100644
--- a/datex4j-model-v3_1/pom.xml
+++ b/datex4j-model-v3_1/pom.xml
@@ -7,7 +7,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
datex4j-model-v3_1
diff --git a/datex4j-model-v3_1/src/main/java/dev/juherr/datex4j/model/v3_1/spi/package-info.java b/datex4j-model-v3_1/src/main/java/dev/juherr/datex4j/model/v3_1/spi/package-info.java
new file mode 100644
index 0000000..eeb2dcc
--- /dev/null
+++ b/datex4j-model-v3_1/src/main/java/dev/juherr/datex4j/model/v3_1/spi/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * DATEX II 3.1 provider implementation loaded through {@link java.util.ServiceLoader}.
+ *
+ *
This package is an implementation detail. Consumers should use {@link
+ * dev.juherr.datex4j.model.spi.DatexModelProvider} instead.
+ */
+package dev.juherr.datex4j.model.v3_1.spi;
diff --git a/datex4j-model-v3_2/pom.xml b/datex4j-model-v3_2/pom.xml
index b3a50a3..fa6dec9 100644
--- a/datex4j-model-v3_2/pom.xml
+++ b/datex4j-model-v3_2/pom.xml
@@ -7,7 +7,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
datex4j-model-v3_2
diff --git a/datex4j-model-v3_2/src/main/java/dev/juherr/datex4j/model/v3_2/spi/package-info.java b/datex4j-model-v3_2/src/main/java/dev/juherr/datex4j/model/v3_2/spi/package-info.java
new file mode 100644
index 0000000..89c69be
--- /dev/null
+++ b/datex4j-model-v3_2/src/main/java/dev/juherr/datex4j/model/v3_2/spi/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * DATEX II 3.2 provider implementation loaded through {@link java.util.ServiceLoader}.
+ *
+ *
This package is an implementation detail. Consumers should use {@link
+ * dev.juherr.datex4j.model.spi.DatexModelProvider} instead.
+ */
+package dev.juherr.datex4j.model.v3_2.spi;
diff --git a/datex4j-model-v3_3/pom.xml b/datex4j-model-v3_3/pom.xml
index d068b07..fbc1c8a 100644
--- a/datex4j-model-v3_3/pom.xml
+++ b/datex4j-model-v3_3/pom.xml
@@ -7,7 +7,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
datex4j-model-v3_3
diff --git a/datex4j-model-v3_3/src/main/java/dev/juherr/datex4j/model/v3_3/spi/package-info.java b/datex4j-model-v3_3/src/main/java/dev/juherr/datex4j/model/v3_3/spi/package-info.java
new file mode 100644
index 0000000..689c41e
--- /dev/null
+++ b/datex4j-model-v3_3/src/main/java/dev/juherr/datex4j/model/v3_3/spi/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * DATEX II 3.3 provider implementation loaded through {@link java.util.ServiceLoader}.
+ *
+ *
This package is an implementation detail. Consumers should use {@link
+ * dev.juherr.datex4j.model.spi.DatexModelProvider} instead.
+ */
+package dev.juherr.datex4j.model.v3_3.spi;
diff --git a/datex4j-model-v3_4/pom.xml b/datex4j-model-v3_4/pom.xml
index 14cc8e0..79df701 100644
--- a/datex4j-model-v3_4/pom.xml
+++ b/datex4j-model-v3_4/pom.xml
@@ -7,7 +7,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
datex4j-model-v3_4
diff --git a/datex4j-model-v3_4/src/main/java/dev/juherr/datex4j/model/v3_4/spi/package-info.java b/datex4j-model-v3_4/src/main/java/dev/juherr/datex4j/model/v3_4/spi/package-info.java
new file mode 100644
index 0000000..4bab9be
--- /dev/null
+++ b/datex4j-model-v3_4/src/main/java/dev/juherr/datex4j/model/v3_4/spi/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * DATEX II 3.4 provider implementation loaded through {@link java.util.ServiceLoader}.
+ *
+ *
This package is an implementation detail. Consumers should use {@link
+ * dev.juherr.datex4j.model.spi.DatexModelProvider} instead.
+ */
+package dev.juherr.datex4j.model.v3_4.spi;
diff --git a/datex4j-model-v3_5/pom.xml b/datex4j-model-v3_5/pom.xml
index 7c08b5c..6c3ef00 100644
--- a/datex4j-model-v3_5/pom.xml
+++ b/datex4j-model-v3_5/pom.xml
@@ -7,7 +7,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
datex4j-model-v3_5
diff --git a/datex4j-model-v3_5/src/main/java/dev/juherr/datex4j/model/v3_5/spi/package-info.java b/datex4j-model-v3_5/src/main/java/dev/juherr/datex4j/model/v3_5/spi/package-info.java
new file mode 100644
index 0000000..66ad3d3
--- /dev/null
+++ b/datex4j-model-v3_5/src/main/java/dev/juherr/datex4j/model/v3_5/spi/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * DATEX II 3.5 provider implementation loaded through {@link java.util.ServiceLoader}.
+ *
+ *
This package is an implementation detail. Consumers should use {@link
+ * dev.juherr.datex4j.model.spi.DatexModelProvider} instead.
+ */
+package dev.juherr.datex4j.model.v3_5.spi;
diff --git a/datex4j-model-v3_6/pom.xml b/datex4j-model-v3_6/pom.xml
index ae311c3..664ef57 100644
--- a/datex4j-model-v3_6/pom.xml
+++ b/datex4j-model-v3_6/pom.xml
@@ -7,7 +7,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
datex4j-model-v3_6
diff --git a/datex4j-model-v3_6/src/main/java/dev/juherr/datex4j/model/v3_6/spi/package-info.java b/datex4j-model-v3_6/src/main/java/dev/juherr/datex4j/model/v3_6/spi/package-info.java
new file mode 100644
index 0000000..25fd2c1
--- /dev/null
+++ b/datex4j-model-v3_6/src/main/java/dev/juherr/datex4j/model/v3_6/spi/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * DATEX II 3.6 provider implementation loaded through {@link java.util.ServiceLoader}.
+ *
+ *
This package is an implementation detail. Consumers should use {@link
+ * dev.juherr.datex4j.model.spi.DatexModelProvider} instead.
+ */
+package dev.juherr.datex4j.model.v3_6.spi;
diff --git a/datex4j-model-v3_7/pom.xml b/datex4j-model-v3_7/pom.xml
index b1a592f..b3f5891 100644
--- a/datex4j-model-v3_7/pom.xml
+++ b/datex4j-model-v3_7/pom.xml
@@ -7,7 +7,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
datex4j-model-v3_7
diff --git a/datex4j-model-v3_7/src/main/java/dev/juherr/datex4j/model/v3_7/spi/package-info.java b/datex4j-model-v3_7/src/main/java/dev/juherr/datex4j/model/v3_7/spi/package-info.java
new file mode 100644
index 0000000..9c954e9
--- /dev/null
+++ b/datex4j-model-v3_7/src/main/java/dev/juherr/datex4j/model/v3_7/spi/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * DATEX II 3.7 provider implementation loaded through {@link java.util.ServiceLoader}.
+ *
+ *
This package is an implementation detail. Consumers should use {@link
+ * dev.juherr.datex4j.model.spi.DatexModelProvider} instead.
+ */
+package dev.juherr.datex4j.model.v3_7.spi;
diff --git a/datex4j-model/pom.xml b/datex4j-model/pom.xml
index 0af3065..57fb4b5 100644
--- a/datex4j-model/pom.xml
+++ b/datex4j-model/pom.xml
@@ -7,7 +7,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
datex4j-model
@@ -81,7 +81,7 @@
-
+
org.apache.maven.plugins
maven-jar-plugin
@@ -94,7 +94,17 @@
-
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ ${maven-javadoc-plugin.version}
+
+ package
+
+
+
+
org.jacoco
jacoco-maven-plugin
diff --git a/datex4j-model/src/main/java/dev/juherr/datex4j/model/AggregateMarker.java b/datex4j-model/src/main/java/dev/juherr/datex4j/model/AggregateMarker.java
new file mode 100644
index 0000000..f41d248
--- /dev/null
+++ b/datex4j-model/src/main/java/dev/juherr/datex4j/model/AggregateMarker.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2026 the datex4j authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package dev.juherr.datex4j.model;
+
+/**
+ * Gives the aggregate JAR a documentable package without adding a supported runtime type.
+ *
+ * The class is deliberately package-private.
+ */
+final class AggregateMarker {
+ private AggregateMarker() {}
+}
diff --git a/datex4j-model/src/main/java/dev/juherr/datex4j/model/package-info.java b/datex4j-model/src/main/java/dev/juherr/datex4j/model/package-info.java
new file mode 100644
index 0000000..01e429e
--- /dev/null
+++ b/datex4j-model/src/main/java/dev/juherr/datex4j/model/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2026 the datex4j authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Convenience aggregate for all version-specific DATEX II model artifacts.
+ *
+ *
This package declares no supported runtime types. The artifact exists to bring every {@code
+ * datex4j-model-vX_Y} dependency onto the consumer classpath.
+ */
+package dev.juherr.datex4j.model;
diff --git a/datex4j-ocpi/pom.xml b/datex4j-ocpi/pom.xml
index 31e82d9..394f9ed 100644
--- a/datex4j-ocpi/pom.xml
+++ b/datex4j-ocpi/pom.xml
@@ -7,7 +7,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
datex4j-ocpi
diff --git a/datex4j-ocpi/src/main/java/dev/juherr/datex4j/ocpi/mapping/internal/package-info.java b/datex4j-ocpi/src/main/java/dev/juherr/datex4j/ocpi/mapping/internal/package-info.java
new file mode 100644
index 0000000..7e0b4b5
--- /dev/null
+++ b/datex4j-ocpi/src/main/java/dev/juherr/datex4j/ocpi/mapping/internal/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2026 the datex4j authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Unsupported implementation details shared by the public OCPI mapping facades.
+ *
+ *
Types in this package are not part of datex4j's supported API and may change without
+ * deprecation.
+ */
+package dev.juherr.datex4j.ocpi.mapping.internal;
diff --git a/datex4j-ocpi/src/main/java/dev/juherr/datex4j/ocpi/support/package-info.java b/datex4j-ocpi/src/main/java/dev/juherr/datex4j/ocpi/support/package-info.java
new file mode 100644
index 0000000..561f459
--- /dev/null
+++ b/datex4j-ocpi/src/main/java/dev/juherr/datex4j/ocpi/support/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2026 the datex4j authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Unsupported support code generated for the OCPI model.
+ *
+ *
Applications should use the generated {@code dev.juherr.datex4j.ocpi.model.v2_3} types and
+ * the public mapping facades. Types in this package may change when the generator changes.
+ */
+package dev.juherr.datex4j.ocpi.support;
diff --git a/datex4j-validation/pom.xml b/datex4j-validation/pom.xml
index f574edf..91b5f2b 100644
--- a/datex4j-validation/pom.xml
+++ b/datex4j-validation/pom.xml
@@ -7,7 +7,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
datex4j-validation
diff --git a/datex4j-xml/pom.xml b/datex4j-xml/pom.xml
index f16e31d..e70b262 100644
--- a/datex4j-xml/pom.xml
+++ b/datex4j-xml/pom.xml
@@ -7,7 +7,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
datex4j-xml
diff --git a/datex4j-xml/src/main/java/dev/juherr/datex4j/xml/SecureXmlSource.java b/datex4j-xml/src/main/java/dev/juherr/datex4j/xml/SecureXmlSource.java
index 10dc12e..1291c17 100644
--- a/datex4j-xml/src/main/java/dev/juherr/datex4j/xml/SecureXmlSource.java
+++ b/datex4j-xml/src/main/java/dev/juherr/datex4j/xml/SecureXmlSource.java
@@ -25,7 +25,13 @@
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
-/** Creates fail-closed SAX sources for untrusted DATEX II XML documents. */
+/**
+ * Creates fail-closed SAX sources for untrusted DATEX II XML documents.
+ *
+ *
API note: This public type exists for cooperation between datex4j modules. It
+ * is an unsupported implementation detail and may change without notice. Applications should use
+ * {@link DatexXml} or the validation facade instead.
+ */
public final class SecureXmlSource {
private static final String DISALLOW_DOCTYPE = "http://apache.org/xml/features/disallow-doctype-decl";
diff --git a/docs/README.md b/docs/README.md
index 4279997..42d9a1f 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -14,6 +14,7 @@ goal.
convenience APIs.
5. Read [OCPI mapping](guides/ocpi-mapping.md) when converting charging infrastructure between OCPI
2.3 and DATEX II 3.7.
+6. Read [API compatibility](api-compatibility.md) before relying on extension points or upgrading.
The [examples module](../examples/src/main/java/dev/juherr/datex4j/examples) contains runnable,
tested sources for every guide.
@@ -27,6 +28,8 @@ tested sources for every guide.
4. Read the [integration-test guide](../datex4j-integration-tests/README.md) before adding a fixture
or live-feed test.
5. Record user-visible changes in [CHANGELOG.md](../CHANGELOG.md).
+6. Follow [API compatibility](api-compatibility.md) when changing public types or accepting a
+ Revapi difference.
## Standards and test data
diff --git a/docs/api-compatibility.md b/docs/api-compatibility.md
new file mode 100644
index 0000000..8f6237e
--- /dev/null
+++ b/docs/api-compatibility.md
@@ -0,0 +1,51 @@
+# API compatibility
+
+datex4j uses Semantic Versioning to communicate source and binary compatibility. Before `1.0.0`,
+minor releases may contain breaking changes, but every such change is announced in the changelog
+with explicit migration guidance. Patch releases preserve the supported API.
+
+## Supported API
+
+Compatibility checks cover:
+
+- the XML, JSON, and validation facades;
+- builders, location helpers, and domain convenience APIs;
+- the version-neutral model SPI;
+- generated DATEX II model classes for every published model artifact;
+- generated OCPI model classes and the public OCPI mapping API.
+
+Applications may implement `DatexModelProvider` to integrate an additional DATEX II version. This
+is the supported extension point. Generated models remain versioned by artifact and Java package,
+so applications can migrate one model version at a time.
+
+## Unsupported implementation details
+
+The following types remain accessible where Java or inter-module integration requires it, but are
+not compatibility commitments:
+
+- every package named `internal` and its subpackages;
+- `dev.juherr.datex4j.ocpi.support`;
+- each `dev.juherr.datex4j.model.vX_Y.spi.DatexModelProviderVXY` implementation;
+- `dev.juherr.datex4j.xml.SecureXmlSource`.
+
+Do not import these types from application code. Use the public facades, mapping APIs, and
+`DatexModelProvider` contract instead.
+
+## Compatibility enforcement
+
+Maven runs Revapi during `verify` and compares each module with its most recent final release.
+Dependencies are checked in their owning modules rather than repeatedly in consumers. Before the
+first release, an unresolved baseline is allowed; once `0.1.0` is available, it automatically
+becomes the baseline for `0.2.0-SNAPSHOT`.
+
+The versioned allowlist in `config/revapi/accepted-differences.json` is empty by default, so the
+`revapi.differences` transform is disabled. When an exception is required, enable the transform,
+add its entries to the analysis configuration, and keep the allowlist synchronized. A
+compatibility exception must:
+
+1. identify the exact Revapi difference;
+2. provide a justification beginning with `Migration:`;
+3. describe the same migration in `CHANGELOG.md`.
+
+Prefer deprecation and an additive replacement before removal. When a breaking change is necessary
+during `0.x`, retain the old API for at least one minor release whenever practical.
diff --git a/examples/pom.xml b/examples/pom.xml
index 7683fee..40dfec5 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -7,7 +7,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
datex4j-examples
diff --git a/pom.xml b/pom.xml
index ec3032f..9ffd831 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
dev.juherr.datex4j
datex4j
- 0.1.0-SNAPSHOT
+ 0.1.0
pom
datex4j
@@ -99,12 +99,16 @@
2.96.0
0.8.15
0.00
+ 0.15.1
+ 0.28.4
3.4.0
3.12.0
3.2.8
0.11.0
+ false
+ validated
@@ -383,6 +387,61 @@
+
+
+
+ org.revapi
+ revapi-maven-plugin
+ ${revapi-maven-plugin.version}
+
+
+ org.revapi
+ revapi-java
+ ${revapi-java.version}
+
+
+
+ RELEASE
+ [0-9]+\.[0-9]+\.[0-9]+
+ false
+ true
+ false
+
+
+
+ - revapi.java.filter.annotated
+
+
+
+
+ - revapi.differences
+ - revapi.ignore
+ - revapi.reclassify
+ - revapi.semver.ignore
+ - revapi.versions
+
+
+
+
+
+
+ ${maven.multiModuleProjectDirectory}/config/revapi/revapi.json
+ ${maven.multiModuleProjectDirectory}/config/revapi/accepted-differences.json
+
+
+
+
+ check-api-compatibility
+ verify
+
+ check
+
+
+
+
@@ -417,7 +476,7 @@
none
true
- false
+ true
@@ -451,8 +510,8 @@
true
central
-
- false
+ ${central.autoPublish}
+ ${central.waitUntil}
diff --git a/scripts/build-central-bundle.sh b/scripts/build-central-bundle.sh
new file mode 100755
index 0000000..663d66e
--- /dev/null
+++ b/scripts/build-central-bundle.sh
@@ -0,0 +1,82 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+if [[ $# -ne 2 ]]; then
+ echo "Usage: $0 " >&2
+ exit 2
+fi
+
+output=$1
+version=$2
+project_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
+inventory="${project_root}/config/release/public-artifacts.txt"
+
+if [[ ! "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+ echo "Invalid release version: ${version}" >&2
+ exit 2
+fi
+
+mkdir -p "$(dirname "${output}")"
+output_directory=$(cd "$(dirname "${output}")" && pwd)
+output="${output_directory}/$(basename "${output}")"
+
+staging=$(mktemp -d)
+trap 'rm -rf "${staging}"' EXIT
+repository_root="${staging}/dev/juherr/datex4j"
+
+copy_payload() {
+ local source=$1
+ local destination=$2
+
+ if [[ ! -f "${source}" ]]; then
+ echo "Missing built release payload: ${source}" >&2
+ exit 1
+ fi
+ if [[ ! -f "${source}.asc" ]]; then
+ echo "Missing built release signature: ${source}.asc" >&2
+ exit 1
+ fi
+
+ cp "${source}" "${destination}"
+ cp "${source}.asc" "${destination}.asc"
+}
+
+while read -r artifact_id packaging; do
+ [[ -z "${artifact_id}" || "${artifact_id}" == \#* ]] && continue
+
+ module_root="${project_root}/${artifact_id}"
+ if [[ "${artifact_id}" == "datex4j" ]]; then
+ module_root="${project_root}"
+ fi
+ artifact_root="${repository_root}/${artifact_id}/${version}"
+ base="${artifact_id}-${version}"
+ mkdir -p "${artifact_root}"
+
+ copy_payload "${module_root}/target/${base}.pom" "${artifact_root}/${base}.pom"
+ if [[ "${packaging}" == "jar" ]]; then
+ copy_payload "${module_root}/target/${base}.jar" "${artifact_root}/${base}.jar"
+ copy_payload \
+ "${module_root}/target/${base}-sources.jar" \
+ "${artifact_root}/${base}-sources.jar"
+ copy_payload \
+ "${module_root}/target/${base}-javadoc.jar" \
+ "${artifact_root}/${base}-javadoc.jar"
+ elif [[ "${packaging}" != "pom" ]]; then
+ echo "Unsupported packaging '${packaging}' for ${artifact_id}" >&2
+ exit 1
+ fi
+done < "${inventory}"
+
+while IFS= read -r payload; do
+ for algorithm in md5 sha1 sha256 sha512; do
+ openssl dgst "-${algorithm}" "${payload}" | awk '{print $NF}' >"${payload}.${algorithm}"
+ done
+done < <(find "${repository_root}" -type f ! -name '*.md5' ! -name '*.sha1' ! -name '*.sha256' ! -name '*.sha512')
+
+(
+ cd "${staging}"
+ rm -f "${output}"
+ zip -q -r "${output}" dev
+)
+
+echo "Built Maven Central bundle: ${output}"
diff --git a/scripts/git-gpg-wrapper.sh b/scripts/git-gpg-wrapper.sh
new file mode 100755
index 0000000..3c22b52
--- /dev/null
+++ b/scripts/git-gpg-wrapper.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+: "${GPG_PASSPHRASE_FILE:?GPG_PASSPHRASE_FILE must point to a protected passphrase file}"
+
+exec gpg \
+ --batch \
+ --pinentry-mode loopback \
+ --passphrase-file "${GPG_PASSPHRASE_FILE}" \
+ "$@"
diff --git a/scripts/verify-central-bundle.sh b/scripts/verify-central-bundle.sh
new file mode 100755
index 0000000..6934647
--- /dev/null
+++ b/scripts/verify-central-bundle.sh
@@ -0,0 +1,94 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+if [[ $# -ne 2 ]]; then
+ echo "Usage: $0 " >&2
+ exit 2
+fi
+
+bundle=$1
+version=$2
+project_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
+inventory="${project_root}/config/release/public-artifacts.txt"
+
+if [[ ! -f "${bundle}" ]]; then
+ echo "Central bundle not found: ${bundle}" >&2
+ exit 1
+fi
+
+staging=$(mktemp -d)
+trap 'rm -rf "${staging}"' EXIT
+unzip -q "${bundle}" -d "${staging}"
+
+repository_root="${staging}/dev/juherr/datex4j"
+if [[ ! -d "${repository_root}" ]]; then
+ echo "Bundle does not contain dev/juherr/datex4j" >&2
+ exit 1
+fi
+
+require_payload() {
+ local path=$1
+ if [[ ! -f "${path}" ]]; then
+ echo "Missing release payload: ${path#${staging}/}" >&2
+ exit 1
+ fi
+
+ if [[ ! -f "${path}.asc" ]]; then
+ echo "Missing signature: ${path#${staging}/}.asc" >&2
+ exit 1
+ fi
+ if ! gpg --batch --verify "${path}.asc" "${path}" >/dev/null 2>&1; then
+ echo "Invalid signature: ${path#${staging}/}.asc" >&2
+ exit 1
+ fi
+
+ local actual_checksum checksum expected_checksum
+ for checksum in md5 sha1 sha256 sha512; do
+ if [[ ! -f "${path}.${checksum}" ]]; then
+ echo "Missing ${checksum} checksum: ${path#${staging}/}.${checksum}" >&2
+ exit 1
+ fi
+ expected_checksum=$(openssl dgst "-${checksum}" "${path}" | awk '{print $NF}')
+ actual_checksum=$(tr -d '[:space:]' <"${path}.${checksum}")
+ if [[ "${actual_checksum}" != "${expected_checksum}" ]]; then
+ echo "Invalid ${checksum} checksum: ${path#${staging}/}.${checksum}" >&2
+ exit 1
+ fi
+ done
+}
+
+expected_artifacts=()
+while read -r artifact_id packaging; do
+ [[ -z "${artifact_id}" || "${artifact_id}" == \#* ]] && continue
+ expected_artifacts+=("${artifact_id}")
+
+ artifact_root="${repository_root}/${artifact_id}/${version}"
+ base="${artifact_root}/${artifact_id}-${version}"
+ require_payload "${base}.pom"
+
+ if [[ "${packaging}" == "jar" ]]; then
+ require_payload "${base}.jar"
+ require_payload "${base}-sources.jar"
+ require_payload "${base}-javadoc.jar"
+ elif [[ "${packaging}" != "pom" ]]; then
+ echo "Unsupported packaging '${packaging}' for ${artifact_id}" >&2
+ exit 1
+ fi
+done < "${inventory}"
+
+for forbidden in datex4j-examples datex4j-consumer-tests datex4j-integration-tests; do
+ if [[ -d "${repository_root}/${forbidden}" ]]; then
+ echo "Non-publishable artifact found in bundle: ${forbidden}" >&2
+ exit 1
+ fi
+done
+
+actual_artifacts=$(find "${repository_root}" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort)
+sorted_expected=$(printf '%s\n' "${expected_artifacts[@]}" | sort)
+if [[ "${actual_artifacts}" != "${sorted_expected}" ]]; then
+ echo "Bundle artifact inventory differs from config/release/public-artifacts.txt" >&2
+ diff -u <(printf '%s\n' "${sorted_expected}") <(printf '%s\n' "${actual_artifacts}") || true
+ exit 1
+fi
+
+echo "Verified ${#expected_artifacts[@]} Maven Central artifacts for ${version}."
diff --git a/scripts/verify-revapi-allowlist.sh b/scripts/verify-revapi-allowlist.sh
new file mode 100755
index 0000000..26255a1
--- /dev/null
+++ b/scripts/verify-revapi-allowlist.sh
@@ -0,0 +1,60 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+project_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
+allowlist="${project_root}/config/revapi/accepted-differences.json"
+changelog="${project_root}/CHANGELOG.md"
+pom="${project_root}/pom.xml"
+
+if ! jq --exit-status 'type == "array"' "${allowlist}" >/dev/null; then
+ echo "Revapi allowlist must be a JSON array." >&2
+ exit 1
+fi
+
+entry_count=$(jq 'length' "${allowlist}")
+if [[ "${entry_count}" -eq 0 ]]; then
+ if ! grep -Fq '- revapi.differences
' "${pom}"; then
+ echo "The empty Revapi allowlist requires the revapi.differences transform to be disabled." >&2
+ exit 1
+ fi
+ echo "Verified empty Revapi allowlist."
+ exit 0
+fi
+
+if grep -Fq '- revapi.differences
' "${pom}"; then
+ echo "Enable the revapi.differences transform before accepting differences." >&2
+ exit 1
+fi
+
+if ! jq --exit-status '
+ length == 1
+ and .[0].extension == "revapi.differences"
+ and (. [0].configuration.differences | type == "array" and length > 0)
+ and all(
+ .[0].configuration.differences[];
+ .ignore == true
+ and (.code | type == "string" and length > 0)
+ and (
+ (.old | type == "string" and length > 0)
+ or (.new | type == "string" and length > 0)
+ )
+ and (
+ .justification
+ | type == "string"
+ and startswith("Migration:")
+ and length > 10
+ )
+ )
+' "${allowlist}" >/dev/null; then
+ echo "Each Revapi exception must be exact, ignored explicitly, and justified with 'Migration:'." >&2
+ exit 1
+fi
+
+while IFS= read -r justification; do
+ if ! grep -Fq "${justification}" "${changelog}"; then
+ echo "Revapi migration is missing from CHANGELOG.md: ${justification}" >&2
+ exit 1
+ fi
+done < <(jq --raw-output '.[0].configuration.differences[].justification' "${allowlist}")
+
+echo "Verified Revapi compatibility exceptions."
diff --git a/scripts/verify.sh b/scripts/verify.sh
index 616b266..ac08dac 100755
--- a/scripts/verify.sh
+++ b/scripts/verify.sh
@@ -47,6 +47,7 @@ run_logged "GitHub Actions syntax" mise exec -- actionlint -color
run_logged \
"GitHub Actions security" \
mise exec -- zizmor --min-severity medium .github/workflows
+run_logged "Revapi allowlist policy" ./scripts/verify-revapi-allowlist.sh
run_logged \
"critical XML and validation modules" \
./mvnw --batch-mode --no-transfer-progress \