Repository: CycloneDX/cyclonedx-core-java
Pull request #174 (2022-02-16 20:03:54)

Implement models for 1.4 spec and serialization and deserialization


Patch:
diff --git a/README.md b/README.md
index ea4cf7c97..a739b4a48 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ Maven Usage
 <dependency>
     <groupId>org.cyclonedx</groupId>
     <artifactId>cyclonedx-core-java</artifactId>
-    <version>5.0.4</version>
+    <version>6.0.0</version>
 </dependency>
 ```
 
@@ -33,6 +33,7 @@ the CycloneDX version supported by the target system.
 
 | Version | Schema Version | Format(s) |
 | ------- | ----------------- | --------- |
+| 6.x | CycloneDX v1.4 | XML/JSON |
 | 5.x | CycloneDX v1.3 | XML/JSON |
 | 4.x | CycloneDX v1.2 | XML/JSON |
 | 3.x | CycloneDX v1.2 | XML/JSON |
diff --git a/src/main/java/org/cyclonedx/generators/json/AbstractBomJsonGenerator.java b/src/main/java/org/cyclonedx/generators/json/AbstractBomJsonGenerator.java
index 9a601e3fd..a678a739c 100644
--- a/src/main/java/org/cyclonedx/generators/json/AbstractBomJsonGenerator.java
+++ b/src/main/java/org/cyclonedx/generators/json/AbstractBomJsonGenerator.java
@@ -19,9 +19,12 @@
 package org.cyclonedx.generators.json;
 
 import java.lang.reflect.Field;
+
+import com.fasterxml.jackson.annotation.JsonValue;
 import org.cyclonedx.CycloneDxSchema;
 import org.cyclonedx.exception.GeneratorException;
 import org.cyclonedx.model.Bom;
+import org.cyclonedx.model.BomReference;
 import org.cyclonedx.util.CollectionTypeSerializer;
 import org.cyclonedx.util.ComponentWrapperSerializer;
 import org.cyclonedx.util.LicenseChoiceSerializer;
@@ -32,6 +35,7 @@ import com.fasterxml.jackson.core.util.DefaultIndenter;
 import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.cyclonedx.util.mixin.MixInBomReference;
 
 public abstract class AbstractBomJsonGenerator extends CycloneDxSchema implements BomJsonGenerator {
 
@@ -82,6 +86,7 @@ public abstract class AbstractBomJsonGenerator extends CycloneDxSchema implement
 
     String toJson(final Bom bom, final boolean prettyPrint) throws GeneratorException {
         try {
+            mapper.addMixIn(BomReference.class, MixInBomReference.class);
             if (prettyPrint) {
                 return mapper.writer(prettyPrinter).writeValueAsString(bom);
             }
diff --git a/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator14.java b/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator14.java
index c3090d703..30835d8b4 100644
--- a/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator14.java
+++ b/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator14.java
@@ -60,7 +60,7 @@ public class BomJsonGenerator14 extends AbstractBomJsonGenerator implements BomJ
     /**
      * Creates a CycloneDX BOM from a set of Components.
      * @return an JSON Document representing a CycloneDX BoM
-     * @since 5.0.0
+     * @since 6.0.0
      */
     public JsonObject toJsonObject() {
         try {
diff --git a/src/main/java/org/cyclonedx/generators/xml/AbstractBomXmlGenerator.java b/src/main/java/org/cyclonedx/generators/xml/AbstractBomXmlGenerator.java
index 816c35182..c8ac3e7a2 100644
--- a/src/main/java/org/cyclonedx/generators/xml/AbstractBomXmlGenerator.java
+++ b/src/main/java/org/cyclonedx/generators/xml/AbstractBomXmlGenerator.java
@@ -50,9 +50,9 @@ public abstract class AbstractBomXmlGenerator extends CycloneDxSchema implements
         setupObjectMapper(mapper);
     }
 
-	public ObjectMapper getMapper() {
-		return mapper;
-	}
+    public ObjectMapper getMapper() {
+        return mapper;
+    }
 	
     Document doc;
 
@@ -71,6 +71,8 @@ public abstract class AbstractBomXmlGenerator extends CycloneDxSchema implements
             registerDependencyModule(mapper, false);
         } else if (this.getSchemaVersion().getVersion() == 1.3) {
             registerDependencyModule(mapper, false);
+        } else if (this.getSchemaVersion().getVersion() == 1.4) {
+            registerDependencyModule(mapper, false);
         }
     }
 
diff --git a/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator14.java b/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator14.java
index 895de79d1..877d7c6be 100644
--- a/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator14.java
+++ b/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator14.java
@@ -57,7 +57,7 @@ public class BomXmlGenerator14 extends AbstractBomXmlGenerator implements BomXml
     /**
      * Creates a CycloneDX BoM from a set of Components.
      * @return an XML Document representing a CycloneDX BoM
-     * @since 5.0.0
+     * @since 6.0.0
      */
     public Document generate() throws ParserConfigurationException {
         return generateDocument(this.bom);
diff --git a/src/main/java/org/cyclonedx/model/Ancestors.java b/src/main/java/org/cyclonedx/model/Ancestors.java
index c69411a3b..21f8a816f 100644
--- a/src/main/java/org/cyclonedx/model/Ancestors.java
+++ b/src/main/java/org/cyclonedx/model/Ancestors.java
@@ -22,6 +22,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
 public class Ancestors extends ComponentWrapper {
 }
diff --git a/src/main/java/org/cyclonedx/model/AttachmentText.java b/src/main/java/org/cyclonedx/model/AttachmentText.java
index 1154db402..f649ec315 100644
--- a/src/main/java/org/cyclonedx/model/AttachmentText.java
+++ b/src/main/java/org/cyclonedx/model/AttachmentText.java
@@ -20,10 +20,14 @@ package org.cyclonedx.model;
 
 import java.util.Objects;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText;
 
 @SuppressWarnings("unused")
+@JsonPropertyOrder({
+    "content-type",
+    "encoding"})
 public class AttachmentText {
 
     @JacksonXmlProperty(isAttribute = true)
diff --git a/src/main/java/org/cyclonedx/model/Bom.java b/src/main/java/org/cyclonedx/model/Bom.java
index ff1498504..fc60a7e24 100644
--- a/src/main/java/org/cyclonedx/model/Bom.java
+++ b/src/main/java/org/cyclonedx/model/Bom.java
@@ -28,12 +28,14 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+import org.cyclonedx.model.vulnerability.Vulnerability;
 import org.cyclonedx.util.DependencyDeserializer;
+import org.cyclonedx.util.VulnerabilityDeserializer;
 
 @SuppressWarnings("unused")
 @JacksonXmlRootElement(localName = "bom")
 @JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
 @JsonPropertyOrder({
         "bomFormat",
         "specVersion",
@@ -45,7 +47,9 @@ import org.cyclonedx.util.DependencyDeserializer;
         "externalReferences",
         "dependencies",
         "compositions",
-        "properties"
+        "vulnerabilities",
+        "properties",
+        "signature"
 })
 public class Bom extends ExtensibleElement {
 
@@ -53,25 +57,29 @@ public class Bom extends ExtensibleElement {
     @JacksonXmlProperty(isAttribute = true)
     private String xmlns;
 
-    @VersionFilter(versions = {"1.2", "1.3"})
+    @VersionFilter(versions = {"1.2", "1.3", "1.4"})
     private Metadata metadata;
 
-    @VersionFilter(versions = {"1.0", "1.1", "1.2", "1.3"})
+    @VersionFilter(versions = {"1.0", "1.1", "1.2", "1.3", "1.4"})
     private List<Component> components;
 
-    @VersionFilter(versions = {"1.2", "1.3"})
+    @VersionFilter(versions = {"1.2", "1.3", "1.4"})
     private List<Service> services;
 
-    @VersionFilter(versions = {"1.1", "1.2", "1.3"})
+    @VersionFilter(versions = {"1.1", "1.2", "1.3", "1.4"})
     private List<Dependency> dependencies;
 
-    @VersionFilter(versions = {"1.1", "1.2", "1.3"})
+    @VersionFilter(versions = {"1.1", "1.2", "1.3", "1.4"})
     private List<ExternalReference> externalReferences;
 
-    @VersionFilter(versions = {"1.3"})
+    @VersionFilter(versions = {"1.3", "1.4"})
     private List<Composition> compositions;
 
-    @VersionFilter(versions = {"1.3"})
+    @VersionFilter(versions = {"1.4"})
+    @JsonDeserialize(using = VulnerabilityDeserializer.class)
+    private List<Vulnerability> vulnerabilities;
+
+    @VersionFilter(versions = {"1.3", "1.4"})
     private List<Property> properties;
 
     @JacksonXmlProperty(isAttribute = true)
@@ -86,6 +94,10 @@ public class Bom extends ExtensibleElement {
     @JsonOnly
     private String bomFormat;
 
+    @JsonOnly
+    @VersionFilter(versions = {"1.4"})
+    private Signature signature;
+
     public Metadata getMetadata() {
         return metadata;
     }
@@ -170,6 +182,12 @@ public class Bom extends ExtensibleElement {
         this.compositions = compositions;
     }
 
+    @JacksonXmlElementWrapper(localName = "vulnerabilities")
+    @JacksonXmlProperty(localName = "vulnerability")
+    public List<Vulnerability> getVulnerabilities() { return vulnerabilities; }
+
+    public void setVulnerabilities(List<Vulnerability> vulnerabilities) { this.vulnerabilities = vulnerabilities; }
+
     @JacksonXmlElementWrapper(localName = "properties")
     @JacksonXmlProperty(localName = "property")
     public List<Property> getProperties() {
@@ -225,6 +243,10 @@ public class Bom extends ExtensibleElement {
         return bomFormat;
     }
 
+    public Signature getSignature() { return signature; }
+
+    public void setSignature(Signature signature) { this.signature = signature; }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
@@ -236,6 +258,7 @@ public class Bom extends ExtensibleElement {
                 Objects.equals(dependencies, bom.dependencies) &&
                 Objects.equals(externalReferences, bom.externalReferences) &&
                 Objects.equals(compositions, bom.compositions) &&
+                Objects.equals(vulnerabilities, bom.vulnerabilities) &&
                 Objects.equals(properties, bom.properties) &&
                 Objects.equals(serialNumber, bom.serialNumber) &&
                 Objects.equals(specVersion, bom.specVersion);
@@ -243,6 +266,6 @@ public class Bom extends ExtensibleElement {
 
     @Override
     public int hashCode() {
-        return Objects.hash(metadata, components, dependencies, externalReferences, compositions, properties, version, serialNumber, specVersion);
+        return Objects.hash(metadata, components, dependencies, externalReferences, compositions, vulnerabilities, properties, version, serialNumber, specVersion);
     }
 }
diff --git a/src/main/java/org/cyclonedx/model/Component.java b/src/main/java/org/cyclonedx/model/Component.java
index 5ca7e4d61..9869854b3 100644
--- a/src/main/java/org/cyclonedx/model/Component.java
+++ b/src/main/java/org/cyclonedx/model/Component.java
@@ -38,7 +38,7 @@ import com.github.packageurl.PackageURL;
 @SuppressWarnings("unused")
 @JacksonXmlRootElement(localName = "component")
 @JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
 @JsonPropertyOrder(
     {"supplier",
      "author",
@@ -59,7 +59,9 @@ import com.github.packageurl.PackageURL;
      "externalReferences",
      "properties",
      "components",
-     "evidence"
+     "evidence",
+     "releaseNotes",
+     "signature"
     })
 public class Component extends ExtensibleElement {
 
@@ -116,11 +118,11 @@ public class Component extends ExtensibleElement {
     private String bomRef;
     @JacksonXmlProperty(isAttribute = true)
     private String mimeType;
-    @VersionFilter(versions = {"1.2", "1.3"})
+    @VersionFilter(versions = {"1.2", "1.3", "1.4"})
     private OrganizationalEntity supplier;
-    @VersionFilter(versions = {"1.2", "1.3"})
+    @VersionFilter(versions = {"1.2", "1.3", "1.4"})
     private String author;
-    @VersionFilter(versions = {"1.1", "1.2", "1.3"})
+    @VersionFilter(versions = {"1.1", "1.2", "1.3", "1.4"})
     private String publisher;
     private String group;
     private String name;
@@ -132,20 +134,25 @@ public class Component extends ExtensibleElement {
     private String copyright;
     private String cpe;
     private String purl;
-    @VersionFilter(versions = {"1.2", "1.3"})
+    @VersionFilter(versions = {"1.2", "1.3", "1.4"})
     private Swid swid;
     private Boolean modified;
-    @VersionFilter(versions = {"1.1", "1.2", "1.3"})
+    @VersionFilter(versions = {"1.1", "1.2", "1.3", "1.4"})
     private Pedigree pedigree;
-    @VersionFilter(versions = {"1.1", "1.2", "1.3"})
+    @VersionFilter(versions = {"1.1", "1.2", "1.3", "1.4"})
     private List<ExternalReference> externalReferences;
-    @VersionFilter(versions = {"1.3"})
+    @VersionFilter(versions = {"1.3", "1.4"})
     private List<Property> properties;
     private List<Component> components;
-    @VersionFilter(versions = {"1.3"})
+    @VersionFilter(versions = {"1.3", "1.4"})
     private Evidence evidence;
     @JacksonXmlProperty(isAttribute = true)
     private Type type;
+    @VersionFilter(versions = {"1.4"})
+    private ReleaseNotes releaseNotes;
+    @JsonOnly
+    @VersionFilter(versions = {"1.4"})
+    private Signature signature;
 
     public String getBomRef() {
         return bomRef;
@@ -388,9 +395,17 @@ public class Component extends ExtensibleElement {
         this.type = type;
     }
 
+    public ReleaseNotes getReleaseNotes() { return releaseNotes; }
+
+    public void setReleaseNotes(ReleaseNotes releaseNotes) { this.releaseNotes = releaseNotes; }
+
+    public Signature getSignature() { return signature; }
+
+    public void setSignature(Signature signature) { this.signature = signature; }
+
     @Override
     public int hashCode() {
-        return Objects.hash(author, publisher, group, name, version, description, scope, hashes, license, copyright, cpe, purl, swid, modified, components, evidence, type);
+        return Objects.hash(author, publisher, group, name, version, description, scope, hashes, license, copyright, cpe, purl, swid, modified, components, evidence, releaseNotes, type);
     }
 
     @Override
@@ -416,6 +431,7 @@ public class Component extends ExtensibleElement {
                 Objects.equals(components, component.components) &&
                 Objects.equals(evidence, component.evidence) &&
                 Objects.equals(mimeType, component.mimeType) &&
+                Objects.equals(releaseNotes, component.releaseNotes) &&
                 Objects.equals(type, component.type);
     }
 }
diff --git a/src/main/java/org/cyclonedx/model/Composition.java b/src/main/java/org/cyclonedx/model/Composition.java
index 9ac457629..d2ff427e8 100644
--- a/src/main/java/org/cyclonedx/model/Composition.java
+++ b/src/main/java/org/cyclonedx/model/Composition.java
@@ -22,12 +22,15 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
 import java.util.ArrayList;
 import java.util.List;
 
 @SuppressWarnings("unused")
 @JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
 @JsonPropertyOrder({"aggregate", "assemblies", "dependencies"})
 public class Composition {
 
@@ -68,6 +71,8 @@ public class Composition {
         this.aggregate = aggregate;
     }
 
+    @JacksonXmlElementWrapper(localName = "assemblies")
+    @JacksonXmlProperty(localName = "assembly")
     public List<BomReference> getAssemblies() {
         return assemblies;
     }
@@ -83,6 +88,8 @@ public class Composition {
         assemblies.add(assembly);
     }
 
+    @JacksonXmlElementWrapper(localName = "dependencies")
+    @JacksonXmlProperty(localName = "dependency")
     public List<BomReference> getDependencies() {
         return dependencies;
     }
diff --git a/src/main/java/org/cyclonedx/model/Dependency.java b/src/main/java/org/cyclonedx/model/Dependency.java
index 3bddc5778..125d18b4e 100644
--- a/src/main/java/org/cyclonedx/model/Dependency.java
+++ b/src/main/java/org/cyclonedx/model/Dependency.java
@@ -28,7 +28,7 @@ import java.util.List;
 import java.util.Objects;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonInclude(JsonInclude.Include.NON_NULL) // We want to include empty List<Dependency>, so DO NOT use Include.NON_EMPTY
 public class Dependency extends BomReference {
 
     @JsonProperty("dependsOn")
diff --git a/src/main/java/org/cyclonedx/model/Descendants.java b/src/main/java/org/cyclonedx/model/Descendants.java
index 8d318734a..4658f0a68 100644
--- a/src/main/java/org/cyclonedx/model/Descendants.java
+++ b/src/main/java/org/cyclonedx/model/Descendants.java
@@ -22,6 +22,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
 public class Descendants extends ComponentWrapper {
 }
diff --git a/src/main/java/org/cyclonedx/model/Evidence.java b/src/main/java/org/cyclonedx/model/Evidence.java
index a5c1d8365..112fba90c 100644
--- a/src/main/java/org/cyclonedx/model/Evidence.java
+++ b/src/main/java/org/cyclonedx/model/Evidence.java
@@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonPropertyOrder;
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import org.cyclonedx.util.LicenseDeserializer;
 import java.util.ArrayList;
@@ -30,7 +31,7 @@ import java.util.List;
 
 @SuppressWarnings("unused")
 @JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
 @JsonPropertyOrder({"licenses", "copyright"})
 public class Evidence extends ExtensibleElement {
 
@@ -48,6 +49,7 @@ public class Evidence extends ExtensibleElement {
         this.license = licenseChoice;
     }
 
+    @JacksonXmlElementWrapper(useWrapping = false)
     public List<Copyright> getCopyright() {
         return copyright;
     }
diff --git a/src/main/java/org/cyclonedx/model/ExternalReference.java b/src/main/java/org/cyclonedx/model/ExternalReference.java
index 44f9190c6..5e78e895c 100644
--- a/src/main/java/org/cyclonedx/model/ExternalReference.java
+++ b/src/main/java/org/cyclonedx/model/ExternalReference.java
@@ -66,6 +66,8 @@ public class ExternalReference {
         BUILD_META("build-meta"),
         @JsonProperty("build-system")
         BUILD_SYSTEM("build-system"),
+        @JsonProperty("release-notes")
+        RELEASE_NOTES("release-notes"),
         @JsonProperty("other")
         OTHER("other");
 
@@ -85,7 +87,7 @@ public class ExternalReference {
     private Type type;
     private String comment;
 
-    @VersionFilter(versions = {"1.3"})
+    @VersionFilter(versions = {"1.3", "1.4"})
     private List<Hash> hashes;
 
     public String getUrl() {
diff --git a/src/main/java/org/cyclonedx/model/Issue.java b/src/main/java/org/cyclonedx/model/Issue.java
index cc369e2b1..42e042c48 100644
--- a/src/main/java/org/cyclonedx/model/Issue.java
+++ b/src/main/java/org/cyclonedx/model/Issue.java
@@ -29,7 +29,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
 @JsonPropertyOrder({"id", "name", "description", "source", "reference"})
 public class Issue {
 
diff --git a/src/main/java/org/cyclonedx/model/LicenseChoice.java b/src/main/java/org/cyclonedx/model/LicenseChoice.java
index 6fe79ce21..4a1fa1db8 100644
--- a/src/main/java/org/cyclonedx/model/LicenseChoice.java
+++ b/src/main/java/org/cyclonedx/model/LicenseChoice.java
@@ -27,7 +27,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
 public class LicenseChoice {
 
     private List<License> license;
diff --git a/src/main/java/org/cyclonedx/model/Metadata.java b/src/main/java/org/cyclonedx/model/Metadata.java
index 7ce6177f9..1d5ce93a8 100644
--- a/src/main/java/org/cyclonedx/model/Metadata.java
+++ b/src/main/java/org/cyclonedx/model/Metadata.java
@@ -34,33 +34,33 @@ import java.util.List;
 import java.util.Objects;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
 @JsonPropertyOrder({"timestamp", "tools", "authors", "component", "manufacture", "supplier", "licenses", "properties"})
 public class Metadata extends ExtensibleElement {
 
     @JsonSerialize(using = CustomDateSerializer.class)
-    @VersionFilter(versions = {"1.2", "1.3"})
+    @VersionFilter(versions = {"1.2", "1.3", "1.4"})
     private Date timestamp = new Date();
 
-    @VersionFilter(versions = {"1.2", "1.3"})
+    @VersionFilter(versions = {"1.2", "1.3", "1.4"})
     private List<Tool> tools;
 
-    @VersionFilter(versions = {"1.2", "1.3"})
+    @VersionFilter(versions = {"1.2", "1.3", "1.4"})
     private List<OrganizationalContact> authors;
 
-    @VersionFilter(versions = {"1.2", "1.3"})
+    @VersionFilter(versions = {"1.2", "1.3", "1.4"})
     private Component component;
 
-    @VersionFilter(versions = {"1.2", "1.3"})
+    @VersionFilter(versions = {"1.2", "1.3", "1.4"})
     private OrganizationalEntity manufacture;
 
-    @VersionFilter(versions = {"1.2", "1.3"})
+    @VersionFilter(versions = {"1.2", "1.3", "1.4"})
     private OrganizationalEntity supplier;
 
-    @VersionFilter(versions = {"1.3"})
+    @VersionFilter(versions = {"1.3", "1.4"})
     private LicenseChoice license;
 
-    @VersionFilter(versions = {"1.3"})
+    @VersionFilter(versions = {"1.3", "1.4"})
     private List<Property> properties;
 
     public Date getTimestamp() {
diff --git a/src/main/java/org/cyclonedx/model/OrganizationalEntity.java b/src/main/java/org/cyclonedx/model/OrganizationalEntity.java
index c6665ba17..32f45287f 100644
--- a/src/main/java/org/cyclonedx/model/OrganizationalEntity.java
+++ b/src/main/java/org/cyclonedx/model/OrganizationalEntity.java
@@ -29,7 +29,7 @@ import java.util.List;
 import java.util.Objects;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
 @JsonPropertyOrder({"name", "url", "contact"})
 public class OrganizationalEntity extends ExtensibleElement {
 
diff --git a/src/main/java/org/cyclonedx/model/Patch.java b/src/main/java/org/cyclonedx/model/Patch.java
index f554dcf5d..b21108d8a 100644
--- a/src/main/java/org/cyclonedx/model/Patch.java
+++ b/src/main/java/org/cyclonedx/model/Patch.java
@@ -28,7 +28,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 
 @SuppressWarnings("unused")
 @JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
 @JsonPropertyOrder({"diff", "resolves"})
 public class Patch {
 
diff --git a/src/main/java/org/cyclonedx/model/Pedigree.java b/src/main/java/org/cyclonedx/model/Pedigree.java
index a72d415d8..fbd96d216 100644
--- a/src/main/java/org/cyclonedx/model/Pedigree.java
+++ b/src/main/java/org/cyclonedx/model/Pedigree.java
@@ -30,7 +30,7 @@ import org.cyclonedx.util.ComponentWrapperDeserializer;
 
 @SuppressWarnings("unused")
 @JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
 @JsonPropertyOrder({"ancestors", "descendants", "variants", "commits", "patches", "notes"})
 public class Pedigree extends ExtensibleElement {
 
@@ -45,7 +45,7 @@ public class Pedigree extends ExtensibleElement {
 
     private List<Commit> commits;
 
-    @VersionFilter(versions = {"1.2", "1.3"})
+    @VersionFilter(versions = {"1.2", "1.3", "1.4"})
     private List<Patch> patches;
 
     private String notes;
diff --git a/src/main/java/org/cyclonedx/model/ReleaseNotes.java b/src/main/java/org/cyclonedx/model/ReleaseNotes.java
new file mode 100644
index 000000000..051918eca
--- /dev/null
+++ b/src/main/java/org/cyclonedx/model/ReleaseNotes.java
@@ -0,0 +1,276 @@
+/*
+ * This file is part of CycloneDX Core (Java).
+ *
+ * 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
+ *
+ *     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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) OWASP Foundation. All Rights Reserved.
+ */
+package org.cyclonedx.model;
+
+import java.util.Date;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import org.cyclonedx.util.CustomDateSerializer;
+
+/**
+ * @since 6.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+@JsonPropertyOrder({
+    "type",
+    "title",
+    "featuredImage",
+    "socialImage",
+    "description",
+    "timestamp",
+    "aliases",
+    "tags",
+    "resolves",
+    "notes",
+    "properties"
+})
+public class ReleaseNotes
+{
+  public ReleaseNotes() {}
+
+  private String type;
+  private String title;
+  private String featuredImage;
+  private String socialImage;
+  private String description;
+  @JsonSerialize(using = CustomDateSerializer.class)
+  @VersionFilter(versions = {"1.4"})
+  private Date timestamp;
+  private List<String> aliases;
+  private List<String> tags;
+  private List<Resolves> resolves;
+  private List<Notes> notes;
+  private List<Property> properties;
+
+  public String getType() {
+    return type;
+  }
+
+  public void setType(final String type) {
+    this.type = type;
+  }
+
+  public String getTitle() {
+    return title;
+  }
+
+  public void setTitle(final String title) {
+    this.title = title;
+  }
+
+  public String getFeaturedImage() {
+    return featuredImage;
+  }
+
+  public void setFeaturedImage(final String featuredImage) {
+    this.featuredImage = featuredImage;
+  }
+
+  public String getSocialImage() {
+    return socialImage;
+  }
+
+  public void setSocialImage(final String socialImage) {
+    this.socialImage = socialImage;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(final String description) {
+    this.description = description;
+  }
+
+  public Date getTimestamp() {
+    return timestamp;
+  }
+
+  public void setTimestamp(final Date timestamp) {
+    this.timestamp = timestamp;
+  }
+
+  @JacksonXmlElementWrapper(localName = "aliases")
+  @JacksonXmlProperty(localName = "alias")
+  public List<String> getAliases() {
+    return aliases;
+  }
+
+  public void setAliases(final List<String> aliases) {
+    this.aliases = aliases;
+  }
+
+  @JacksonXmlElementWrapper(localName = "tags")
+  @JacksonXmlProperty(localName = "tag")
+  public List<String> getTags() {
+    return tags;
+  }
+
+  public void setTags(final List<String> tags) {
+    this.tags = tags;
+  }
+
+  @JacksonXmlElementWrapper(localName = "resolves")
+  @JacksonXmlProperty(localName = "issue")
+  public List<Resolves> getResolves() {
+    return resolves;
+  }
+
+  public void setResolves(final List<Resolves> resolves) {
+    this.resolves = resolves;
+  }
+
+  @JacksonXmlElementWrapper(localName = "notes")
+  @JacksonXmlProperty(localName = "note")
+  public List<Notes> getNotes() {
+    return notes;
+  }
+
+  public void setNotes(final List<Notes> notes) {
+    this.notes = notes;
+  }
+
+  public List<Property> getProperties() {
+    return properties;
+  }
+
+  public void setProperties(final List<Property> properties) {
+    this.properties = properties;
+  }
+
+  public static class Resolves {
+
+    public enum Type {
+      @JsonProperty("defect")
+      DEFECT("defect"),
+      @JsonProperty("enhancement")
+      ENHANCEMENT("enhancement"),
+      @JsonProperty("security")
+      SECURITY("security");
+
+      private final String name;
+
+      public String getResolvesName() {
+        return this.name;
+      }
+
+      Type(String name) {
+        this.name = name;
+      }
+
+      public static Resolves.Type fromString(String text) {
+        for (Resolves.Type t : Resolves.Type.values()) {
+          if (t.name.equals(text)) {
+            return t;
+
+          }
+        }
+        return null;
+      }
+    }
+
+    @JacksonXmlProperty(isAttribute = true)
+    private Type type;
+
+    private String id;
+    private String name;
+    private String description;
+    private Source source;
+    @JacksonXmlElementWrapper(localName = "references")
+    @JacksonXmlProperty(localName = "url")
+    private List<String> references;
+
+    public Type getType() {
+      return type;
+    }
+
+    public void setType(final Type type) {
+      this.type = type;
+    }
+
+    public String getId() {
+      return id;
+    }
+
+    public void setId(final String id) {
+      this.id = id;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    public void setName(final String name) {
+      this.name = name;
+    }
+
+    public String getDescription() {
+      return description;
+    }
+
+    public void setDescription(final String description) {
+      this.description = description;
+    }
+
+    public Source getSource() {
+      return source;
+    }
+
+    public void setSource(final Source source) {
+      this.source = source;
+    }
+
+    public List<String> getReferences() {
+      return references;
+    }
+
+    public void setReferences(final List<String> references) {
+      this.references = references;
+    }
+  }
+
+  public static class Notes {
+    private String locale;
+    private AttachmentText text;
+
+    public String getLocale() {
+      return locale;
+    }
+
+    public void setLocale(final String locale) {
+      this.locale = locale;
+    }
+
+    public AttachmentText getText() {
+      return text;
+    }
+
+    public void setText(final AttachmentText text) {
+      this.text = text;
+    }
+  }
+}
diff --git a/src/main/java/org/cyclonedx/model/Service.java b/src/main/java/org/cyclonedx/model/Service.java
index 40b894063..275211b93 100644
--- a/src/main/java/org/cyclonedx/model/Service.java
+++ b/src/main/java/org/cyclonedx/model/Service.java
@@ -31,7 +31,7 @@ import java.util.List;
 
 @SuppressWarnings("unused")
 @JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
 @JsonPropertyOrder({
         "provider",
         "group",
@@ -45,7 +45,9 @@ import java.util.List;
         "licenses",
         "externalReferences",
         "properties",
-        "services"
+        "services",
+        "releaseNotes",
+        "signature"
 })
 public class Service extends ExtensibleElement {
 
@@ -65,9 +67,13 @@ public class Service extends ExtensibleElement {
     private List<ServiceData> data;
     private LicenseChoice license;
     private List<ExternalReference> externalReferences;
-    @VersionFilter(versions = {"1.3"})
+    @VersionFilter(versions = {"1.3", "1.4"})
     private List<Property> properties;
     private List<Service> services;
+    private ReleaseNotes releaseNotes;
+    @JsonOnly
+    @VersionFilter(versions = {"1.4"})
+    private Signature signature;
 
     public String getBomRef() {
         return bomRef;
@@ -221,4 +227,12 @@ public class Service extends ExtensibleElement {
     public void setServices(List<Service> services) {
         this.services = services;
     }
+
+    public ReleaseNotes getReleaseNotes() { return releaseNotes; }
+
+    public void setReleaseNotes(ReleaseNotes releaseNotes) { this.releaseNotes = releaseNotes; }
+
+    public Signature getSignature() { return signature; }
+
+    public void setSignature(Signature signature) { this.signature = signature; }
 }
diff --git a/src/main/java/org/cyclonedx/model/Signature.java b/src/main/java/org/cyclonedx/model/Signature.java
new file mode 100644
index 000000000..c91a77825
--- /dev/null
+++ b/src/main/java/org/cyclonedx/model/Signature.java
@@ -0,0 +1,275 @@
+/*
+ * This file is part of CycloneDX Core (Java).
+ *
+ * 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
+ *
+ *     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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) OWASP Foundation. All Rights Reserved.
+ */
+package org.cyclonedx.model;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonAlias;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+
+/**
+ * @since 6.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonPropertyOrder({
+    "signers",
+    "chain",
+    "algorithm",
+    "keyId",
+    "publicKey",
+    "certificatePath",
+    "excludes",
+    "value"
+})
+public class Signature
+{
+  public Signature() {}
+
+  public enum Algorithm {
+    @JsonProperty("RS256")
+    RS256("RS256"),
+    @JsonProperty("RS384")
+    RS384("RS384"),
+    @JsonProperty("RS512")
+    RS512("RS512"),
+    @JsonProperty("PS256")
+    PS256("PS256"),
+    @JsonProperty("PS384")
+    PS384("PS384"),
+    @JsonProperty("PS512")
+    PS512("PS512"),
+    @JsonProperty("ES256")
+    ES256("ES256"),
+    @JsonProperty("ES384")
+    ES384("ES384"),
+    @JsonProperty("ES512")
+    ES512("ES512"),
+    @JsonProperty("Ed25519")
+    ED25519("Ed25519"),
+    @JsonProperty("Ed448")
+    ED448("Ed448"),
+    @JsonProperty("HS256")
+    HS256("HS256"),
+    @JsonProperty("HS384")
+    HS384("HS384"),
+    @JsonProperty("HS512")
+    HS512("HS512");
+
+    private final String name;
+
+    public String getAlgorithmName() {
+      return this.name;
+    }
+
+    Algorithm(String name) {
+      this.name = name;
+    }
+
+    public static Signature.Algorithm fromString(String text) {
+      for (Signature.Algorithm a : Signature.Algorithm.values()) {
+        if (a.name.equals(text)) {
+          return a;
+        }
+      }
+      return null;
+    }
+  }
+
+  @JsonAlias("chain")
+  private List<Signature> signers;
+  private Algorithm algorithm;
+  private String keyId;
+  private PublicKey publicKey;
+  private List<String> certificatePath;
+  private List<String> excludes;
+  private String value;
+
+  public List<Signature> getSigners() {
+    return signers;
+  }
+
+  public void setSigners(final List<Signature> signers) {
+    this.signers = signers;
+  }
+
+  public Algorithm getAlgorithm() {
+    return algorithm;
+  }
+
+  public void setAlgorithm(final Algorithm algorithm) {
+    this.algorithm = algorithm;
+  }
+
+  public String getKeyId() {
+    return keyId;
+  }
+
+  public void setKeyId(final String keyId) {
+    this.keyId = keyId;
+  }
+
+  public PublicKey getPublicKey() {
+    return publicKey;
+  }
+
+  public void setPublicKey(final PublicKey publicKey) {
+    this.publicKey = publicKey;
+  }
+
+  public List<String> getCertificatePath() {
+    return certificatePath;
+  }
+
+  public void setCertificatePath(final List<String> certificatePath) {
+    this.certificatePath = certificatePath;
+  }
+
+  public List<String> getExcludes() {
+    return excludes;
+  }
+
+  public void setExcludes(final List<String> excludes) {
+    this.excludes = excludes;
+  }
+
+  public String getValue() {
+    return value;
+  }
+
+  public void setValue(final String value) {
+    this.value = value;
+  }
+
+  public static class PublicKey {
+
+    public enum Kty {
+      @JsonProperty("EC")
+      EC("EC"),
+      @JsonProperty("OKP")
+      OKP("OKP"),
+      @JsonProperty("RSA")
+      RSA("RSA");
+
+      private final String name;
+
+      public String getKtyName() {
+        return this.name;
+      }
+
+      Kty(String name) {
+        this.name = name;
+      }
+
+      public static PublicKey.Kty fromString(String text) {
+        for (PublicKey.Kty k : PublicKey.Kty.values()) {
+          if (k.name.equals(text)) {
+            return k;
+          }
+        }
+        return null;
+      }
+    }
+
+    private Kty kty;
+
+    public enum Crv {
+      @JsonProperty("P-256")
+      P_256("P-256"),
+      @JsonProperty("P-384")
+      P_384("P-384"),
+      @JsonProperty("P-521")
+      P_521("P-521");
+
+      private final String name;
+
+      public String getCrvName() {
+        return this.name;
+      }
+
+      Crv(String name) {
+        this.name = name;
+      }
+
+      public static PublicKey.Crv fromString(String text) {
+        for (PublicKey.Crv c : PublicKey.Crv.values()) {
+          if (c.name.equals(text)) {
+            return c;
+          }
+        }
+        return null;
+      }
+    }
+
+    private Crv crv;
+    private String x;
+    private String y;
+    private String e;
+    private String n;
+
+    public Kty getKty() {
+      return kty;
+    }
+
+    public void setKty(final Kty kty) {
+      this.kty = kty;
+    }
+
+    public Crv getCrv() {
+      return crv;
+    }
+
+    public void setCrv(final Crv crv) {
+      this.crv = crv;
+    }
+
+    public String getX() {
+      return x;
+    }
+
+    public void setX(final String x) {
+      this.x = x;
+    }
+
+    public String getY() {
+      return y;
+    }
+
+    public void setY(final String y) {
+      this.y = y;
+    }
+
+    public String getE() {
+      return e;
+    }
+
+    public void setE(final String e) {
+      this.e = e;
+    }
+
+    public String getN() {
+      return n;
+    }
+
+    public void setN(final String n) {
+      this.n = n;
+    }
+  }
+}
diff --git a/src/main/java/org/cyclonedx/model/Source.java b/src/main/java/org/cyclonedx/model/Source.java
index 534945e8e..3f39f9cbd 100644
--- a/src/main/java/org/cyclonedx/model/Source.java
+++ b/src/main/java/org/cyclonedx/model/Source.java
@@ -21,12 +21,16 @@ package org.cyclonedx.model;
 import java.net.URL;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
 @JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonPropertyOrder({
+    "name",
+    "url"})
 public class Source {
-  @JacksonXmlProperty(localName = "name", isAttribute = true)
+  @JacksonXmlProperty(localName = "name")
   private String name;
   private URL url;
 
diff --git a/src/main/java/org/cyclonedx/model/Tool.java b/src/main/java/org/cyclonedx/model/Tool.java
index 07fbbcfc1..b8ae43548 100644
--- a/src/main/java/org/cyclonedx/model/Tool.java
+++ b/src/main/java/org/cyclonedx/model/Tool.java
@@ -27,14 +27,15 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(JsonInclude.Include.NON_NULL)
-@JsonPropertyOrder({"vendor", "name", "version", "hashes"})
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+@JsonPropertyOrder({"vendor", "name", "version", "hashes", "externalReferences"})
 public class Tool extends ExtensibleElement {
 
     private String vendor;
     private String name;
     private String version;
     private List<Hash> hashes;
+    private List<ExternalReference> externalReferences;
 
     public String getVendor() {
         return vendor;
@@ -70,6 +71,16 @@ public class Tool extends ExtensibleElement {
         this.hashes = hashes;
     }
 
+    @JacksonXmlElementWrapper(localName = "externalReferences")
+    @JacksonXmlProperty(localName = "reference")
+    public List<ExternalReference> getExternalReferences() {
+        return externalReferences;
+    }
+
+    public void setExternalReferences(final List<ExternalReference> externalReferences) {
+        this.externalReferences = externalReferences;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
@@ -78,11 +89,12 @@ public class Tool extends ExtensibleElement {
         return Objects.equals(vendor, tool.vendor) &&
                 Objects.equals(name, tool.name) &&
                 Objects.equals(version, tool.version) &&
-                Objects.equals(hashes, tool.hashes);
+                Objects.equals(hashes, tool.hashes) &&
+                Objects.equals(externalReferences, tool.externalReferences);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(vendor, name, version, hashes);
+        return Objects.hash(vendor, name, version, hashes, externalReferences);
     }
 }
diff --git a/src/main/java/org/cyclonedx/model/Variants.java b/src/main/java/org/cyclonedx/model/Variants.java
index 6076ae593..a54ac9cb9 100644
--- a/src/main/java/org/cyclonedx/model/Variants.java
+++ b/src/main/java/org/cyclonedx/model/Variants.java
@@ -22,6 +22,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
 public class Variants extends ComponentWrapper {
 }
diff --git a/src/main/java/org/cyclonedx/model/vulnerability/Vulnerability.java b/src/main/java/org/cyclonedx/model/vulnerability/Vulnerability.java
new file mode 100644
index 000000000..548870cbb
--- /dev/null
+++ b/src/main/java/org/cyclonedx/model/vulnerability/Vulnerability.java
@@ -0,0 +1,727 @@
+/*
+ * This file is part of CycloneDX Core (Java).
+ *
+ * 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
+ *
+ *     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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) OWASP Foundation. All Rights Reserved.
+ */
+package org.cyclonedx.model.vulnerability;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import org.cyclonedx.model.OrganizationalContact;
+import org.cyclonedx.model.OrganizationalEntity;
+import org.cyclonedx.model.Property;
+import org.cyclonedx.model.Tool;
+import org.cyclonedx.model.VersionFilter;
+import org.cyclonedx.util.CustomDateSerializer;
+
+/**
+ * @since 6.0.0
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonPropertyOrder({
+    "bomRef",
+    "id",
+    "source",
+    "references",
+    "ratings",
+    "cwes",
+    "description",
+    "detail",
+    "recommendation",
+    "advisories",
+    "created",
+    "published",
+    "updated",
+    "credits",
+    "tools",
+    "analysis",
+    "affects",
+    "properties"
+})
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+public class Vulnerability
+{
+  public Vulnerability() {}
+
+  @JacksonXmlProperty(isAttribute = true, localName = "bom-ref")
+  @JsonProperty("bom-ref")
+  private String bomRef;
+  private String id;
+  private Source source;
+  private List<Reference> references;
+  private List<Rating> ratings;
+  private List<Integer> cwes;
+  private String description;
+  private String detail;
+  private String recommendation;
+  private List<Advisory> advisories;
+  @JsonSerialize(using = CustomDateSerializer.class)
+  @VersionFilter(versions = { "1.4"})
+  private Date created;
+  @JsonSerialize(using = CustomDateSerializer.class)
+  @VersionFilter(versions = {"1.4"})
+  private Date published;
+  @JsonSerialize(using = CustomDateSerializer.class)
+  @VersionFilter(versions = {"1.4"})
+  private Date updated;
+  private Credits credits;
+  @JacksonXmlElementWrapper(localName = "tools")
+  @JacksonXmlProperty(localName = "tool")
+  private List<Tool> tools;
+  private Analysis analysis;
+  private List<Affect> affects;
+  private List<Property> properties;
+
+  public String getBomRef() {
+    return bomRef;
+  }
+
+  public void setBomRef(final String bomRef) {
+    this.bomRef = bomRef;
+  }
+
+  public String getId() {
+    return id;
+  }
+
+  public void setId(final String id) {
+    this.id = id;
+  }
+
+  public Source getSource() {
+    return source;
+  }
+
+  public void setSource(final Source source) {
+    this.source = source;
+  }
+
+  @JacksonXmlElementWrapper(localName = "references")
+  @JacksonXmlProperty(localName = "reference")
+  public List<Reference> getReferences() {
+    return references;
+  }
+
+  public void setReferences(final List<Reference> references) {
+    this.references = references;
+  }
+
+  @JacksonXmlElementWrapper(localName = "ratings")
+  @JacksonXmlProperty(localName = "rating")
+  public List<Rating> getRatings() {
+    return ratings;
+  }
+
+  public void setRatings(final List<Rating> ratings) {
+    this.ratings = ratings;
+  }
+
+  public void addRating(final Rating rating) {
+    if (this.ratings == null) {
+      this.ratings = new ArrayList<>();
+    }
+    this.ratings.add(rating);
+  }
+
+  @JacksonXmlElementWrapper(localName = "cwes")
+  @JacksonXmlProperty(localName = "cwe")
+  public List<Integer> getCwes() {
+    return cwes;
+  }
+
+  public void setCwes(final List<Integer> cwes) {
+    this.cwes = cwes;
+  }
+
+  public void addCwe(final int cweId) {
+    if (this.cwes == null) {
+      this.cwes = new ArrayList<>();
+    }
+    this.cwes.add(cweId);
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(final String description) {
+    this.description = description;
+  }
+
+  public String getDetail() {
+    return detail;
+  }
+
+  public void setDetail(final String detail) {
+    this.detail = detail;
+  }
+
+  public String getRecommendation() {
+    return recommendation;
+  }
+
+  public void setRecommendation(final String recommendation) {
+    this.recommendation = recommendation;
+  }
+
+  @JacksonXmlElementWrapper(localName = "advisories")
+  @JacksonXmlProperty(localName = "advisory")
+  public List<Advisory> getAdvisories() {
+    return advisories;
+  }
+
+  public void setAdvisories(final List<Advisory> advisories) {
+    this.advisories = advisories;
+  }
+
+  public Date getCreated() {
+    return created;
+  }
+
+  public void setCreated(final Date created) {
+    this.created = created;
+  }
+
+  public Date getPublished() {
+    return published;
+  }
+
+  public void setPublished(final Date published) {
+    this.published = published;
+  }
+
+  public Date getUpdated() {
+    return updated;
+  }
+
+  public void setUpdated(final Date updated) {
+    this.updated = updated;
+  }
+
+  public Credits getCredits() {
+    return credits;
+  }
+
+  public void setCredits(final Credits credits) {
+    this.credits = credits;
+  }
+
+  public List<Tool> getTools() {
+    return tools;
+  }
+
+  public void setTools(final List<Tool> tools) {
+    this.tools = tools;
+  }
+
+  public Analysis getAnalysis() {
+    return analysis;
+  }
+
+  public void setAnalysis(final Analysis analysis) {
+    this.analysis = analysis;
+  }
+
+  @JacksonXmlElementWrapper(localName = "affects")
+  @JacksonXmlProperty(localName = "target")
+  public List<Affect> getAffects() {
+    return affects;
+  }
+
+  public void setAffects(final List<Affect> affects) {
+    this.affects = affects;
+  }
+
+  public List<Property> getProperties() {
+    return properties;
+  }
+
+  public void setProperties(final List<Property> properties) {
+    this.properties = properties;
+  }
+
+  @JsonInclude(JsonInclude.Include.NON_NULL)
+  public static class Reference {
+    private String id;
+    private Source source;
+
+    public String getId() {
+      return id;
+    }
+
+    public void setId(final String id) {
+      this.id = id;
+    }
+
+    public Source getSource() {
+      return source;
+    }
+
+    public void setSource(final Source source) {
+      this.source = source;
+    }
+  }
+
+  @JsonInclude(JsonInclude.Include.NON_NULL)
+  public static class Source {
+    private String name;
+    private String url;
+
+    public String getName() {
+      return name;
+    }
+
+    public void setName(final String name) {
+      this.name = name;
+    }
+
+    public String getUrl() {
+      return url;
+    }
+
+    public void setUrl(final String url) {
+      this.url = url;
+    }
+  }
+
+  @JsonInclude(JsonInclude.Include.NON_NULL)
+  public static class Advisory {
+    private String title;
+    private String url;
+
+    public String getTitle() {
+      return title;
+    }
+
+    public void setTitle(final String title) {
+      this.title = title;
+    }
+
+    public String getUrl() {
+      return url;
+    }
+
+    public void setUrl(final String url) {
+      this.url = url;
+    }
+  }
+
+  @JsonInclude(JsonInclude.Include.NON_NULL)
+  public static class Rating {
+    private Source source;
+    private Double score;
+
+    public enum Severity {
+      @JsonProperty("critical")
+      CRITICAL("critical"),
+      @JsonProperty("high")
+      HIGH("high"),
+      @JsonProperty("medium")
+      MEDIUM("medium"),
+      @JsonProperty("low")
+      LOW("low"),
+      @JsonProperty("info")
+      INFO("info"),
+      @JsonProperty("none")
+      NONE("none"),
+      @JsonProperty("unknown")
+      UNKNOWN("unknown");
+
+      private final String name;
+
+      public String getSeverityName() {
+        return this.name;
+      }
+
+      Severity(String name) {
+        this.name = name;
+      }
+
+      public static Rating.Severity fromString(String text) {
+        for (Rating.Severity s : Rating.Severity.values()) {
+          if (s.name.equals(text)) {
+            return s;
+          }
+        }
+        return null;
+      }
+    }
+
+    private Severity severity;
+
+    public enum Method {
+      @JsonProperty("CVSSv2")
+      CVSSV2("CVSSv2"),
+      @JsonProperty("CVSSv3")
+      CVSSV3("CVSSv3"),
+      @JsonProperty("CVSSv31")
+      CVSSV31("CVSSv31"),
+      @JsonProperty("OWASP")
+      OWASP("OWASP"),
+      @JsonProperty("other")
+      OTHER("other");
+
+      private final String name;
+
+      public String getMethodName() {
+        return this.name;
+      }
+
+      Method(String name) {
+        this.name = name;
+      }
+
+      public static Rating.Method fromString(String text) {
+        for (Rating.Method m : Rating.Method.values()) {
+          if (m.name.equals(text)) {
+            return m;
+          }
+        }
+        return null;
+      }
+    }
+
+    private Method method;
+    private String vector;
+    private String justification;
+
+    public Source getSource() {
+      return source;
+    }
+
+    public void setSource(final Source source) {
+      this.source = source;
+    }
+
+    public Double getScore() {
+      return score;
+    }
+
+    public void setScore(final Double score) {
+      this.score = score;
+    }
+
+    public Severity getSeverity() {
+      return severity;
+    }
+
+    public void setSeverity(final Severity severity) {
+      this.severity = severity;
+    }
+
+    public Method getMethod() {
+      return method;
+    }
+
+    public void setMethod(final Method method) {
+      this.method = method;
+    }
+
+    public String getVector() {
+      return vector;
+    }
+
+    public void setVector(final String vector) {
+      this.vector = vector;
+    }
+
+    public String getJustification() {
+      return justification;
+    }
+
+    public void setJustification(final String justification) {
+      this.justification = justification;
+    }
+  }
+
+  @JsonInclude(JsonInclude.Include.NON_NULL)
+  @JsonPropertyOrder({
+      "state",
+      "justification",
+      "responses",
+      "detail"})
+  public static class Analysis {
+
+    public enum State {
+      @JsonProperty("resolved")
+      RESOLVED("resolved"),
+      @JsonProperty("resolved_with_pedigree")
+      RESOLVED_WITH_PEDIGREE("resolved_with_pedigree"),
+      @JsonProperty("exploitable")
+      EXPLOITABLE("exploitable"),
+      @JsonProperty("in_triage")
+      IN_TRIAGE("in_triage"),
+      @JsonProperty("false_positive")
+      FALSE_POSITIVE("false_positive"),
+      @JsonProperty("not_affected")
+      NOT_AFFECTED("not_affected");
+
+      private final String name;
+
+      public String getStateName() {
+        return this.name;
+      }
+
+      State(String name) {
+        this.name = name;
+      }
+
+      public static Analysis.State fromString(String text) {
+        for (Analysis.State a : Analysis.State.values()) {
+          if (a.name.equals(text)) {
+            return a;
+          }
+        }
+        return null;
+      }
+    }
+
+    private State state;
+
+    public enum Justification {
+      @JsonProperty("code_not_present")
+      CODE_NOT_PRESENT("code_not_present"),
+      @JsonProperty("code_not_reachable")
+      CODE_NOT_REACHABLE("code_not_reachable"),
+      @JsonProperty("requires_configuration")
+      REQUIRES_CONFIGURATION("requires_configuration"),
+      @JsonProperty("requires_dependency")
+      REQUIRES_DEPENDENCY("requires_dependency"),
+      @JsonProperty("requires_environment")
+      REQUIRES_ENVIRONMENT("requires_environment"),
+      @JsonProperty("protected_by_compiler")
+      PROTECTED_BY_COMPILER("protected_by_compiler"),
+      @JsonProperty("protected_at_runtime")
+      PROTECTED_AT_RUNTIME("protected_at_runtime"),
+      @JsonProperty("protected_at_perimeter")
+      PROTECTED_AT_PERIMETER("protected_at_perimeter"),
+      @JsonProperty("protected_by_mitigating_control")
+      PROTECTED_BY_MITIGATING_CONTROL("protected_by_mitigating_control");
+
+      private final String name;
+
+      public String getJustificationName() {
+        return this.name;
+      }
+
+      Justification(String name) {
+        this.name = name;
+      }
+
+      public static Analysis.Justification fromString(String text) {
+        for (Analysis.Justification j : Analysis.Justification.values()) {
+          if (j.name.equals(text)) {
+            return j;
+          }
+        }
+        return null;
+      }
+    }
+
+    private Justification justification;
+
+    public enum Response {
+      @JsonProperty("can_not_fix")
+      CAN_NOT_FIX("can_not_fix"),
+      @JsonProperty("will_not_fix")
+      WILL_NOT_FIX("will_not_fix"),
+      @JsonProperty("update")
+      UPDATE("update"),
+      @JsonProperty("rollback")
+      ROLLBACK("rollback"),
+      @JsonProperty("workaround_available")
+      WORKAROUND_AVAILABLE("workaround_available");
+
+      private final String name;
+
+      public String getResponseName() {
+        return this.name;
+      }
+
+      Response(String name) {
+        this.name = name;
+      }
+
+      public static Analysis.Response fromString(String text) {
+        for (Analysis.Response r : Analysis.Response.values()) {
+          if (r.name.equals(text)) {
+            return r;
+          }
+        }
+        return null;
+      }
+    }
+
+    private List<Response> responses;
+    private String detail;
+
+    public State getState() {
+      return state;
+    }
+
+    public void setState(final State state) {
+      this.state = state;
+    }
+
+    public Justification getJustification() {
+      return justification;
+    }
+
+    public void setJustification(final Justification justification) {
+      this.justification = justification;
+    }
+
+    @JacksonXmlElementWrapper(localName = "responses")
+    @JsonProperty("response")
+    @JacksonXmlProperty(localName = "response")
+    public List<Response> getResponses() {
+      return responses;
+    }
+
+    public void setResponses(final List<Response> responses) {
+      this.responses = responses;
+    }
+
+    public String getDetail() {
+      return detail;
+    }
+
+    public void setDetail(final String detail) {
+      this.detail = detail;
+    }
+  }
+
+  @JsonInclude(JsonInclude.Include.NON_EMPTY)
+  public static class Affect {
+    private String ref;
+    @JacksonXmlElementWrapper(localName = "versions")
+    @JacksonXmlProperty(localName = "version")
+    private List<Version> versions;
+
+    public String getRef() {
+      return ref;
+    }
+
+    public void setRef(final String ref) {
+      this.ref = ref;
+    }
+
+    public List<Version> getVersions() {
+      return versions;
+    }
+
+    public void setVersions(final List<Version> versions) {
+      this.versions = versions;
+    }
+  }
+
+  @JsonInclude(JsonInclude.Include.NON_NULL)
+  public static class Version {
+    private String version;
+    private String range;
+
+    public enum Status {
+      @JsonProperty("affected")
+      AFFECTED("affected"),
+      @JsonProperty("unaffected")
+      UNAFFECTED("unaffected"),
+      @JsonProperty("unknown")
+      UNKNOWN("unknown");
+
+      private final String name;
+
+      public String getStatusName() {
+        return this.name;
+      }
+
+      Status(String name) {
+        this.name = name;
+      }
+
+      public static Version.Status fromString(String text) {
+        for (Version.Status s : Version.Status.values()) {
+          if (s.name.equals(text)) {
+            return s;
+
+          }
+        }
+        return null;
+      }
+    }
+
+    private Status status;
+
+    public String getVersion() {
+      return version;
+    }
+
+    public void setVersion(final String version) {
+      this.version = version;
+    }
+
+    public String getRange() {
+      return range;
+    }
+
+    public void setRange(final String range) {
+      this.range = range;
+    }
+
+    public Status getStatus() {
+      return status;
+    }
+
+    public void setStatus(final Status status) {
+      this.status = status;
+    }
+  }
+
+  @JsonInclude(JsonInclude.Include.NON_EMPTY)
+  public static class Credits
+  {
+    @JacksonXmlElementWrapper(localName = "organizations")
+    @JacksonXmlProperty(localName = "organization")
+    private List<OrganizationalEntity> organizations;
+    @JacksonXmlElementWrapper(localName = "individuals")
+    @JacksonXmlProperty(localName = "individual")
+    private List<OrganizationalContact> individuals;
+
+    public List<OrganizationalContact> getIndividuals() {
+      return individuals;
+    }
+
+    public void setIndividuals(final List<OrganizationalContact> individuals) {
+      this.individuals = individuals;
+    }
+
+    public List<OrganizationalEntity> getOrganizations() {
+      return organizations;
+    }
+
+    public void setOrganizations(final List<OrganizationalEntity> organizations) {
+      this.organizations = organizations;
+    }
+  }
+}
diff --git a/src/main/java/org/cyclonedx/model/vulnerability/Vulnerability10.java b/src/main/java/org/cyclonedx/model/vulnerability/Vulnerability10.java
index 1cb5c70ca..233fc5fe2 100644
--- a/src/main/java/org/cyclonedx/model/vulnerability/Vulnerability10.java
+++ b/src/main/java/org/cyclonedx/model/vulnerability/Vulnerability10.java
@@ -18,9 +18,12 @@
  */
 package org.cyclonedx.model.vulnerability;
 
+import java.net.URL;
 import java.util.List;
 import java.io.Serializable;
 
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonRootName;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
@@ -198,6 +201,30 @@ public class Vulnerability10
     this.id = id;
   }
 
+  @JsonIgnoreProperties(ignoreUnknown = true)
+  @JsonInclude(JsonInclude.Include.NON_NULL)
+  public static class Source {
+    @JacksonXmlProperty(localName = "name", isAttribute = true)
+    private String name;
+    private java.net.URL url;
+
+    public URL getUrl() {
+      return url;
+    }
+
+    public void setUrl(final URL url) {
+      this.url = url;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    public void setName(final String name) {
+      this.name = name;
+    }
+  }
+
   public static class Score {
     private Double base;
     private Double impact;
diff --git a/src/main/java/org/cyclonedx/util/ExtensionDeserializer.java b/src/main/java/org/cyclonedx/util/ExtensionDeserializer.java
index 411cf6722..22bb15f36 100644
--- a/src/main/java/org/cyclonedx/util/ExtensionDeserializer.java
+++ b/src/main/java/org/cyclonedx/util/ExtensionDeserializer.java
@@ -190,8 +190,8 @@ public class ExtensionDeserializer extends StdDeserializer<Extension>
     return c;
   }
 
-  private Source processSource(final JsonNode sourceNode) {
-    Source source = new Source();
+  private Vulnerability10.Source processSource(final JsonNode sourceNode) {
+    Vulnerability10.Source source = new Vulnerability10.Source();
     source.setName(getAsString("name", sourceNode));
     if (sourceNode.get(Vulnerability10.URL) != null) {
       try {
diff --git a/src/main/java/org/cyclonedx/util/VulnerabilityDeserializer.java b/src/main/java/org/cyclonedx/util/VulnerabilityDeserializer.java
new file mode 100644
index 000000000..ffa6f7615
--- /dev/null
+++ b/src/main/java/org/cyclonedx/util/VulnerabilityDeserializer.java
@@ -0,0 +1,53 @@
+/*
+ * This file is part of CycloneDX Core (Java).
+ *
+ * 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
+ *
+ *     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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) OWASP Foundation. All Rights Reserved.
+ */
+package org.cyclonedx.util;
+
+import java.util.ArrayList;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import org.cyclonedx.model.vulnerability.Vulnerability;
+
+
+public class VulnerabilityDeserializer
+    extends StdDeserializer<List<Vulnerability>>
+{
+  public VulnerabilityDeserializer() {
+    this(null);
+  }
+
+  public VulnerabilityDeserializer(final Class<?> vc) {
+    super(vc);
+  }
+
+  @Override
+  public List<Vulnerability> deserialize(final JsonParser parser, final DeserializationContext context)
+  {
+    try {
+      return parser.readValueAs(new TypeReference<List<Vulnerability>>(){});
+    }
+    catch (Exception e) {
+      return null;
+    }
+  }
+}
diff --git a/src/main/java/org/cyclonedx/util/mixin/MixInBomReference.java b/src/main/java/org/cyclonedx/util/mixin/MixInBomReference.java
new file mode 100644
index 000000000..51dcd4c20
--- /dev/null
+++ b/src/main/java/org/cyclonedx/util/mixin/MixInBomReference.java
@@ -0,0 +1,17 @@
+package org.cyclonedx.util.mixin;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+public class MixInBomReference
+{
+  @JsonValue
+  private String ref;
+
+  public String getRef() {
+    return ref;
+  }
+
+  public void setRef(final String ref) {
+    this.ref = ref;
+  }
+}


Test patch:
diff --git a/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java b/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java
index 3dd67289b..176605ae8 100644
--- a/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java
+++ b/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java
@@ -23,6 +23,8 @@ import org.cyclonedx.CycloneDxSchema.Version;
 import org.cyclonedx.generators.json.BomJsonGenerator;
 import org.cyclonedx.generators.json.BomJsonGenerator12;
 import org.cyclonedx.generators.json.BomJsonGenerator13;
+import org.cyclonedx.generators.json.BomJsonGenerator14;
+import org.cyclonedx.generators.xml.BomXmlGenerator14;
 import org.cyclonedx.model.Bom;
 import org.cyclonedx.parsers.JsonParser;
 import org.cyclonedx.parsers.XmlParser;
@@ -95,6 +97,32 @@ public class BomJsonGeneratorTest {
         assertTrue(parser.isValid(file, CycloneDxSchema.Version.VERSION_13));
     }
 
+    @Test
+    public void schema13JsonObjectGenerationTest() throws Exception {
+        Bom bom = createCommonBom("/bom-1.3.xml");
+        BomJsonGenerator generator = BomGeneratorFactory.createJson(Version.VERSION_13, bom);
+
+        assertTrue(generator instanceof BomJsonGenerator13);
+        assertEquals(CycloneDxSchema.Version.VERSION_13, generator.getSchemaVersion());
+
+        File file = writeToFile(generator.toJsonString());
+        JsonParser parser = new JsonParser();
+        assertTrue(parser.isValid(file, CycloneDxSchema.Version.VERSION_13));
+    }
+
+    @Test
+    public void schema14JsonObjectGenerationTest() throws Exception {
+        Bom bom = createCommonBom("/bom-1.4.xml");
+        BomJsonGenerator generator = BomGeneratorFactory.createJson(Version.VERSION_14, bom);
+
+        assertTrue(generator instanceof BomJsonGenerator14);
+        assertEquals(CycloneDxSchema.Version.VERSION_14, generator.getSchemaVersion());
+
+        File file = writeToFile(generator.toJsonString());
+        JsonParser parser = new JsonParser();
+        assertTrue(parser.isValid(file, CycloneDxSchema.Version.VERSION_14));
+    }
+
     private File writeToFile(String jsonString) throws Exception {
         try (FileWriter writer = new FileWriter(tempFile.getAbsolutePath())) {
             writer.write(jsonString);
diff --git a/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java b/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java
index 7ba10e746..bb9ec8daf 100644
--- a/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java
+++ b/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java
@@ -157,6 +157,32 @@ public class BomXmlGeneratorTest {
         assertTrue(parser.isValid(file, CycloneDxSchema.Version.VERSION_12));
     }
 
+    @Test
+    public void schema13GenerationTest() throws Exception {
+        BomXmlGenerator generator = BomGeneratorFactory.createXml(CycloneDxSchema.Version.VERSION_13, createCommonBom("/bom-1.3.xml"));
+        Document doc = generator.generate();
+        testDocument(doc);
+
+        assertTrue(generator instanceof BomXmlGenerator13);
+        assertEquals(CycloneDxSchema.Version.VERSION_13, generator.getSchemaVersion());
+        File file = writeToFile(generator.toXmlString());
+        XmlParser parser = new XmlParser();
+        assertTrue(parser.isValid(file, CycloneDxSchema.Version.VERSION_13));
+    }
+
+    @Test
+    public void schema14GenerationTest() throws Exception {
+        BomXmlGenerator generator = BomGeneratorFactory.createXml(CycloneDxSchema.Version.VERSION_14, createCommonBom("/bom-1.4.xml"));
+        Document doc = generator.generate();
+        testDocument(doc);
+
+        assertTrue(generator instanceof BomXmlGenerator14);
+        assertEquals(CycloneDxSchema.Version.VERSION_14, generator.getSchemaVersion());
+        File file = writeToFile(generator.toXmlString());
+        XmlParser parser = new XmlParser();
+        assertTrue(parser.isValid(file, CycloneDxSchema.Version.VERSION_14));
+    }
+
     @Test
     public void invalidUrlTest() throws Exception {
         Component c = new Component();
diff --git a/src/test/java/org/cyclonedx/parse/BaseParseTest.java b/src/test/java/org/cyclonedx/parse/BaseParseTest.java
index a61132fa8..8052169a9 100644
--- a/src/test/java/org/cyclonedx/parse/BaseParseTest.java
+++ b/src/test/java/org/cyclonedx/parse/BaseParseTest.java
@@ -21,6 +21,7 @@ package org.cyclonedx.parse;
 import org.cyclonedx.BomGeneratorFactory;
 import org.cyclonedx.BomParserFactory;
 import org.cyclonedx.CycloneDxSchema;
+import org.cyclonedx.CycloneDxSchema.Version;
 import org.cyclonedx.exception.GeneratorException;
 import org.cyclonedx.exception.ParseException;
 import org.cyclonedx.generators.json.BomJsonGenerator;
@@ -45,6 +46,7 @@ public abstract class BaseParseTest {
         VERSIONS.add(CycloneDxSchema.Version.VERSION_11);
         VERSIONS.add(CycloneDxSchema.Version.VERSION_12);
         VERSIONS.add(CycloneDxSchema.Version.VERSION_13);
+        VERSIONS.add(CycloneDxSchema.Version.VERSION_14);
     }
 
     List<File> getAllResources() throws Exception {
diff --git a/src/test/java/org/cyclonedx/parsers/JsonParserTest.java b/src/test/java/org/cyclonedx/parsers/JsonParserTest.java
index 6c405d65b..064a3bb61 100644
--- a/src/test/java/org/cyclonedx/parsers/JsonParserTest.java
+++ b/src/test/java/org/cyclonedx/parsers/JsonParserTest.java
@@ -24,10 +24,20 @@ import org.cyclonedx.model.Bom;
 import org.cyclonedx.model.Component;
 import org.cyclonedx.model.Dependency;
 import org.cyclonedx.model.ExternalReference;
+import org.cyclonedx.model.Metadata;
 import org.cyclonedx.model.OrganizationalContact;
 import org.cyclonedx.model.OrganizationalEntity;
+import org.cyclonedx.model.ReleaseNotes;
+import org.cyclonedx.model.ReleaseNotes.Notes;
+import org.cyclonedx.model.ReleaseNotes.Resolves;
 import org.cyclonedx.model.Service;
 import org.cyclonedx.model.ServiceData;
+import org.cyclonedx.model.vulnerability.Vulnerability;
+import org.cyclonedx.model.vulnerability.Vulnerability.Analysis.Justification;
+import org.cyclonedx.model.vulnerability.Vulnerability.Analysis.State;
+import org.cyclonedx.model.vulnerability.Vulnerability.Rating.Method;
+import org.cyclonedx.model.vulnerability.Vulnerability.Rating.Severity;
+import org.cyclonedx.model.vulnerability.Vulnerability.Version.Status;
 import org.junit.jupiter.api.Test;
 import java.io.File;
 import java.util.List;
@@ -294,4 +304,255 @@ public class JsonParserTest {
         assertEquals("pkg:npm/acme/common@1.0.0", d11.getRef());
         assertNull(d11.getDependencies());
     }
+
+    @Test
+    public void testParsedObjects14Bom() throws Exception {
+        final byte[] bomBytes = IOUtils.toByteArray(this.getClass().getResourceAsStream("/bom-1.4.json"));
+        final JsonParser parser = new JsonParser();
+        final Bom bom = parser.parse(bomBytes);
+
+        assertEquals("1.4", bom.getSpecVersion());
+        assertEquals(1, bom.getVersion());
+
+        assertMetadata(bom.getMetadata());
+        assertComponent(bom);
+        assertServices(bom);
+        assertVulnerabilities(bom);
+
+        // Dependencies
+        assertEquals(1, bom.getDependencies().size());
+        Dependency d1 = bom.getDependencies().get(0);
+        assertNotNull(d1);
+        assertEquals("pkg:maven/com.acme/jackson-databind@2.9.4", d1.getRef());
+    }
+
+    private void assertVulnerabilities(final Bom bom) {
+        final List<Vulnerability> vulnerabilities = bom.getVulnerabilities();
+        assertEquals(1, vulnerabilities.size());
+        Vulnerability vuln = vulnerabilities.get(0);
+
+        assertEquals("6eee14da-8f42-4cc4-bb65-203235f02415", vuln.getBomRef());
+        assertEquals("SONATYPE-2021123", vuln.getId());
+        assertEquals("Description", vuln.getDescription());
+        assertEquals("Detail", vuln.getDetail());
+        assertEquals("Upgrade", vuln.getRecommendation());
+        assertEquals(184, vuln.getCwes().get(0));
+        assertNotNull(vuln.getCreated());
+        assertNotNull(vuln.getPublished());
+        assertNotNull(vuln.getUpdated());
+
+        //Source
+        assertEquals("Sonatype", vuln.getSource().getName());
+        assertEquals("https://www.vuln.com", vuln.getSource().getUrl());
+
+        //References
+        assertEquals(1, vuln.getReferences().size());
+        assertEquals("CVE-2018-7489", vuln.getReferences().get(0).getId());
+        assertEquals("NVD", vuln.getReferences().get(0).getSource().getName());
+        assertEquals("https://nvd.nist.gov/vuln/detail/CVE-2019-9997",
+            vuln.getReferences().get(0).getSource().getUrl());
+
+        //Ratings
+        assertEquals(1, vuln.getRatings().size());
+        assertEquals("NVD", vuln.getRatings().get(0).getSource().getName());
+        assertEquals(
+            "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H&version=3.0",
+            vuln.getRatings().get(0).getSource().getUrl());
+        assertEquals(9.8, vuln.getRatings().get(0).getScore());
+        assertEquals(Severity.CRITICAL, vuln.getRatings().get(0).getSeverity());
+        assertEquals(Method.CVSSV3, vuln.getRatings().get(0).getMethod());
+        assertEquals("AN/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", vuln.getRatings().get(0).getVector());
+        assertEquals("An optional reason for rating the vulnerability as it was",
+            vuln.getRatings().get(0).getJustification());
+
+        //Advisories
+        assertEquals(1, vuln.getAdvisories().size());
+        assertEquals("GitHub Commit", vuln.getAdvisories().get(0).getTitle());
+        assertEquals("https://github.com/FasterXML/jackson-databind/commit/6799f8f10cc78e9af6d443ed6982d00a13f2e7d2",
+            vuln.getAdvisories().get(0).getUrl());
+
+        //Credits
+        assertEquals(1, vuln.getCredits().getIndividuals().size());
+        assertEquals(1, vuln.getCredits().getOrganizations().size());
+
+        assertEquals("Jane Doe", vuln.getCredits().getIndividuals().get(0).getName());
+        assertEquals("jane.doe@example.com", vuln.getCredits().getIndividuals().get(0).getEmail());
+
+        assertEquals("Acme, Inc.", vuln.getCredits().getOrganizations().get(0).getName());
+        assertEquals("https://example.com", vuln.getCredits().getOrganizations().get(0).getUrls().get(0));
+
+        //Tools
+        assertEquals(1, vuln.getTools().size());
+        assertEquals("Sonatype CLI", vuln.getTools().get(0).getName());
+        assertEquals("Sonatype", vuln.getTools().get(0).getVendor());
+        assertEquals("1.131", vuln.getTools().get(0).getVersion());
+        assertEquals(1, vuln.getTools().get(0).getHashes().size());
+
+        //Analysis
+        assertEquals(State.NOT_AFFECTED, vuln.getAnalysis().getState());
+        assertEquals(Justification.CODE_NOT_REACHABLE, vuln.getAnalysis().getJustification());
+        assertEquals("An optional explanation of why the application is not affected by the vulnerable component.",
+            vuln.getAnalysis().getDetail());
+        assertEquals("update", vuln.getAnalysis().getResponses().get(0).getResponseName());
+
+        //Affects
+        assertEquals(1, vuln.getAffects().size());
+        assertEquals("pkg:maven/com.acme/jackson-databind@2.9.9", vuln.getAffects().get(0).getRef());
+
+        assertEquals("vers:semver/<2.6.7.5", vuln.getAffects().get(0).getVersions().get(0).getRange());
+        assertEquals(Status.AFFECTED, vuln.getAffects().get(0).getVersions().get(0).getStatus());
+
+        assertEquals("2.9.9", vuln.getAffects().get(0).getVersions().get(1).getVersion());
+        assertEquals(Status.AFFECTED, vuln.getAffects().get(0).getVersions().get(1).getStatus());
+    }
+
+    private void assertServices(final Bom bom) {
+        //Services
+        List<Service> services = bom.getServices();
+        assertEquals(1, services.size());
+        Service s = services.get(0);
+        OrganizationalEntity provider = s.getProvider();
+        assertNotNull(provider);
+        assertEquals("Partner Org", provider.getName());
+        List<String> urls = provider.getUrls();
+        assertEquals(1, urls.size());
+        assertEquals("https://partner.org", urls.get(0));
+
+        List<OrganizationalContact> contacts = provider.getContacts();
+        assertEquals(1, contacts.size());
+        OrganizationalContact contact = contacts.get(0);
+        assertEquals("Support", contact.getName());
+        assertEquals("support@partner", contact.getEmail());
+        assertEquals("800-555-1212", contact.getPhone());
+        assertEquals("org.partner", s.getGroup());
+        assertEquals("Stock ticker service", s.getName());
+        assertEquals("2020-Q2", s.getVersion());
+        assertEquals("Provides real-time stock information", s.getDescription());
+
+        List<String> endpoints = s.getEndpoints();
+        assertEquals(2, endpoints.size());
+        assertEquals("https://partner.org/api/v1/lookup", endpoints.get(0));
+        assertEquals("https://partner.org/api/v1/stock", endpoints.get(1));
+        assertNotNull(s.getAuthenticated());
+        assertNotNull(s.getxTrustBoundary());
+        assertTrue(s.getAuthenticated());
+        assertTrue(s.getxTrustBoundary());
+
+        List<ServiceData> data = s.getData();
+        assertEquals(4, data.size());
+        assertEquals("inbound", data.get(0).getFlow().getFlowName());
+        assertEquals("PII", data.get(0).getClassification());
+        assertEquals(ServiceData.Flow.OUTBOUND, data.get(1).getFlow());
+        assertEquals("PIFI", data.get(1).getClassification());
+        assertEquals(ServiceData.Flow.BI_DIRECTIONAL, data.get(2).getFlow());
+        assertEquals("pubic", data.get(2).getClassification());
+        assertNotNull(s.getLicense());
+        assertEquals(1, s.getLicense().getLicenses().size());
+        assertEquals("Partner license", s.getLicense().getLicenses().get(0).getName());
+        assertEquals(2, s.getExternalReferences().size());
+        assertEquals(ExternalReference.Type.WEBSITE, s.getExternalReferences().get(0).getType());
+        assertEquals("http://partner.org", s.getExternalReferences().get(0).getUrl());
+        assertEquals(ExternalReference.Type.DOCUMENTATION, s.getExternalReferences().get(1).getType());
+        assertEquals("http://api.partner.org/swagger", s.getExternalReferences().get(1).getUrl());
+    }
+
+    private void assertComponent(final Bom bom) {
+        final List<Component> components = bom.getComponents();
+        assertEquals(1, components.size());
+        Component component = components.get(0);
+        assertEquals("com.acme", component.getGroup());
+        assertEquals("jackson-databind", component.getName());
+        assertEquals("2.9.4", component.getVersion());
+        assertEquals(Component.Type.LIBRARY, component.getType());
+        assertEquals("pkg:maven/com.acme/jackson-databind@2.9.4", component.getPurl());
+        assertEquals(12, component.getHashes().size());
+        assertNotNull(component.getLicenseChoice().getExpression());
+        assertEquals("Copyright Example Inc. All rights reserved.", component.getCopyright());
+        assertEquals("Acme Application", component.getSwid().getName());
+        assertEquals("9.1.1", component.getSwid().getVersion());
+        assertEquals("swidgen-242eb18a-503e-ca37-393b-cf156ef09691_9.1.1", component.getSwid().getTagId());
+        assertEquals(1, component.getExternalReferences().size());
+
+        //Evidence
+        assertNotNull(component.getEvidence());
+        assertEquals("Copyright 2012 Google Inc. All Rights Reserved.",
+            component.getEvidence().getCopyright().get(0).getText());
+        assertEquals("Apache-2.0", component.getEvidence().getLicenseChoice().getLicenses().get(0).getId());
+        assertEquals("http://www.apache.org/licenses/LICENSE-2.0",
+            component.getEvidence().getLicenseChoice().getLicenses().get(0).getUrl());
+    }
+
+    private void assertMetadata(final Metadata metadata) {
+        assertNotNull(metadata);
+        assertNotNull(metadata.getTimestamp());
+
+        //Tool
+        assertEquals(1, metadata.getTools().size());
+        assertEquals("Awesome Vendor", metadata.getTools().get(0).getVendor());
+        assertEquals("Awesome Tool", metadata.getTools().get(0).getName());
+        assertEquals("9.1.2", metadata.getTools().get(0).getVersion());
+        assertEquals(1, metadata.getTools().get(0).getHashes().size());
+        assertEquals("SHA-1", metadata.getTools().get(0).getHashes().get(0).getAlgorithm());
+        assertEquals("25ed8e31b995bb927966616df2a42b979a2717f0",
+            metadata.getTools().get(0).getHashes().get(0).getValue());
+
+        //Author
+        assertEquals(1, metadata.getAuthors().size());
+        assertEquals("Samantha Wright", metadata.getAuthors().get(0).getName());
+        assertEquals("samantha.wright@example.com", metadata.getAuthors().get(0).getEmail());
+        assertEquals("800-555-1212", metadata.getAuthors().get(0).getPhone());
+
+        //Component
+        Component component = metadata.getComponent();
+        assertEquals("Acme Application", component.getName());
+        assertEquals("9.1.1", component.getVersion());
+        assertEquals(Component.Type.APPLICATION, component.getType());
+        assertNotNull(component.getSwid());
+        assertEquals("swidgen-242eb18a-503e-ca37-393b-cf156ef09691_9.1.1", component.getSwid().getTagId());
+        assertEquals("Acme Application", component.getSwid().getName());
+        assertEquals("9.1.1", component.getSwid().getVersion());
+        assertEquals(0, component.getSwid().getTagVersion());
+        assertFalse(component.getSwid().isPatch());
+
+        //Release Notes
+        ReleaseNotes releaseNotes = metadata.getComponent().getReleaseNotes();
+        assertEquals("major", releaseNotes.getType());
+        assertEquals("My new release", releaseNotes.getTitle());
+        assertNotNull(releaseNotes.getDescription());
+        assertNotNull(releaseNotes.getSocialImage());
+        assertNotNull(releaseNotes.getTimestamp());
+        assertEquals(1, releaseNotes.getAliases().size());
+        assertEquals(1, releaseNotes.getTags().size());
+
+        //Resolves
+        assertEquals(1, releaseNotes.getResolves().size());
+        Resolves resolves = releaseNotes.getResolves().get(0);
+        assertEquals(Resolves.Type.SECURITY, resolves.getType());
+        assertEquals("CVE-2019-9997", resolves.getId());
+        assertEquals("A security issue was fixed that did something bad", resolves.getDescription());
+        assertEquals("CVE-2019-9997", resolves.getName());
+
+        //Notes
+        assertEquals(1, releaseNotes.getNotes().size());
+        Notes note = releaseNotes.getNotes().get(0);
+        assertEquals("en-US", note.getLocale());
+        assertNotNull(note.getText());
+        assertEquals("PGgxPk15IG5ldyByZWxlYXNlPGgxPgo8cD5SZWxlYXNlIG5vdGVzIGhlcmU8L3A+", note.getText().getText());
+        assertEquals("text/html", note.getText().getContentType());
+        assertEquals("base64", note.getText().getEncoding());
+
+        //Manufacture
+        assertEquals("Acme, Inc.", metadata.getManufacture().getName());
+        assertEquals("https://example.com", metadata.getManufacture().getUrls().get(0));
+        assertEquals(1, metadata.getManufacture().getContacts().size());
+        assertEquals("Acme Professional Services", metadata.getManufacture().getContacts().get(0).getName());
+        assertEquals("professional.services@example.com", metadata.getManufacture().getContacts().get(0).getEmail());
+        assertEquals("Acme, Inc.", metadata.getSupplier().getName());
+
+        //Supplier
+        assertEquals("https://example.com", metadata.getSupplier().getUrls().get(0));
+        assertEquals(1, metadata.getSupplier().getContacts().size());
+        assertEquals("Acme Distribution", metadata.getSupplier().getContacts().get(0).getName());
+        assertEquals("distribution@example.com", metadata.getSupplier().getContacts().get(0).getEmail());
+    }
 }
diff --git a/src/test/java/org/cyclonedx/parsers/XmlParserTest.java b/src/test/java/org/cyclonedx/parsers/XmlParserTest.java
index 405aa75f3..c0658d157 100644
--- a/src/test/java/org/cyclonedx/parsers/XmlParserTest.java
+++ b/src/test/java/org/cyclonedx/parsers/XmlParserTest.java
@@ -24,11 +24,21 @@ import org.cyclonedx.model.Bom;
 import org.cyclonedx.model.Component;
 import org.cyclonedx.model.Dependency;
 import org.cyclonedx.model.ExternalReference;
+import org.cyclonedx.model.Metadata;
 import org.cyclonedx.model.OrganizationalContact;
 import org.cyclonedx.model.OrganizationalEntity;
 import org.cyclonedx.model.Pedigree;
+import org.cyclonedx.model.ReleaseNotes;
+import org.cyclonedx.model.ReleaseNotes.Notes;
+import org.cyclonedx.model.ReleaseNotes.Resolves;
 import org.cyclonedx.model.Service;
 import org.cyclonedx.model.ServiceData;
+import org.cyclonedx.model.vulnerability.Vulnerability;
+import org.cyclonedx.model.vulnerability.Vulnerability.Analysis.Justification;
+import org.cyclonedx.model.vulnerability.Vulnerability.Analysis.State;
+import org.cyclonedx.model.vulnerability.Vulnerability.Rating.Method;
+import org.cyclonedx.model.vulnerability.Vulnerability.Rating.Severity;
+import org.cyclonedx.model.vulnerability.Vulnerability.Version.Status;
 import org.junit.jupiter.api.Test;
 import java.io.File;
 import java.util.List;
@@ -427,6 +437,268 @@ public class XmlParserTest {
         assertNull(d11.getDependencies());
     }
 
+    @Test
+    public void testParsedObjects14Bom() throws Exception {
+        final byte[] bomBytes = IOUtils.toByteArray(this.getClass().getResourceAsStream("/bom-1.4.xml"));
+        final XmlParser parser = new XmlParser();
+        final Bom bom = parser.parse(bomBytes);
+
+        assertEquals("1.4", bom.getSpecVersion());
+        assertEquals(1, bom.getVersion());
+
+        assertMetadata(bom.getMetadata());
+        assertComponent(bom);
+        assertServices(bom);
+        assertVulnerabilities(bom);
+
+        // Dependencies
+        assertEquals(1, bom.getDependencies().size());
+        Dependency d1 = bom.getDependencies().get(0);
+        assertNotNull(d1);
+        assertEquals("pkg:maven/com.acme/jackson-databind@2.9.4", d1.getRef());
+    }
+
+    @Test
+    public void testParsedObjects14Bom_WithVulnsExtension() throws Exception {
+        final byte[] bomBytes = IOUtils.toByteArray(this.getClass().getResourceAsStream("/valid-ext-vulnerability-1.4.xml"));
+        final XmlParser parser = new XmlParser();
+        final Bom bom = parser.parse(bomBytes);
+
+        assertEquals("1.4", bom.getSpecVersion());
+        assertEquals(1, bom.getVersion());
+        assertNull(bom.getVulnerabilities());
+    }
+
+    private void assertVulnerabilities(final Bom bom) {
+        final List<Vulnerability> vulnerabilities = bom.getVulnerabilities();
+        assertEquals(1, vulnerabilities.size());
+        Vulnerability vuln = vulnerabilities.get(0);
+
+        assertEquals("6eee14da-8f42-4cc4-bb65-203235f02415", vuln.getBomRef());
+        assertEquals("SONATYPE-2021123", vuln.getId());
+        assertEquals("Description", vuln.getDescription());
+        assertEquals("Detail", vuln.getDetail());
+        assertEquals("Upgrade", vuln.getRecommendation());
+        assertEquals(184, vuln.getCwes().get(0));
+        assertNotNull(vuln.getCreated());
+        assertNotNull(vuln.getPublished());
+        assertNotNull(vuln.getUpdated());
+
+        //Source
+        assertEquals("Sonatype", vuln.getSource().getName());
+        assertEquals("https://www.vuln.com", vuln.getSource().getUrl());
+
+        //References
+        assertEquals(1, vuln.getReferences().size());
+        assertEquals("CVE-2018-7489", vuln.getReferences().get(0).getId());
+        assertEquals("NVD", vuln.getReferences().get(0).getSource().getName());
+        assertEquals("https://nvd.nist.gov/vuln/detail/CVE-2019-9997",
+            vuln.getReferences().get(0).getSource().getUrl());
+
+        //Ratings
+        assertEquals(1, vuln.getRatings().size());
+        assertEquals("NVD", vuln.getRatings().get(0).getSource().getName());
+        assertEquals(
+            "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H&version=3.0",
+            vuln.getRatings().get(0).getSource().getUrl());
+        assertEquals(9.8, vuln.getRatings().get(0).getScore());
+        assertEquals(Severity.CRITICAL, vuln.getRatings().get(0).getSeverity());
+        assertEquals(Method.CVSSV3, vuln.getRatings().get(0).getMethod());
+        assertEquals("AN/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", vuln.getRatings().get(0).getVector());
+        assertEquals("An optional reason for rating the vulnerability as it was",
+            vuln.getRatings().get(0).getJustification());
+
+        //Advisories
+        assertEquals(1, vuln.getAdvisories().size());
+        assertEquals("GitHub Commit", vuln.getAdvisories().get(0).getTitle());
+        assertEquals("https://github.com/FasterXML/jackson-databind/commit/6799f8f10cc78e9af6d443ed6982d00a13f2e7d2",
+            vuln.getAdvisories().get(0).getUrl());
+
+        //Credits
+        assertEquals(1, vuln.getCredits().getIndividuals().size());
+        assertEquals(1, vuln.getCredits().getOrganizations().size());
+
+        assertEquals("Jane Doe", vuln.getCredits().getIndividuals().get(0).getName());
+        assertEquals("jane.doe@example.com", vuln.getCredits().getIndividuals().get(0).getEmail());
+
+        assertEquals("Acme, Inc.", vuln.getCredits().getOrganizations().get(0).getName());
+        assertEquals("https://example.com", vuln.getCredits().getOrganizations().get(0).getUrls().get(0));
+
+        //Tools
+        assertEquals(1, vuln.getTools().size());
+        assertEquals("Sonatype CLI", vuln.getTools().get(0).getName());
+        assertEquals("Sonatype", vuln.getTools().get(0).getVendor());
+        assertEquals("1.131", vuln.getTools().get(0).getVersion());
+        assertEquals(1, vuln.getTools().get(0).getHashes().size());
+
+        //Analysis
+        assertEquals(State.NOT_AFFECTED, vuln.getAnalysis().getState());
+        assertEquals(Justification.CODE_NOT_REACHABLE, vuln.getAnalysis().getJustification());
+        assertEquals("An optional explanation of why the application is not affected by the vulnerable component.",
+            vuln.getAnalysis().getDetail());
+        assertEquals("update", vuln.getAnalysis().getResponses().get(0).getResponseName());
+
+        //Affects
+        assertEquals(1, vuln.getAffects().size());
+        assertEquals("pkg:maven/com.acme/jackson-databind@2.9.9", vuln.getAffects().get(0).getRef());
+
+        assertEquals("vers:semver/<2.6.7.5", vuln.getAffects().get(0).getVersions().get(0).getRange());
+        assertEquals(Status.AFFECTED, vuln.getAffects().get(0).getVersions().get(0).getStatus());
+
+        assertEquals("2.9.9", vuln.getAffects().get(0).getVersions().get(1).getVersion());
+        assertEquals(Status.AFFECTED, vuln.getAffects().get(0).getVersions().get(1).getStatus());
+    }
+
+    private void assertServices(final Bom bom) {
+        //Services
+        List<Service> services = bom.getServices();
+        assertEquals(1, services.size());
+        Service s = services.get(0);
+        OrganizationalEntity provider = s.getProvider();
+        assertNotNull(provider);
+        assertEquals("Partner Org", provider.getName());
+        List<String> urls = provider.getUrls();
+        assertEquals(1, urls.size());
+        assertEquals("https://partner.org", urls.get(0));
+
+        List<OrganizationalContact> contacts = provider.getContacts();
+        assertEquals(1, contacts.size());
+        OrganizationalContact contact = contacts.get(0);
+        assertEquals("Support", contact.getName());
+        assertEquals("support@partner", contact.getEmail());
+        assertEquals("800-555-1212", contact.getPhone());
+        assertEquals("org.partner", s.getGroup());
+        assertEquals("Stock ticker service", s.getName());
+        assertEquals("2020-Q2", s.getVersion());
+        assertEquals("Provides real-time stock information", s.getDescription());
+
+        List<String> endpoints = s.getEndpoints();
+        assertEquals(2, endpoints.size());
+        assertEquals("https://partner.org/api/v1/lookup", endpoints.get(0));
+        assertEquals("https://partner.org/api/v1/stock", endpoints.get(1));
+        assertNotNull(s.getAuthenticated());
+        assertNotNull(s.getxTrustBoundary());
+        assertTrue(s.getAuthenticated());
+        assertTrue(s.getxTrustBoundary());
+
+        List<ServiceData> data = s.getData();
+        assertEquals(4, data.size());
+        assertEquals("inbound", data.get(0).getFlow().getFlowName());
+        assertEquals("PII", data.get(0).getClassification());
+        assertEquals(ServiceData.Flow.OUTBOUND, data.get(1).getFlow());
+        assertEquals("PIFI", data.get(1).getClassification());
+        assertEquals(ServiceData.Flow.BI_DIRECTIONAL, data.get(2).getFlow());
+        assertEquals("pubic", data.get(2).getClassification());
+        assertNotNull(s.getLicense());
+        assertEquals(1, s.getLicense().getLicenses().size());
+        assertEquals("Partner license", s.getLicense().getLicenses().get(0).getName());
+        assertEquals(2, s.getExternalReferences().size());
+        assertEquals(ExternalReference.Type.WEBSITE, s.getExternalReferences().get(0).getType());
+        assertEquals("http://partner.org", s.getExternalReferences().get(0).getUrl());
+        assertEquals(ExternalReference.Type.DOCUMENTATION, s.getExternalReferences().get(1).getType());
+        assertEquals("http://api.partner.org/swagger", s.getExternalReferences().get(1).getUrl());
+    }
+
+    private void assertComponent(final Bom bom) {
+        final List<Component> components = bom.getComponents();
+        assertEquals(1, components.size());
+        Component component = components.get(0);
+        assertEquals("com.acme", component.getGroup());
+        assertEquals("jackson-databind", component.getName());
+        assertEquals("2.9.4", component.getVersion());
+        assertEquals(Component.Type.LIBRARY, component.getType());
+        assertEquals("pkg:maven/com.acme/jackson-databind@2.9.4", component.getPurl());
+        assertEquals(12, component.getHashes().size());
+        assertNotNull(component.getLicenseChoice().getExpression());
+        assertEquals("Copyright Example Inc. All rights reserved.", component.getCopyright());
+        assertEquals("Acme Application", component.getSwid().getName());
+        assertEquals("9.1.1", component.getSwid().getVersion());
+        assertEquals("swidgen-242eb18a-503e-ca37-393b-cf156ef09691_9.1.1", component.getSwid().getTagId());
+        assertEquals(1, component.getExternalReferences().size());
+
+        //Evidence
+        assertNotNull(component.getEvidence());
+        assertEquals("Copyright 2012 Google Inc. All Rights Reserved.",
+            component.getEvidence().getCopyright().get(0).getText());
+        assertEquals("Apache-2.0", component.getEvidence().getLicenseChoice().getLicenses().get(0).getId());
+        assertEquals("http://www.apache.org/licenses/LICENSE-2.0",
+            component.getEvidence().getLicenseChoice().getLicenses().get(0).getUrl());
+    }
+
+    private void assertMetadata(final Metadata metadata) {
+        assertNotNull(metadata);
+        assertNotNull(metadata.getTimestamp());
+
+        //Tool
+        assertEquals(1, metadata.getTools().size());
+        assertEquals("Awesome Vendor", metadata.getTools().get(0).getVendor());
+        assertEquals("Awesome Tool", metadata.getTools().get(0).getName());
+        assertEquals("9.1.2", metadata.getTools().get(0).getVersion());
+        assertEquals(1, metadata.getTools().get(0).getHashes().size());
+        assertEquals("SHA-1", metadata.getTools().get(0).getHashes().get(0).getAlgorithm());
+        assertEquals("25ed8e31b995bb927966616df2a42b979a2717f0",
+            metadata.getTools().get(0).getHashes().get(0).getValue());
+
+        //Author
+        assertEquals(1, metadata.getAuthors().size());
+        assertEquals("Samantha Wright", metadata.getAuthors().get(0).getName());
+        assertEquals("samantha.wright@example.com", metadata.getAuthors().get(0).getEmail());
+        assertEquals("800-555-1212", metadata.getAuthors().get(0).getPhone());
+
+        //Component
+        Component component = metadata.getComponent();
+        assertEquals("Acme Application", component.getName());
+        assertEquals("9.1.1", component.getVersion());
+        assertEquals(Component.Type.APPLICATION, component.getType());
+        assertNotNull(component.getSwid());
+        assertEquals("swidgen-242eb18a-503e-ca37-393b-cf156ef09691_9.1.1", component.getSwid().getTagId());
+        assertEquals("Acme Application", component.getSwid().getName());
+        assertEquals("9.1.1", component.getSwid().getVersion());
+        assertEquals(0, component.getSwid().getTagVersion());
+        assertFalse(component.getSwid().isPatch());
+
+        //Release Notes
+        ReleaseNotes releaseNotes = metadata.getComponent().getReleaseNotes();
+        assertEquals("major", releaseNotes.getType());
+        assertEquals("My new release", releaseNotes.getTitle());
+        assertNotNull(releaseNotes.getDescription());
+        assertNotNull(releaseNotes.getSocialImage());
+        assertNotNull(releaseNotes.getTimestamp());
+        assertEquals(1, releaseNotes.getAliases().size());
+        assertEquals(1, releaseNotes.getTags().size());
+
+        //Resolves
+        assertEquals(1, releaseNotes.getResolves().size());
+        Resolves resolves = releaseNotes.getResolves().get(0);
+        assertEquals(Resolves.Type.SECURITY, resolves.getType());
+        assertEquals("CVE-2019-9997", resolves.getId());
+        assertEquals("A security issue was fixed that did something bad", resolves.getDescription());
+        assertEquals("CVE-2019-9997", resolves.getName());
+
+        //Notes
+        assertEquals(1, releaseNotes.getNotes().size());
+        Notes note = releaseNotes.getNotes().get(0);
+        assertEquals("en-US", note.getLocale());
+        assertNotNull(note.getText());
+        assertEquals("PGgxPk15IG5ldyByZWxlYXNlPGgxPgo8cD5SZWxlYXNlIG5vdGVzIGhlcmU8L3A+", note.getText().getText());
+        assertEquals("text/html", note.getText().getContentType());
+        assertEquals("base64", note.getText().getEncoding());
+
+        //Manufacture
+        assertEquals("Acme, Inc.", metadata.getManufacture().getName());
+        assertEquals("https://example.com", metadata.getManufacture().getUrls().get(0));
+        assertEquals(1, metadata.getManufacture().getContacts().size());
+        assertEquals("Acme Professional Services", metadata.getManufacture().getContacts().get(0).getName());
+        assertEquals("professional.services@example.com", metadata.getManufacture().getContacts().get(0).getEmail());
+        assertEquals("Acme, Inc.", metadata.getSupplier().getName());
+
+        //Supplier
+        assertEquals("https://example.com", metadata.getSupplier().getUrls().get(0));
+        assertEquals(1, metadata.getSupplier().getContacts().size());
+        assertEquals("Acme Distribution", metadata.getSupplier().getContacts().get(0).getName());
+        assertEquals("distribution@example.com", metadata.getSupplier().getContacts().get(0).getEmail());
+    }
+
     @Test
     public void test13PrereleasePropertyBom() throws Exception {
         final File file = new File(this.getClass().getResource("/1.3/valid-properties-1.3-prerelease.xml").getFile());
diff --git a/src/test/resources/1.4/valid-vulnerability-1.4.json b/src/test/resources/1.4/valid-vulnerability-1.4.json
index b971cd046..c4065f6c7 100644
--- a/src/test/resources/1.4/valid-vulnerability-1.4.json
+++ b/src/test/resources/1.4/valid-vulnerability-1.4.json
@@ -100,7 +100,7 @@
       },
       "affects": [
         {
-          "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.4",
+          "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.9",
           "versions": [
             {
               "range": "vers:semver/<2.6.7.5",
@@ -119,4 +119,4 @@
       ]
     }
   ]
-}
+}
\ No newline at end of file
diff --git a/src/test/resources/1.4/valid-vulnerability-1.4.textproto b/src/test/resources/1.4/valid-vulnerability-1.4.textproto
index 6c0d0c66b..bcc70e57c 100644
--- a/src/test/resources/1.4/valid-vulnerability-1.4.textproto
+++ b/src/test/resources/1.4/valid-vulnerability-1.4.textproto
@@ -86,7 +86,7 @@ vulnerabilities {
     detail: "An optional explanation of why the application is not affected by the vulnerable component."
   }
   affects: {
-    ref: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.4"
+    ref: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.9"
     versions: {
       range: "vers:semver/<2.6.7.5"
       status: VULNERABILITY_AFFECTED_STATUS_AFFECTED
diff --git a/src/test/resources/1.4/valid-vulnerability-1.4.xml b/src/test/resources/1.4/valid-vulnerability-1.4.xml
index 131c7bacd..46b4448ab 100644
--- a/src/test/resources/1.4/valid-vulnerability-1.4.xml
+++ b/src/test/resources/1.4/valid-vulnerability-1.4.xml
@@ -99,7 +99,7 @@
             </analysis>
             <affects>
                 <target>
-                    <ref>pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.4</ref>
+                    <ref>pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.9</ref>
                     <versions>
                         <version>
                             <range>vers:semver/&lt;2.6.7.5</range>
diff --git a/src/test/resources/bom-1.4.json b/src/test/resources/bom-1.4.json
new file mode 100644
index 000000000..c3f1b4ad4
--- /dev/null
+++ b/src/test/resources/bom-1.4.json
@@ -0,0 +1,388 @@
+{
+  "bomFormat": "CycloneDX",
+  "specVersion": "1.4",
+  "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
+  "version": 1,
+  "metadata": {
+    "timestamp": "2020-04-07T07:01:00Z",
+    "tools": [
+      {
+        "vendor": "Awesome Vendor",
+        "name": "Awesome Tool",
+        "version": "9.1.2",
+        "hashes": [
+          {
+            "alg": "SHA-1",
+            "content": "25ed8e31b995bb927966616df2a42b979a2717f0"
+          }
+        ]
+      }
+    ],
+    "authors": [
+      {
+        "name": "Samantha Wright",
+        "email": "samantha.wright@example.com",
+        "phone": "800-555-1212"
+      }
+    ],
+    "component": {
+      "type": "application",
+      "author": "Acme Super Heros",
+      "name": "Acme Application",
+      "version": "9.1.1",
+      "swid": {
+        "tagId": "swidgen-242eb18a-503e-ca37-393b-cf156ef09691_9.1.1",
+        "name": "Acme Application",
+        "version": "9.1.1",
+        "text": {
+          "contentType": "text/xml",
+          "encoding": "base64",
+          "content": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiID8+CjxTb2Z0d2FyZUlkZW50aXR5IHhtbDpsYW5nPSJFTiIgbmFtZT0iQWNtZSBBcHBsaWNhdGlvbiIgdmVyc2lvbj0iOS4xLjEiIAogdmVyc2lvblNjaGVtZT0ibXVsdGlwYXJ0bnVtZXJpYyIgCiB0YWdJZD0ic3dpZGdlbi1iNTk1MWFjOS00MmMwLWYzODItM2YxZS1iYzdhMmE0NDk3Y2JfOS4xLjEiIAogeG1sbnM9Imh0dHA6Ly9zdGFuZGFyZHMuaXNvLm9yZy9pc28vMTk3NzAvLTIvMjAxNS9zY2hlbWEueHNkIj4gCiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiAKIHhzaTpzY2hlbWFMb2NhdGlvbj0iaHR0cDovL3N0YW5kYXJkcy5pc28ub3JnL2lzby8xOTc3MC8tMi8yMDE1LWN1cnJlbnQvc2NoZW1hLnhzZCBzY2hlbWEueHNkIiA+CiAgPE1ldGEgZ2VuZXJhdG9yPSJTV0lEIFRhZyBPbmxpbmUgR2VuZXJhdG9yIHYwLjEiIC8+IAogIDxFbnRpdHkgbmFtZT0iQWNtZSwgSW5jLiIgcmVnaWQ9ImV4YW1wbGUuY29tIiByb2xlPSJ0YWdDcmVhdG9yIiAvPiAKPC9Tb2Z0d2FyZUlkZW50aXR5Pg=="
+        }
+      },
+      "releaseNotes": {
+        "type": "major",
+        "title": "My new release",
+        "featuredImage": "https://example.com/featured_image.png",
+        "socialImage": "https://example.com/social_image.png",
+        "description": "The main description of your release",
+        "timestamp": "2020-04-07T07:01:00Z",
+        "aliases": [
+          "Project Orion"
+        ],
+        "tags": [
+          "CMS"
+        ],
+        "resolves": [
+          {
+            "type": "security",
+            "id": "CVE-2019-9997",
+            "name": "CVE-2019-9997",
+            "description": "A security issue was fixed that did something bad",
+            "source": {
+              "name": "NVD",
+              "url": "https://nvd.nist.gov/vuln/detail/CVE-2019-9997"
+            },
+            "references": [
+              "http://some/other/site-1"
+            ]
+          }
+        ],
+        "notes": [
+          {
+            "locale": "en-US",
+            "text": {
+              "contentType": "text/html",
+              "encoding": "base64",
+              "content": "PGgxPk15IG5ldyByZWxlYXNlPGgxPgo8cD5SZWxlYXNlIG5vdGVzIGhlcmU8L3A+"
+            }
+          }
+        ]
+      }
+    },
+    "manufacture": {
+      "name": "Acme, Inc.",
+      "url": [
+        "https://example.com"
+      ],
+      "contact": [
+        {
+          "name": "Acme Professional Services",
+          "email": "professional.services@example.com"
+        }
+      ]
+    },
+    "supplier": {
+      "name": "Acme, Inc.",
+      "url": [
+        "https://example.com"
+      ],
+      "contact": [
+        {
+          "name": "Acme Distribution",
+          "email": "distribution@example.com"
+        }
+      ]
+    }
+  },
+  "components": [
+    {
+      "type": "library",
+      "bom-ref": "pkg:maven/com.acme/jackson-databind@2.9.4",
+      "group": "com.acme",
+      "name": "jackson-databind",
+      "version": "2.9.4",
+      "hashes": [
+        {
+          "alg": "MD5",
+          "content": "641b6e166f8b33c5e959e2adcc18b1c7"
+        },
+        {
+          "alg": "SHA-1",
+          "content": "9188560f22e0b73070d2efce670c74af2bdf30af"
+        },
+        {
+          "alg": "SHA-256",
+          "content": "d88bc4e70bfb34d18b5542136639acbb26a8ae2429aa1e47489332fb389cc964"
+        },
+        {
+          "alg": "SHA-384",
+          "content": "d4835048a0f57c74b8fb617d5366ab81376fc92bebe9a93bf24ba7f9da6c9aeeb6179f5d1361f6533211b15f3224cbad"
+        },
+        {
+          "alg": "SHA-512",
+          "content": "74a51ff45e4c11df9ba1f0094282c80489649cb157a75fa337992d2d4592a5a1b8cb4525de8db0ae25233553924d76c36e093ea7fa9df4e5b8b07fd2e074efd6"
+        },
+        {
+          "alg": "SHA3-256",
+          "content": "7478c7cf41c883a04ee89f1813f687886d53fa86f791fff90690c6221e3853aa"
+        },
+        {
+          "alg": "SHA3-384",
+          "content": "a1eea7229716487ad2ebe96b2f997a8408f32f14047994fbcc99b49012cf86c96dbd518e5d57a61b0e57dd37dd0b48f5"
+        },
+        {
+          "alg": "SHA3-512",
+          "content": "7d584825bc1767dfabe7e82b45ccb7a1119b145fa17e76b885e71429c706cef0a3171bc6575b968eec5da56a7966c02fec5402fcee55097ac01d40c550de9d20"
+        },
+        {
+          "alg": "BLAKE2b-256",
+          "content": "d8779633380c050bccf4e733b763ab2abd8ad2db60b517d47fd29bbf76433237"
+        },
+        {
+          "alg": "BLAKE2b-384",
+          "content": "e728ba56c2da995a559a178116c594e8bee4894a79ceb4399d8f479e5563cb1942b85936f646d14170717c576b14db7a"
+        },
+        {
+          "alg": "BLAKE2b-512",
+          "content": "f8ce8d612a6c85c96cf7cebc230f6ddef26e6cedcfbc4a41c766033cc08c6ba097d1470948226807fb2d88d2a2b6fc0ff5e5440e93a603086fdd568bafcd1a9d"
+        },
+        {
+          "alg": "BLAKE3",
+          "content": "26cdc7fb3fd65fc3b621a4ef70bc7d2489d5c19e70c76cf7ec20e538df0047cf"
+        }
+      ],
+      "licenses": [
+        {
+          "expression": "EPL-2.0 OR GPL-2.0-with-classpath-exception"
+        }
+      ],
+      "copyright": "Copyright Example Inc. All rights reserved.",
+      "cpe": "cpe:/a:example:myapplication:1.0.0",
+      "purl": "pkg:maven/com.acme/jackson-databind@2.9.4",
+      "swid": {
+        "tagId": "swidgen-242eb18a-503e-ca37-393b-cf156ef09691_9.1.1",
+        "name": "Acme Application",
+        "version": "9.1.1"
+      },
+      "modified": false,
+      "externalReferences": [
+        {
+          "type": "documentation",
+          "url": "http://example.org/docs",
+          "comment": "All component versions are documented here"
+        }
+      ],
+      "evidence": {
+        "licenses": [
+          {
+            "license": {
+                "id": "Apache-2.0",
+                "url": "http://www.apache.org/licenses/LICENSE-2.0"
+            }
+          }
+        ],
+        "copyright": [
+          {
+            "text": "Copyright 2012 Google Inc. All Rights Reserved."
+          }
+        ]
+      }
+    }
+  ],
+  "services": [
+    {
+      "bom-ref": "b2a46a4b-8367-4bae-9820-95557cfe03a8",
+      "provider": {
+        "name": "Partner Org",
+        "url": [
+            "https://partner.org"
+        ],
+        "contact": [
+          {
+            "name": "Support",
+            "email": "support@partner",
+            "phone": "800-555-1212"
+          }
+        ]
+      },
+      "group": "org.partner",
+      "name": "Stock ticker service",
+      "version": "2020-Q2",
+      "description": "Provides real-time stock information",
+      "endpoints": [
+          "https://partner.org/api/v1/lookup",
+          "https://partner.org/api/v1/stock"
+      ],
+      "authenticated": true,
+      "x-trust-boundary": true,
+      "data": [
+          {
+            "flow": "inbound",
+            "classification": "PII"
+          },
+          {
+            "flow": "outbound",
+            "classification": "PIFI"
+          },
+          {
+            "flow": "bi-directional",
+            "classification": "pubic"
+          },
+          {
+            "flow": "unknown",
+            "classification": "partner-data"
+          }
+        ],
+      "licenses": [
+        {
+          "license": {
+            "name": "Partner license"
+          }
+        }
+      ],
+      "externalReferences": [
+          {
+            "type": "website",
+            "url": "http://partner.org"
+          },
+          {
+            "type": "documentation",
+            "url": "http://api.partner.org/swagger"
+          }
+        ]
+    }
+  ],
+  "dependencies": [
+    {
+      "ref": "pkg:maven/com.acme/jackson-databind@2.9.4",
+      "dependsOn": []
+    }
+  ],
+  "compositions": [
+      {
+        "aggregate": "complete",
+        "assemblies": [
+            "pkg:maven/partner/shaded-library@1.0"
+        ],
+        "dependencies": [
+          "acme-application-1.0"
+        ]
+      },
+      {
+        "aggregate": "unknown",
+        "assemblies": [
+          "pkg:maven/acme/library@3.0"
+        ]
+      }
+    ],
+  "vulnerabilities": [
+    {
+      "bom-ref": "6eee14da-8f42-4cc4-bb65-203235f02415",
+      "id": "SONATYPE-2021123",
+      "source": {
+        "name": "Sonatype",
+        "url": "https://www.vuln.com"
+      },
+      "references": [
+        {
+          "id": "CVE-2018-7489",
+          "source": {
+            "name": "NVD",
+            "url": "https://nvd.nist.gov/vuln/detail/CVE-2019-9997"
+          }
+        }
+      ],
+      "ratings": [
+        {
+          "source": {
+            "name": "NVD",
+            "url": "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H&version=3.0"
+          },
+          "score": 9.8,
+          "severity": "critical",
+          "method": "CVSSv3",
+          "vector": "AN/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
+          "justification": "An optional reason for rating the vulnerability as it was"
+        }
+      ],
+      "cwes": [
+        184
+      ],
+      "description": "Description",
+      "detail": "Detail",
+      "recommendation": "Upgrade",
+      "advisories": [
+        {
+          "title": "GitHub Commit",
+          "url": "https://github.com/FasterXML/jackson-databind/commit/6799f8f10cc78e9af6d443ed6982d00a13f2e7d2"
+        }
+      ],
+      "created": "2020-04-07T07:01:00Z",
+      "published": "2020-04-07T07:01:00Z",
+      "updated": "2020-04-07T07:01:00Z",
+      "credits": {
+        "organizations": [
+          {
+            "name": "Acme, Inc.",
+            "url": [
+            "https://example.com"
+             ]
+          }
+        ],
+        "individuals": [
+          {
+            "name": "Jane Doe",
+            "email": "jane.doe@example.com"
+          }
+        ]
+      },
+      "tools": [
+        {
+          "vendor": "Sonatype",
+          "name": "Sonatype CLI",
+          "version": "1.131",
+          "hashes": [
+            {
+              "alg": "SHA-256",
+              "content": "2eaf8c62831a1658c95d41fdc683cd177c147733c64a93e59cb2362829e45b7d"
+            }
+          ]
+        }
+      ],
+      "analysis": {
+        "state": "not_affected",
+        "justification": "code_not_reachable",
+        "response": [
+          "update"
+        ],
+        "detail": "An optional explanation of why the application is not affected by the vulnerable component."
+      },
+      "affects": [
+         {
+          "ref": "pkg:maven/com.acme/jackson-databind@2.9.9",
+          "versions":  [
+              {
+                "range": "vers:semver/<2.6.7.5",
+                "status": "affected"
+              },
+              {
+                "version": "2.9.9",
+                "status": "affected"
+              }
+            ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/src/test/resources/bom-1.4.xml b/src/test/resources/bom-1.4.xml
new file mode 100644
index 000000000..2b9fd2c54
--- /dev/null
+++ b/src/test/resources/bom-1.4.xml
@@ -0,0 +1,282 @@
+<?xml version="1.0"?>
+<bom serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79" version="1" xmlns="http://cyclonedx.org/schema/bom/1.4">
+    <metadata>
+        <timestamp>2020-04-07T07:01:00Z</timestamp>
+        <tools>
+            <tool>
+                <vendor>Awesome Vendor</vendor>
+                <name>Awesome Tool</name>
+                <version>9.1.2</version>
+                <hashes>
+                    <hash alg="SHA-1">25ed8e31b995bb927966616df2a42b979a2717f0</hash>
+                </hashes>
+            </tool>
+        </tools>
+        <authors>
+            <author>
+                <name>Samantha Wright</name>
+                <email>samantha.wright@example.com</email>
+                <phone>800-555-1212</phone>
+            </author>
+        </authors>
+        <component type="application">
+            <author>Acme Super Heros</author>
+            <name>Acme Application</name>
+            <version>9.1.1</version>
+            <swid tagId="swidgen-242eb18a-503e-ca37-393b-cf156ef09691_9.1.1" name="Acme Application" version="9.1.1">
+                <text content-type="text/xml" encoding="base64">PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiID8+CjxTb2Z0d2FyZUlkZW50aXR5IHhtbDpsYW5nPSJFTiIgbmFtZT0iQWNtZSBBcHBsaWNhdGlvbiIgdmVyc2lvbj0iOS4xLjEiIAogdmVyc2lvblNjaGVtZT0ibXVsdGlwYXJ0bnVtZXJpYyIgCiB0YWdJZD0ic3dpZGdlbi1iNTk1MWFjOS00MmMwLWYzODItM2YxZS1iYzdhMmE0NDk3Y2JfOS4xLjEiIAogeG1sbnM9Imh0dHA6Ly9zdGFuZGFyZHMuaXNvLm9yZy9pc28vMTk3NzAvLTIvMjAxNS9zY2hlbWEueHNkIj4gCiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiAKIHhzaTpzY2hlbWFMb2NhdGlvbj0iaHR0cDovL3N0YW5kYXJkcy5pc28ub3JnL2lzby8xOTc3MC8tMi8yMDE1LWN1cnJlbnQvc2NoZW1hLnhzZCBzY2hlbWEueHNkIiA+CiAgPE1ldGEgZ2VuZXJhdG9yPSJTV0lEIFRhZyBPbmxpbmUgR2VuZXJhdG9yIHYwLjEiIC8+IAogIDxFbnRpdHkgbmFtZT0iQWNtZSwgSW5jLiIgcmVnaWQ9ImV4YW1wbGUuY29tIiByb2xlPSJ0YWdDcmVhdG9yIiAvPiAKPC9Tb2Z0d2FyZUlkZW50aXR5Pg==</text>
+            </swid>
+            <releaseNotes>
+                <type>major</type>
+                <title>My new release</title>
+                <featuredImage>https://example.com/featured_image.png</featuredImage>
+                <socialImage>https://example.com/social_image.png</socialImage>
+                <description>The main description of your release</description>
+                <timestamp>2020-04-07T07:01:00Z</timestamp>
+                <aliases>
+                    <alias>Project Orion</alias>
+                </aliases>
+                <tags>
+                    <tag>CMS</tag>
+                </tags>
+                <resolves>
+                    <issue type="security">
+                        <id>CVE-2019-9997</id>
+                        <name>CVE-2019-9997</name>
+                        <description>A security issue was fixed that did something bad</description>
+                        <source>
+                            <name>NVD</name>
+                            <url>https://nvd.nist.gov/vuln/detail/CVE-2019-9997</url>
+                        </source>
+                        <references>
+                            <url>http://some/other/site-1</url>
+                        </references>
+                    </issue>
+                </resolves>
+                <notes>
+                    <note>
+                        <locale>en-US</locale>
+                        <text content-type="text/html" encoding="base64">PGgxPk15IG5ldyByZWxlYXNlPGgxPgo8cD5SZWxlYXNlIG5vdGVzIGhlcmU8L3A+</text>
+                    </note>
+                </notes>
+            </releaseNotes>
+        </component>
+        <manufacture>
+            <name>Acme, Inc.</name>
+            <url>https://example.com</url>
+            <contact>
+                <name>Acme Professional Services</name>
+                <email>professional.services@example.com</email>
+            </contact>
+        </manufacture>
+        <supplier>
+            <name>Acme, Inc.</name>
+            <url>https://example.com</url>
+            <contact>
+                <name>Acme Distribution</name>
+                <email>distribution@example.com</email>
+            </contact>
+        </supplier>
+    </metadata>
+    <components>
+        <component type="library" bom-ref="pkg:maven/com.acme/jackson-databind@2.9.4">
+            <group>com.acme</group>
+            <name>jackson-databind</name>
+            <version>2.9.4</version>
+            <hashes>
+                <hash alg="MD5">641b6e166f8b33c5e959e2adcc18b1c7</hash>
+                <hash alg="SHA-1">9188560f22e0b73070d2efce670c74af2bdf30af</hash>
+                <hash alg="SHA-256">d88bc4e70bfb34d18b5542136639acbb26a8ae2429aa1e47489332fb389cc964</hash>
+                <hash alg="SHA-384">d4835048a0f57c74b8fb617d5366ab81376fc92bebe9a93bf24ba7f9da6c9aeeb6179f5d1361f6533211b15f3224cbad</hash>
+                <hash alg="SHA-512">74a51ff45e4c11df9ba1f0094282c80489649cb157a75fa337992d2d4592a5a1b8cb4525de8db0ae25233553924d76c36e093ea7fa9df4e5b8b07fd2e074efd6</hash>
+                <hash alg="SHA3-256">7478c7cf41c883a04ee89f1813f687886d53fa86f791fff90690c6221e3853aa</hash>
+                <hash alg="SHA3-384">a1eea7229716487ad2ebe96b2f997a8408f32f14047994fbcc99b49012cf86c96dbd518e5d57a61b0e57dd37dd0b48f5</hash>
+                <hash alg="SHA3-512">7d584825bc1767dfabe7e82b45ccb7a1119b145fa17e76b885e71429c706cef0a3171bc6575b968eec5da56a7966c02fec5402fcee55097ac01d40c550de9d20</hash>
+                <hash alg="BLAKE2b-256">d8779633380c050bccf4e733b763ab2abd8ad2db60b517d47fd29bbf76433237</hash>
+                <hash alg="BLAKE2b-384">e728ba56c2da995a559a178116c594e8bee4894a79ceb4399d8f479e5563cb1942b85936f646d14170717c576b14db7a</hash>
+                <hash alg="BLAKE2b-512">f8ce8d612a6c85c96cf7cebc230f6ddef26e6cedcfbc4a41c766033cc08c6ba097d1470948226807fb2d88d2a2b6fc0ff5e5440e93a603086fdd568bafcd1a9d</hash>
+                <hash alg="BLAKE3">26cdc7fb3fd65fc3b621a4ef70bc7d2489d5c19e70c76cf7ec20e538df0047cf</hash>
+            </hashes>
+            <licenses>
+                <expression>EPL-2.0 OR GPL-2.0-with-classpath-exception</expression>
+            </licenses>
+            <copyright>Copyright Example Inc. All rights reserved.</copyright>
+            <cpe>cpe:/a:example:myapplication:1.0.0</cpe>
+            <purl>pkg:maven/com.acme/jackson-databind@2.9.4</purl>
+            <swid tagId="swidgen-242eb18a-503e-ca37-393b-cf156ef09691_9.1.1" name="Acme Application" version="9.1.1" />
+            <modified>false</modified>
+            <externalReferences>
+                <reference type="documentation">
+                    <url>http://example.org/docs</url>
+                    <comment>All component versions are documented here</comment>
+                </reference>
+            </externalReferences>
+            <evidence>
+                <licenses>
+                    <license>
+                        <id>Apache-2.0</id>
+                        <url>http://www.apache.org/licenses/LICENSE-2.0</url>
+                    </license>
+                </licenses>
+                <copyright>
+                    <text><![CDATA[Copyright 2012 Google Inc. All Rights Reserved.]]></text>
+                </copyright>
+            </evidence>
+        </component>
+    </components>
+    <services>
+        <service bom-ref="b2a46a4b-8367-4bae-9820-95557cfe03a8">
+            <provider>
+                <name>Partner Org</name>
+                <url>https://partner.org</url>
+                <contact>
+                    <name>Support</name>
+                    <email>support@partner</email>
+                    <phone>800-555-1212</phone>
+                </contact>
+            </provider>
+            <group>org.partner</group>
+            <name>Stock ticker service</name>
+            <version>2020-Q2</version>
+            <description>Provides real-time stock information</description>
+            <endpoints>
+                <endpoint>https://partner.org/api/v1/lookup</endpoint>
+                <endpoint>https://partner.org/api/v1/stock</endpoint>
+            </endpoints>
+            <authenticated>true</authenticated>
+            <x-trust-boundary>true</x-trust-boundary>
+            <data>
+                <classification flow="inbound">PII</classification>
+                <classification flow="outbound">PIFI</classification>
+                <classification flow="bi-directional">pubic</classification>
+                <classification flow="unknown">partner-data</classification>
+            </data>
+            <licenses>
+                <license>
+                    <name>Partner license</name>
+                </license>
+            </licenses>
+            <externalReferences>
+                <reference type="website">
+                    <url>http://partner.org</url>
+                </reference>
+                <reference type="documentation">
+                    <url>http://api.partner.org/swagger</url>
+                </reference>
+            </externalReferences>
+        </service>
+    </services>
+    <dependencies>
+        <dependency ref="pkg:maven/com.acme/jackson-databind@2.9.4"/>
+    </dependencies>
+    <compositions>
+        <composition>
+            <aggregate>complete</aggregate>
+            <assemblies>
+                <assembly ref="pkg:maven/partner/shaded-library@1.0"/>
+            </assemblies>
+            <dependencies>
+                <dependency ref="acme-application-1.0"/>
+            </dependencies>
+        </composition>
+        <composition>
+            <aggregate>unknown</aggregate>
+            <assemblies>
+                <assembly ref="pkg:maven/acme/library@3.0"/>
+            </assemblies>
+        </composition>
+    </compositions>
+    <vulnerabilities>
+        <vulnerability bom-ref="6eee14da-8f42-4cc4-bb65-203235f02415">
+            <id>SONATYPE-2021123</id>
+            <source>
+                <name>Sonatype</name>
+                <url>https://www.vuln.com</url>
+            </source>
+            <references>
+                <reference>
+                    <id>CVE-2018-7489</id>
+                    <source>
+                        <name>NVD</name>
+                        <url>https://nvd.nist.gov/vuln/detail/CVE-2019-9997</url>
+                    </source>
+                </reference>
+            </references>
+            <ratings>
+                <rating>
+                    <source>
+                        <name>NVD</name>
+                        <url>https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H&amp;version=3.0</url>
+                    </source>
+                    <score>9.8</score>
+                    <severity>critical</severity>
+                    <method>CVSSv3</method>
+                    <vector>AN/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H</vector>
+                    <justification>An optional reason for rating the vulnerability as it was</justification>
+                </rating>
+            </ratings>
+            <cwes>
+                <cwe>184</cwe>
+            </cwes>
+            <description>Description</description>
+            <detail>Detail</detail>
+            <recommendation>Upgrade</recommendation>
+            <advisories>
+                <advisory>
+                    <title>GitHub Commit</title>
+                    <url>https://github.com/FasterXML/jackson-databind/commit/6799f8f10cc78e9af6d443ed6982d00a13f2e7d2</url>
+                </advisory>
+            </advisories>
+            <created>2020-04-07T07:01:00Z</created>
+            <published>2020-04-07T07:01:00Z</published>
+            <updated>2020-04-07T07:01:00Z</updated>
+            <credits>
+                <organizations>
+                    <organization>
+                        <name>Acme, Inc.</name>
+                        <url>https://example.com</url>
+                    </organization>
+                </organizations>
+                <individuals>
+                    <individual>
+                        <name>Jane Doe</name>
+                        <email>jane.doe@example.com</email>
+                    </individual>
+                </individuals>
+            </credits>
+            <tools>
+                <tool>
+                    <vendor>Sonatype</vendor>
+                    <name>Sonatype CLI</name>
+                    <version>1.131</version>
+                    <hashes>
+                        <hash alg="SHA-256">2eaf8c62831a1658c95d41fdc683cd177c147733c64a93e59cb2362829e45b7d</hash>
+                    </hashes>
+                </tool>
+            </tools>
+            <analysis>
+                <state>not_affected</state>
+                <justification>code_not_reachable</justification>
+                <responses>
+                    <response>update</response>
+                </responses>
+                <detail>An optional explanation of why the application is not affected by the vulnerable component.</detail>
+            </analysis>
+            <affects>
+                <target>
+                    <ref>pkg:maven/com.acme/jackson-databind@2.9.9</ref>
+                    <versions>
+                        <version>
+                            <range>vers:semver/&lt;2.6.7.5</range>
+                            <status>affected</status>
+                        </version>
+                        <version>
+                            <version>2.9.9</version>
+                            <status>affected</status>
+                        </version>
+                    </versions>
+                </target>
+            </affects>
+        </vulnerability>
+    </vulnerabilities>
+</bom>
diff --git a/src/test/resources/valid-ext-vulnerability-1.4.xml b/src/test/resources/valid-ext-vulnerability-1.4.xml
new file mode 100644
index 000000000..8e5d2d675
--- /dev/null
+++ b/src/test/resources/valid-ext-vulnerability-1.4.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<bom xmlns="http://cyclonedx.org/schema/bom/1.4"
+     xmlns:v="http://cyclonedx.org/schema/ext/vulnerability/1.0"
+     serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"
+     version="1">
+    <components>
+        <component type="library" bom-ref="pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.9">
+            <group>com.fasterxml.jackson.core</group>
+            <name>jackson-databind</name>
+            <version>2.9.9</version>
+            <purl>pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.9</purl>
+        </component>
+    </components>
+    <v:vulnerabilities>
+        <v:vulnerability ref="pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.9">
+            <v:id>CVE-2018-7489</v:id>
+            <v:source name="NVD">
+                <v:url>https://nvd.nist.gov/vuln/detail/CVE-2018-7489</v:url>
+            </v:source>
+            <v:ratings>
+                <v:rating>
+                    <v:score>
+                        <v:base>9.8</v:base>
+                        <v:impact>5.9</v:impact>
+                        <v:exploitability>3.0</v:exploitability>
+                    </v:score>
+                    <v:severity>Critical</v:severity>
+                    <v:method>CVSSv3</v:method>
+                    <v:vector>AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H</v:vector>
+                </v:rating>
+            </v:ratings>
+            <v:cwes>
+                <v:cwe>184</v:cwe>
+                <v:cwe>502</v:cwe>
+            </v:cwes>
+            <v:description>FasterXML jackson-databind before 2.7.9.3, 2.8.x before 2.8.11.1 and 2.9.x before 2.9.5 allows unauthenticated remote code execution because of an incomplete fix for the CVE-2017-7525 deserialization flaw. This is exploitable by sending maliciously crafted JSON input to the readValue method of the ObjectMapper, bypassing a blacklist that is ineffective if the c3p0 libraries are available in the classpath.</v:description>
+            <v:recommendations>
+                <v:recommendation>Upgrade</v:recommendation>
+            </v:recommendations>
+            <v:advisories>
+                <v:advisory>https://github.com/FasterXML/jackson-databind/issues/1931</v:advisory>
+                <v:advisory>http://www.securityfocus.com/bid/103203</v:advisory>
+                <v:advisory>http://www.securitytracker.com/id/1040693</v:advisory>
+                <v:advisory>http://www.securitytracker.com/id/1041890</v:advisory>
+            </v:advisories>
+        </v:vulnerability>
+        <v:vulnerability ref="pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.8">
+            <v:id>CVE-2018-7489</v:id>
+            <v:source name="NVD">
+                <v:url>https://nvd.nist.gov/vuln/detail/CVE-2018-7489</v:url>
+            </v:source>
+            <v:ratings>
+                <v:rating>
+                    <v:score>
+                        <v:base>9.8</v:base>
+                        <v:impact>5.9</v:impact>
+                        <v:exploitability>3.0</v:exploitability>
+                    </v:score>
+                    <v:severity>Critical</v:severity>
+                    <v:method>CVSSv3</v:method>
+                    <v:vector>AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H</v:vector>
+                </v:rating>
+            </v:ratings>
+            <v:cwes>
+                <v:cwe>184</v:cwe>
+                <v:cwe>502</v:cwe>
+            </v:cwes>
+            <v:description>FasterXML jackson-databind before 2.7.9.3, 2.8.x before 2.8.11.1 and 2.9.x before 2.9.5 allows unauthenticated remote code execution because of an incomplete fix for the CVE-2017-7525 deserialization flaw. This is exploitable by sending maliciously crafted JSON input to the readValue method of the ObjectMapper, bypassing a blacklist that is ineffective if the c3p0 libraries are available in the classpath.</v:description>
+            <v:recommendations>
+                <v:recommendation>Upgrade</v:recommendation>
+            </v:recommendations>
+            <v:advisories>
+                <v:advisory>https://github.com/FasterXML/jackson-databind/issues/1931</v:advisory>
+                <v:advisory>http://www.securityfocus.com/bid/103203</v:advisory>
+                <v:advisory>http://www.securitytracker.com/id/1040693</v:advisory>
+                <v:advisory>http://www.securitytracker.com/id/1041890</v:advisory>
+            </v:advisories>
+        </v:vulnerability>
+    </v:vulnerabilities>
+</bom>