Command to compare version upgrades
diff --git a/pom.xml b/pom.xml
index 72fdb48..e4346f1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -288,17 +288,17 @@
     <dependency>
       <groupId>org.tomitribe</groupId>
       <artifactId>tomitribe-crest</artifactId>
-      <version>0.15</version>
+      <version>0.19</version>
     </dependency>
     <dependency>
       <groupId>org.tomitribe</groupId>
       <artifactId>swizzle</artifactId>
-      <version>1.1</version>
+      <version>1.3</version>
     </dependency>
     <dependency>
       <groupId>org.tomitribe</groupId>
       <artifactId>tomitribe-util</artifactId>
-      <version>1.3.18</version>
+      <version>1.4.4</version>
     </dependency>
     <dependency>
       <groupId>org.tomitribe.jamira</groupId>
diff --git a/src/main/java/org/apache/openejb/tools/release/Loader.java b/src/main/java/org/apache/openejb/tools/release/Loader.java
index 41a927e..6ab7238 100644
--- a/src/main/java/org/apache/openejb/tools/release/Loader.java
+++ b/src/main/java/org/apache/openejb/tools/release/Loader.java
@@ -17,6 +17,7 @@
 package org.apache.openejb.tools.release;
 
 
+import org.apache.openejb.tools.release.cmd.AnalyzeUpgrades;
 import org.apache.openejb.tools.release.cmd.Dist;
 import org.apache.openejb.tools.release.cmd.ReleaseNotes;
 
@@ -30,7 +31,7 @@
         return Arrays.asList(
                 Dist.class,
                 ReleaseNotes.class,
-                Object.class
+                AnalyzeUpgrades.class
         ).iterator();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/openejb/tools/release/cmd/AnalyzeUpgrades.java b/src/main/java/org/apache/openejb/tools/release/cmd/AnalyzeUpgrades.java
new file mode 100644
index 0000000..c460f38
--- /dev/null
+++ b/src/main/java/org/apache/openejb/tools/release/cmd/AnalyzeUpgrades.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.openejb.tools.release.cmd;
+
+import lombok.Data;
+import org.apache.openejb.tools.release.maven.pom.Dependency;
+import org.apache.openejb.tools.release.maven.pom.PomParser;
+import org.apache.openejb.tools.release.maven.pom.Project;
+import org.apache.openejb.tools.release.util.Exec;
+import org.tomitribe.crest.api.Command;
+import org.tomitribe.crest.api.table.Table;
+import org.tomitribe.util.Files;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+@Command("upgrades")
+public class AnalyzeUpgrades {
+
+    /**
+     * Compares the version upgrades from one version to another using
+     * the bom files for tomee-webprofile, tomee-microprofile, tomee-plus,
+     * and tomee-plume
+     * @param from a previous TomEE version
+     * @param to a newer TomEE version
+     */
+    @Table(fields = "artifactId from to")
+    @Command("compare")
+    public Stream<Upgrade> compare(final String from, final String to) {
+
+        final Map<String, Dependency> previous = getDependencies(from);
+        final Map<String, Dependency> current = getDependencies(to);
+
+        return current.keySet().stream()
+                .sorted()
+                .filter(previous::containsKey)
+                .filter(s -> !previous.get(s).getVersion().equals(current.get(s).getVersion()))
+                .map(s -> new Upgrade(previous.get(s), current.get(s)));
+    }
+
+    private Map<String, Dependency> getDependencies(final String version) {
+        final Map<String, Dependency> map = new HashMap<>();
+        Stream.of("tomee-webprofile", "tomee-microprofile", "tomee-plus", "tomee-plume")
+                .map(s -> getDependencies(s, version))
+                .flatMap(Collection::stream)
+                .forEach(dependency -> map.put(dependency.getGroupId() + ":" + dependency.getArtifactId(), dependency));
+        return map;
+    }
+
+    private List<Dependency> getDependencies(final String artifactId, final String version) {
+        final File pom = resolve("org.apache.tomee.bom", artifactId, version, "pom");
+
+        final Project project = PomParser.parse(pom);
+        return project.getDependencies();
+    }
+
+    private File resolve(final String groupId, final String artifactId, final String version, final String packaging) {
+        try {
+            return mvn(groupId, artifactId, version, packaging);
+        } catch (IllegalStateException e) {
+            Exec.exec("mvn", "org.apache.maven.plugins:maven-dependency-plugin:3.3.0:get",
+                    "-DgroupId=" + groupId,
+                    "-DartifactId=" + artifactId,
+                    "-Dversion=" + version,
+                    "-Dpackaging=" + packaging
+            );
+            return mvn(groupId, artifactId, version, packaging);
+        }
+    }
+
+    public static File mvn(final String group, final String artifact, final String version, final String packaging) {
+        final File repository = repository();
+
+        // org/apache/tomee/tomee-util/7.1.0/tomee-util-7.1.0.jar
+        final File archive = Files.file(
+                repository,
+                group.replace('.', '/'),
+                artifact,
+                version,
+                String.format("%s-%s.%s", artifact, version, packaging));
+
+        Files.exists(archive);
+        Files.file(archive);
+        Files.readable(archive);
+        return archive;
+    }
+
+    private static File repository() {
+        final List<String> path = Arrays.asList(System.getProperty("user.home"), ".m2", "repository");
+
+        File file = null;
+        ;
+        for (final String part : path) {
+            if (part == null) file = new File(part);
+            else file = new File(file, part);
+            Files.exists(file);
+            Files.dir(file);
+        }
+        return file;
+    }
+
+    @Data
+    public static class Upgrade {
+        private final String groupId;
+        private final String artifactId;
+        private final String from;
+        private final String to;
+
+        public Upgrade(final Dependency from, final Dependency to) {
+            this.groupId = to.getGroupId();
+            this.artifactId = to.getArtifactId();
+            this.from = from.getVersion();
+            this.to = to.getVersion();
+        }
+    }
+
+    public static void main(String[] args) {
+        new AnalyzeUpgrades().compare("9.0.0-M8", "9.0.0-M9-SNAPSHOT").forEach(System.out::println);
+    }
+}
diff --git a/src/main/java/org/apache/openejb/tools/release/maven/pom/Coordinates.java b/src/main/java/org/apache/openejb/tools/release/maven/pom/Coordinates.java
new file mode 100644
index 0000000..ced020c
--- /dev/null
+++ b/src/main/java/org/apache/openejb/tools/release/maven/pom/Coordinates.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.openejb.tools.release.maven.pom;
+
+public interface Coordinates {
+    String getGroupId();
+
+    String getArtifactId();
+
+    String getVersion();
+
+    default String gav() {
+        return String.format("%s:%s:%s", getGroupId(), getArtifactId(), getVersion());
+    }
+}
diff --git a/src/main/java/org/apache/openejb/tools/release/maven/pom/Dependency.java b/src/main/java/org/apache/openejb/tools/release/maven/pom/Dependency.java
new file mode 100644
index 0000000..11e7339
--- /dev/null
+++ b/src/main/java/org/apache/openejb/tools/release/maven/pom/Dependency.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.openejb.tools.release.maven.pom;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@XmlRootElement(name = "dependency")
+@XmlAccessorType(XmlAccessType.FIELD)
+@EqualsAndHashCode(onlyExplicitlyIncluded = true)
+public class Dependency implements Coordinates {
+    @XmlElement(name = "groupId")
+    private String groupId;
+
+    @XmlElement(name = "artifactId")
+    private String artifactId;
+
+    @XmlElement(name = "version")
+    private String version;
+
+    @XmlElement(name = "scope")
+    private String scope;
+
+    @XmlElement(name = "classifier")
+    private String classifier;
+
+    @XmlElement(name = "type")
+    private String type;
+}
diff --git a/src/main/java/org/apache/openejb/tools/release/maven/pom/MavenPomException.java b/src/main/java/org/apache/openejb/tools/release/maven/pom/MavenPomException.java
new file mode 100644
index 0000000..9f84c3f
--- /dev/null
+++ b/src/main/java/org/apache/openejb/tools/release/maven/pom/MavenPomException.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.openejb.tools.release.maven.pom;
+
+public class MavenPomException extends IllegalStateException {
+    public MavenPomException(final Exception e) {
+        super(e);
+    }
+
+    public MavenPomException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/src/main/java/org/apache/openejb/tools/release/maven/pom/Parent.java b/src/main/java/org/apache/openejb/tools/release/maven/pom/Parent.java
new file mode 100644
index 0000000..e3e10cc
--- /dev/null
+++ b/src/main/java/org/apache/openejb/tools/release/maven/pom/Parent.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.openejb.tools.release.maven.pom;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@XmlRootElement(name = "parent")
+@XmlAccessorType(XmlAccessType.FIELD)
+@EqualsAndHashCode(onlyExplicitlyIncluded = true)
+public class Parent implements Coordinates {
+    @XmlElement(name = "groupId")
+    private String groupId;
+
+    @XmlElement(name = "artifactId")
+    private String artifactId;
+
+    @XmlElement(name = "version")
+    private String version;
+
+    @XmlElement(name = "relativePath")
+    private String relativePath;
+}
diff --git a/src/main/java/org/apache/openejb/tools/release/maven/pom/PomParser.java b/src/main/java/org/apache/openejb/tools/release/maven/pom/PomParser.java
new file mode 100644
index 0000000..ad174de
--- /dev/null
+++ b/src/main/java/org/apache/openejb/tools/release/maven/pom/PomParser.java
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.openejb.tools.release.maven.pom;
+
+import org.apache.openejb.tools.release.util.JsonMarshalling;
+import org.tomitribe.swizzle.stream.StreamBuilder;
+import org.tomitribe.util.IO;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+
+public class PomParser {
+
+    public static Project parse(final URL url) {
+        try {
+            final String xml = IO.slurp(url);
+            return parse(xml);
+        } catch (IOException e) {
+            throw new MavenPomException(e);
+        }
+    }
+
+    public static Project parse(final File file) {
+        try {
+            final String xml = IO.slurp(file);
+            return parse(xml);
+        } catch (IOException e) {
+            throw new MavenPomException(e);
+        }
+    }
+
+    public static Project parse(final String xml) {
+        try {
+            final JAXBContext context = JAXBContext.newInstance(Project.class);
+            final Unmarshaller unmarshaller = context.createUnmarshaller();
+
+            final Project project = (Project) unmarshaller.unmarshal(IO.read(trimPomXml(xml)));
+
+            return interpolate(project);
+        } catch (Exception e) {
+            throw new MavenPomException(e);
+        }
+    }
+
+    /**
+     * JAXB is unforgiving if you do not model the xml schema in 100% entirety.
+     *
+     * We only want a few key elements and do not need all the rest, so we use this method
+     * to strip out all the stuff we do not need, leaving only what we can unmarshal.
+     */
+    private static String trimPomXml(final String rawXml) throws IOException, ParserConfigurationException, SAXException, TransformerException {
+
+        /*
+         * Read the xml into a dom and strip out the parts we do not need
+         */
+        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        final DocumentBuilder builder = factory.newDocumentBuilder();
+
+        final Document document = builder.parse(IO.read(rawXml.trim()));
+        final Element documentElement = document.getDocumentElement();
+
+        /*
+         * Strip off the namespace.  Some people use it, some do not.  JAXB is incapable of
+         * being flexible on this so we normalize everything to not have the namespace.
+         */
+        final NamedNodeMap attributes = documentElement.getAttributes();
+        while (attributes.getLength() > 0) {
+            final Node item = attributes.item(0);
+            attributes.removeNamedItem(item.getNodeName());
+        }
+
+        /*
+         * These are the only elements we care about
+         */
+        final Predicate<Node> wanted = element("parent")
+                .or(element("groupId"))
+                .or(element("artifactId"))
+                .or(element("version"))
+                .or(element("properties"))
+                .or(element("dependencies"));
+
+        /*
+         * Remove anything but the wanted elements
+         */
+        nodes(documentElement).stream()
+                .filter(wanted.negate())
+                .forEach(documentElement::removeChild);
+
+        /*
+         * Long-winded boilerplate code to write the DOM back out as xml
+         */
+        final TransformerFactory tf = TransformerFactory.newInstance();
+        final Transformer trans = tf.newTransformer();
+        final StringWriter sw = new StringWriter();
+        trans.transform(new DOMSource(document), new StreamResult(sw));
+        return sw.toString();
+    }
+
+    private static Predicate<Node> element(final String name) {
+        return node -> node.getNodeName().equals(name);
+    }
+
+    private static List<Node> nodes(final Element documentElement) {
+        final List<Node> nodes = new ArrayList<Node>();
+        final NodeList childNodes = documentElement.getChildNodes();
+        for (int i = 0; i < childNodes.getLength(); i++) {
+            final Node item = childNodes.item(i);
+            nodes.add(item);
+        }
+        return nodes;
+    }
+
+    private static Project interpolate(final Project project) throws IOException {
+        if (project.getProperties() == null) return project;
+        final String actualJson = JsonMarshalling.toFormattedJson(project);
+        final Map<String, String> properties = project.getProperties();
+
+        final String interpolated = interpolate(actualJson, properties);
+        return JsonMarshalling.unmarshal(Project.class, interpolated);
+    }
+
+    public static String interpolate(final String content, final Map<String, String> properties) throws IOException {
+        String previous, current = content;
+
+        do {
+            previous = current;
+            final InputStream inputStream = StreamBuilder.create(IO.read(current))
+                    .replace("${", "}", s -> {
+                        final String value = properties.get(s);
+                        if (value == null) return "${" + s + "}";
+                        return value;
+                    }).get();
+            current = IO.slurp(inputStream);
+        } while (!current.equals(previous));
+
+        return current;
+    }
+}
diff --git a/src/main/java/org/apache/openejb/tools/release/maven/pom/Project.java b/src/main/java/org/apache/openejb/tools/release/maven/pom/Project.java
new file mode 100644
index 0000000..53c436a
--- /dev/null
+++ b/src/main/java/org/apache/openejb/tools/release/maven/pom/Project.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.openejb.tools.release.maven.pom;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.tomitribe.util.IO;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@XmlRootElement(name = "project")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class Project implements Coordinates {
+
+    @XmlTransient
+    private File file;
+
+    @XmlElement(name = "parent")
+    private Parent parent;
+
+    @XmlElement(name = "groupId")
+    private String groupId;
+
+    @XmlElement(name = "artifactId")
+    private String artifactId;
+
+    @XmlElement(name = "version")
+    private String version;
+
+    @XmlElement(name = "properties")
+    @XmlJavaTypeAdapter(PropertiesAdapter.class)
+    private Map<String, String> properties;
+
+    @XmlElementWrapper(name = "dependencies")
+    @XmlElement(name = "dependency")
+    private List<Dependency> dependencies = new ArrayList<>();
+
+
+    public static Project parse(final File file) {
+        try {
+            final Project project = PomParser.parse(IO.slurp(file));
+            project.setFile(file);
+            return project;
+        } catch (IOException e) {
+            throw new MavenPomException("Unable to read file " + file.getAbsolutePath(), e);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/openejb/tools/release/maven/pom/Properties.java b/src/main/java/org/apache/openejb/tools/release/maven/pom/Properties.java
new file mode 100644
index 0000000..a2627c5
--- /dev/null
+++ b/src/main/java/org/apache/openejb/tools/release/maven/pom/Properties.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.openejb.tools.release.maven.pom;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.w3c.dom.Node;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAnyElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import java.util.List;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@XmlRootElement(name = "properties")
+@XmlAccessorType(XmlAccessType.FIELD)
+@EqualsAndHashCode(onlyExplicitlyIncluded = true)
+public class Properties {
+
+    @XmlAnyElement(lax = true)
+    protected List<Node> any;
+}
diff --git a/src/main/java/org/apache/openejb/tools/release/maven/pom/PropertiesAdapter.java b/src/main/java/org/apache/openejb/tools/release/maven/pom/PropertiesAdapter.java
new file mode 100644
index 0000000..8a52376
--- /dev/null
+++ b/src/main/java/org/apache/openejb/tools/release/maven/pom/PropertiesAdapter.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.openejb.tools.release.maven.pom;
+
+import org.w3c.dom.Node;
+
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class PropertiesAdapter extends XmlAdapter<Properties, Map<String, String>> {
+
+    @Override
+    public Map<String, String> unmarshal(final Properties v) throws Exception {
+        final Map<String, String> map = new LinkedHashMap<>();
+
+        for (final Node node : v.getAny()) {
+            final String name = node.getNodeName();
+            final String value = node.getTextContent();
+            map.put(name, value);
+        }
+
+        return map;
+    }
+
+    @Override
+    public Properties marshal(final Map<String, String> v) throws Exception {
+        return null;
+    }
+}
diff --git a/src/main/java/org/apache/openejb/tools/release/util/JsonMarshalling.java b/src/main/java/org/apache/openejb/tools/release/util/JsonMarshalling.java
new file mode 100644
index 0000000..1111195
--- /dev/null
+++ b/src/main/java/org/apache/openejb/tools/release/util/JsonMarshalling.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.openejb.tools.release.util;
+
+import org.tomitribe.util.IO;
+import org.tomitribe.util.PrintString;
+
+import javax.json.bind.Jsonb;
+import javax.json.bind.JsonbBuilder;
+import javax.json.bind.JsonbConfig;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import static javax.json.bind.JsonbConfig.FORMATTING;
+
+public class JsonMarshalling {
+    private JsonMarshalling() {
+    }
+
+    public static <JsonbType> JsonbType unmarshal(final Class<JsonbType> type, final File jsonFile) {
+        try {
+            final Jsonb jsonb = JsonbBuilder.create();
+            try (final InputStream stream = IO.read(jsonFile)) {
+                return jsonb.fromJson(stream, type);
+            }
+        } catch (IOException e) {
+            throw new IllegalStateException(String.format("Unable to unmarshal %s from file %s", type.getSimpleName(), jsonFile.getAbsolutePath()), e);
+        }
+    }
+
+    public static <JsonbType> JsonbType unmarshal(final Class<JsonbType> type, final String json) {
+        try {
+            final Jsonb jsonb = JsonbBuilder.create();
+            try (final InputStream stream = IO.read(json)) {
+                return jsonb.fromJson(stream, type);
+            }
+        } catch (IOException e) {
+            throw new IllegalStateException(String.format("Unable to unmarshal %s", type.getSimpleName()), e);
+        }
+    }
+
+    public static <JsonObject> File toFormattedJson(final JsonObject jsonObject, final File file) {
+        try {
+            final JsonbConfig config = new JsonbConfig();
+            config.setProperty(FORMATTING, true);
+            final Jsonb jsonb = JsonbBuilder.create(config);
+            try (final OutputStream write = IO.write(file)) {
+                jsonb.toJson(jsonObject, write);
+            }
+            return file;
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new IllegalStateException(e);
+        }
+    }
+
+    public static <JsonObject> String toFormattedJson(final JsonObject jsonObject) {
+        final JsonbConfig config = new JsonbConfig();
+        config.setProperty(FORMATTING, true);
+        final Jsonb jsonb = JsonbBuilder.create(config);
+        final PrintString out = new PrintString();
+        jsonb.toJson(jsonObject, out);
+        return out.toString();
+    }
+}