Repository: CycloneDX/cyclonedx-core-java
Pull request #411 (2024-05-26 02:27:45)

Fix issue with License Serialization - Issue 408 and 409
This fixes:
https://github.com/CycloneDX/cyclonedx-core-java/issues/409
https://github.com/CycloneDX/cyclonedx-core-java/issues/408

There is still some code simplification that can be done, but given the urgency and the need for a solution, it'll be done in a different PR


Patch:
diff --git a/src/main/java/org/cyclonedx/generators/AbstractBomGenerator.java b/src/main/java/org/cyclonedx/generators/AbstractBomGenerator.java
index e50316679..025a91288 100644
--- a/src/main/java/org/cyclonedx/generators/AbstractBomGenerator.java
+++ b/src/main/java/org/cyclonedx/generators/AbstractBomGenerator.java
@@ -37,7 +37,7 @@ public abstract class AbstractBomGenerator extends CycloneDxSchema
 
   protected void setupObjectMapper(boolean isXml) {
     SimpleModule licenseModule = new SimpleModule();
-    licenseModule.addSerializer(new LicenseChoiceSerializer());
+    licenseModule.addSerializer(new LicenseChoiceSerializer(isXml, version));
     mapper.registerModule(licenseModule);
 
     SimpleModule lifecycleModule = new SimpleModule();
diff --git a/src/main/java/org/cyclonedx/model/Component.java b/src/main/java/org/cyclonedx/model/Component.java
index 6fde7019a..a3904671f 100644
--- a/src/main/java/org/cyclonedx/model/Component.java
+++ b/src/main/java/org/cyclonedx/model/Component.java
@@ -22,6 +22,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonUnwrapped;
 import org.cyclonedx.Version;
 import org.cyclonedx.model.component.ModelCard;
@@ -39,6 +40,7 @@ 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 com.github.packageurl.PackageURL;
+import org.cyclonedx.util.deserializer.LicenseDeserializer;
 import org.cyclonedx.util.deserializer.PropertiesDeserializer;
 
 @SuppressWarnings("unused")
@@ -155,7 +157,7 @@ public class Component extends ExtensibleElement {
     private String description;
     private Scope scope;
     private List<Hash> hashes;
-    private LicenseChoice license;
+    private LicenseChoice licenses;
     private String copyright;
     private String cpe;
     private String purl;
@@ -310,14 +312,25 @@ public class Component extends ExtensibleElement {
         this.hashes.add(hash);
     }
 
-    @JacksonXmlProperty(localName = "licenses")
-    @JsonProperty("licenses")
+    @JsonDeserialize(using = LicenseDeserializer.class)
+    public LicenseChoice getLicenses() {
+        return licenses;
+    }
+
+    @JacksonXmlElementWrapper (useWrapping = false)
+    public void setLicenses(LicenseChoice licenses) {
+        this.licenses = licenses;
+    }
+
+    @Deprecated
     public LicenseChoice getLicenseChoice() {
-        return license;
+        return getLicenses();
     }
 
+    @Deprecated
+    @JsonIgnore
     public void setLicenseChoice(LicenseChoice licenseChoice) {
-        this.license = licenseChoice;
+        setLicenses(licenseChoice);
     }
 
     public String getCopyright() {
@@ -534,7 +547,7 @@ public class Component extends ExtensibleElement {
 
     @Override
     public int hashCode() {
-        return Objects.hash(author, publisher, group, name, version, description, scope, hashes, license, copyright,
+        return Objects.hash(author, publisher, group, name, version, description, scope, hashes, licenses, copyright,
             cpe, purl, omniborId, swhid, swid, modified, components, evidence, releaseNotes, type, modelCard, data);
     }
 
@@ -553,7 +566,7 @@ public class Component extends ExtensibleElement {
                 Objects.equals(description, component.description) &&
                 Objects.equals(scope, component.scope) &&
                 Objects.equals(hashes, component.hashes) &&
-                Objects.equals(license, component.license) &&
+                Objects.equals(licenses, component.licenses) &&
                 Objects.equals(copyright, component.copyright) &&
                 Objects.equals(cpe, component.cpe) &&
                 Objects.equals(purl, component.purl) &&
diff --git a/src/main/java/org/cyclonedx/model/Evidence.java b/src/main/java/org/cyclonedx/model/Evidence.java
index 743ed4073..e484a06aa 100644
--- a/src/main/java/org/cyclonedx/model/Evidence.java
+++ b/src/main/java/org/cyclonedx/model/Evidence.java
@@ -18,6 +18,7 @@
  */
 package org.cyclonedx.model;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
@@ -42,7 +43,7 @@ import java.util.List;
 public class Evidence
     extends ExtensibleElement
 {
-    private LicenseChoice license;
+    private LicenseChoice licenses;
 
     private List<Copyright> copyright;
 
@@ -55,15 +56,25 @@ public class Evidence
     @VersionFilter(Version.VERSION_15)
     private Callstack callstack;
 
-    @JacksonXmlProperty(localName = "licenses")
-    @JsonProperty("licenses")
-    @JsonDeserialize(using = LicenseDeserializer.class)
+    @Deprecated
     public LicenseChoice getLicenseChoice() {
-        return license;
+        return getLicenses();
     }
 
+    @Deprecated
+    @JsonIgnore
     public void setLicenseChoice(LicenseChoice licenseChoice) {
-        this.license = licenseChoice;
+        setLicenses(licenseChoice);
+    }
+
+    @JsonDeserialize(using = LicenseDeserializer.class)
+    public LicenseChoice getLicenses() {
+        return licenses;
+    }
+
+    @JacksonXmlElementWrapper (useWrapping = false)
+    public void setLicenses(LicenseChoice licenses) {
+        this.licenses = licenses;
     }
 
     @JacksonXmlElementWrapper(useWrapping = false)
diff --git a/src/main/java/org/cyclonedx/model/License.java b/src/main/java/org/cyclonedx/model/License.java
index ace179b03..ae1206fbd 100644
--- a/src/main/java/org/cyclonedx/model/License.java
+++ b/src/main/java/org/cyclonedx/model/License.java
@@ -50,6 +50,7 @@ public class License extends ExtensibleElement {
 
     @JacksonXmlProperty(isAttribute = true, localName = "acknowledgement")
     @JsonProperty("acknowledgement")
+    @VersionFilter(Version.VERSION_16)
     private String acknowledgement;
 
     @VersionFilter(Version.VERSION_15)
diff --git a/src/main/java/org/cyclonedx/model/LicenseChoice.java b/src/main/java/org/cyclonedx/model/LicenseChoice.java
index 9bda4b551..b82778a1c 100644
--- a/src/main/java/org/cyclonedx/model/LicenseChoice.java
+++ b/src/main/java/org/cyclonedx/model/LicenseChoice.java
@@ -23,22 +23,26 @@ import java.util.List;
 import java.util.Objects;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonRootName;
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 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.license.Expression;
 import org.cyclonedx.util.deserializer.LicenseDeserializer;
+import org.cyclonedx.util.serializer.LicenseChoiceSerializer;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
 @JsonInclude(JsonInclude.Include.NON_EMPTY)
 @JsonDeserialize(using = LicenseDeserializer.class)
 public class LicenseChoice {
 
+    @JacksonXmlElementWrapper(useWrapping = false)
     private List<License> license;
     private Expression expression;
 
     @JacksonXmlProperty(localName = "license")
-    @JacksonXmlElementWrapper(useWrapping = false)
     public List<License> getLicenses() {
         return license;
     }
diff --git a/src/main/java/org/cyclonedx/model/Metadata.java b/src/main/java/org/cyclonedx/model/Metadata.java
index 35114b487..3b8c64fac 100644
--- a/src/main/java/org/cyclonedx/model/Metadata.java
+++ b/src/main/java/org/cyclonedx/model/Metadata.java
@@ -28,6 +28,7 @@ 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.Version;
+import org.cyclonedx.util.deserializer.LicenseDeserializer;
 import org.cyclonedx.util.deserializer.LifecycleDeserializer;
 import org.cyclonedx.util.deserializer.MetadataDeserializer;
 import org.cyclonedx.util.serializer.CustomDateSerializer;
@@ -85,7 +86,7 @@ public class Metadata
     private OrganizationalEntity supplier;
 
     @VersionFilter(Version.VERSION_13)
-    private LicenseChoice license;
+    private LicenseChoice licenses;
 
     @VersionFilter(Version.VERSION_13)
     private List<Property> properties;
@@ -159,14 +160,25 @@ public class Metadata
         this.supplier = supplier;
     }
 
-    @JacksonXmlProperty(localName = "licenses")
-    @JsonProperty("licenses")
+    @Deprecated
     public LicenseChoice getLicenseChoice() {
-        return license;
+        return getLicenses();
     }
 
+    @Deprecated
+    @JsonIgnore
     public void setLicenseChoice(LicenseChoice licenseChoice) {
-        this.license = licenseChoice;
+        setLicenses(licenseChoice);
+    }
+
+    @JsonDeserialize(using = LicenseDeserializer.class)
+    public LicenseChoice getLicenses() {
+        return licenses;
+    }
+
+    @JacksonXmlElementWrapper (useWrapping = false)
+    public void setLicenses(LicenseChoice licenses) {
+        this.licenses = licenses;
     }
 
     @JacksonXmlElementWrapper(localName = "properties")
@@ -222,7 +234,7 @@ public class Metadata
                 Objects.equals(component, metadata.component) &&
                 Objects.equals(manufacture, metadata.manufacture) &&
                 Objects.equals(supplier, metadata.supplier) &&
-                Objects.equals(license, metadata.license) &&
+                Objects.equals(licenses, metadata.licenses) &&
                 Objects.equals(lifecycles, metadata.lifecycles) &&
                 Objects.equals(toolInformation, metadata.toolInformation) &&
                 Objects.equals(properties, metadata.properties);
@@ -230,7 +242,7 @@ public class Metadata
 
     @Override
     public int hashCode() {
-        return Objects.hash(timestamp, toolInformation, authors, component, manufacture, supplier, license, properties,
+        return Objects.hash(timestamp, toolInformation, authors, component, manufacture, supplier, licenses, properties,
             lifecycles);
     }
 }
diff --git a/src/main/java/org/cyclonedx/model/Service.java b/src/main/java/org/cyclonedx/model/Service.java
index fb6e6dd44..7bab4273c 100644
--- a/src/main/java/org/cyclonedx/model/Service.java
+++ b/src/main/java/org/cyclonedx/model/Service.java
@@ -18,6 +18,7 @@
  */
 package org.cyclonedx.model;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
@@ -29,6 +30,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import org.cyclonedx.Version;
 import org.cyclonedx.model.component.Tags;
 import org.cyclonedx.util.deserializer.ExternalReferencesDeserializer;
+import org.cyclonedx.util.deserializer.LicenseDeserializer;
 import org.cyclonedx.util.deserializer.StringListDeserializer;
 
 import java.util.ArrayList;
@@ -71,7 +73,7 @@ public class Service extends ExtensibleElement {
     @JsonProperty("x-trust-boundary")
     private Boolean xTrustBoundary;
     private List<ServiceData> data;
-    private LicenseChoice license;
+    private LicenseChoice licenses;
     private List<ExternalReference> externalReferences;
     @VersionFilter(Version.VERSION_13)
     private List<Property> properties;
@@ -183,14 +185,25 @@ public class Service extends ExtensibleElement {
         this.data = data;
     }
 
-    @JacksonXmlProperty(localName = "licenses")
-    @JsonProperty("licenses")
+    @Deprecated
     public LicenseChoice getLicense() {
-        return license;
+        return getLicenses();
     }
 
-    public void setLicense(LicenseChoice license) {
-        this.license = license;
+    @Deprecated
+    @JsonIgnore
+    public void setLicense(LicenseChoice licenseChoice) {
+        setLicenses(licenseChoice);
+    }
+
+    @JsonDeserialize(using = LicenseDeserializer.class)
+    public LicenseChoice getLicenses() {
+        return licenses;
+    }
+
+    @JacksonXmlElementWrapper (useWrapping = false)
+    public void setLicenses(LicenseChoice licenses) {
+        this.licenses = licenses;
     }
 
     @JacksonXmlElementWrapper(localName = "externalReferences")
diff --git a/src/main/java/org/cyclonedx/model/license/Expression.java b/src/main/java/org/cyclonedx/model/license/Expression.java
index 18b510bd0..987fdf76f 100644
--- a/src/main/java/org/cyclonedx/model/license/Expression.java
+++ b/src/main/java/org/cyclonedx/model/license/Expression.java
@@ -8,6 +8,8 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText;
+import org.cyclonedx.Version;
+import org.cyclonedx.model.VersionFilter;
 import org.cyclonedx.util.deserializer.ExpressionDeserializer;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
@@ -18,9 +20,11 @@ public class Expression
 {
   @JacksonXmlProperty(isAttribute = true, localName = "bom-ref")
   @JsonProperty("bom-ref")
+  @VersionFilter(Version.VERSION_16)
   private String bomRef;
   @JacksonXmlProperty(isAttribute = true, localName = "acknowledgement")
   @JsonProperty("acknowledgement")
+  @VersionFilter(Version.VERSION_16)
   private String acknowledgement;
 
   @JacksonXmlText
diff --git a/src/main/java/org/cyclonedx/util/deserializer/LicenseDeserializer.java b/src/main/java/org/cyclonedx/util/deserializer/LicenseDeserializer.java
index 0d87e8c56..b1fb8f7d4 100644
--- a/src/main/java/org/cyclonedx/util/deserializer/LicenseDeserializer.java
+++ b/src/main/java/org/cyclonedx/util/deserializer/LicenseDeserializer.java
@@ -24,7 +24,9 @@ import com.fasterxml.jackson.core.JsonParser;
 import com.fasterxml.jackson.databind.DeserializationContext;
 import com.fasterxml.jackson.databind.JsonDeserializer;
 import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
 import org.cyclonedx.model.License;
 import org.cyclonedx.model.LicenseChoice;
 import org.cyclonedx.model.license.Expression;
@@ -38,21 +40,29 @@ public class LicenseDeserializer extends JsonDeserializer<LicenseChoice>
   public LicenseChoice deserialize(
       final JsonParser p, final DeserializationContext ctxt) throws IOException
   {
+    ObjectMapper codec = (ObjectMapper) p.getCodec();
+    boolean isXml = codec instanceof XmlMapper;
     JsonNode rootNode = p.getCodec().readTree(p);
+
     if (!rootNode.isEmpty()) {
       ArrayNode nodes = (rootNode.isArray() ? (ArrayNode) rootNode : new ArrayNode(null).add(rootNode));
       LicenseChoice licenseChoice = new LicenseChoice();
 
       for (JsonNode node : nodes) {
-        if (node.has("license")) {
-          processLicenseNode(p, node.get("license"), licenseChoice);
-        }
-        else {
-          JsonParser expressionParser = node.traverse(p.getCodec());
-          expressionParser.nextToken();
-          Expression expression = expressionDeserializer.deserialize(expressionParser, ctxt);
-          licenseChoice.setExpression(expression);
-          return licenseChoice;
+        if(isXml) {
+          if (node.has("license")) {
+            processLicenseNode(p, node.get("license"), licenseChoice);
+          }
+          else {
+            processExpression(p, node, licenseChoice, ctxt);
+          }
+        } else {
+          if (node.has("expression")) {
+            processExpression(p, node, licenseChoice, ctxt);
+          }
+          else {
+            processLicenseNode(p, node, licenseChoice);
+          }
         }
       }
       return licenseChoice;
@@ -68,4 +78,16 @@ public class LicenseDeserializer extends JsonDeserializer<LicenseChoice>
       licenseChoice.addLicense(licenseObj);
     }
   }
+
+  private void processExpression(
+      final JsonParser p,
+      JsonNode node,
+      LicenseChoice licenseChoice,
+      DeserializationContext ctxt) throws IOException
+  {
+    JsonParser expressionParser = node.traverse(p.getCodec());
+    expressionParser.nextToken();
+    Expression expression = expressionDeserializer.deserialize(expressionParser, ctxt);
+    licenseChoice.setExpression(expression);
+  }
 }
diff --git a/src/main/java/org/cyclonedx/util/deserializer/MetadataDeserializer.java b/src/main/java/org/cyclonedx/util/deserializer/MetadataDeserializer.java
index e7b3b42c8..2e4a2deda 100644
--- a/src/main/java/org/cyclonedx/util/deserializer/MetadataDeserializer.java
+++ b/src/main/java/org/cyclonedx/util/deserializer/MetadataDeserializer.java
@@ -34,6 +34,8 @@ public class MetadataDeserializer
 
   private final PropertiesDeserializer propertiesDeserializer = new PropertiesDeserializer();
 
+  private final LicenseDeserializer licenseDeserializer = new LicenseDeserializer();
+
   @Override
   public Metadata deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
     JsonNode node = jsonParser.getCodec().readTree(jsonParser);
@@ -82,8 +84,10 @@ public class MetadataDeserializer
     }
 
     if(node.has("licenses")) {
-      LicenseChoice license = mapper.convertValue(node.get("licenses"), LicenseChoice.class);
-      metadata.setLicenseChoice(license);
+      JsonParser licensesParser = node.get("licenses").traverse(jsonParser.getCodec());
+      licensesParser.nextToken();
+      LicenseChoice licenses = licenseDeserializer.deserialize(licensesParser, ctxt);
+      metadata.setLicenses(licenses);
     }
 
     if (node.has("timestamp")) {
diff --git a/src/main/java/org/cyclonedx/util/serializer/EvidenceSerializer.java b/src/main/java/org/cyclonedx/util/serializer/EvidenceSerializer.java
index 0f8d16a8e..314a2aab9 100644
--- a/src/main/java/org/cyclonedx/util/serializer/EvidenceSerializer.java
+++ b/src/main/java/org/cyclonedx/util/serializer/EvidenceSerializer.java
@@ -58,9 +58,9 @@ public class EvidenceSerializer
       gen.writeObjectField("callstack", evidence.getCallstack());
     }
 
-    if (evidence.getLicenseChoice() != null) {
+    if (evidence.getLicenses() != null) {
       gen.writeFieldName("licenses");
-      new LicenseChoiceSerializer().serialize(evidence.getLicenseChoice(), gen, serializerProvider);
+      new LicenseChoiceSerializer(isXml, version).serialize(evidence.getLicenses(), gen, serializerProvider);
     }
 
     if (CollectionUtils.isNotEmpty(evidence.getCopyright())) {
diff --git a/src/main/java/org/cyclonedx/util/serializer/LicenseChoiceSerializer.java b/src/main/java/org/cyclonedx/util/serializer/LicenseChoiceSerializer.java
index fb37a5f71..bba0d90e3 100644
--- a/src/main/java/org/cyclonedx/util/serializer/LicenseChoiceSerializer.java
+++ b/src/main/java/org/cyclonedx/util/serializer/LicenseChoiceSerializer.java
@@ -19,6 +19,7 @@
 package org.cyclonedx.util.serializer;
 
 import java.io.IOException;
+import java.lang.reflect.Field;
 
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.databind.SerializerProvider;
@@ -26,19 +27,28 @@ import com.fasterxml.jackson.databind.ser.std.StdSerializer;
 import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.cyclonedx.Version;
 import org.cyclonedx.model.License;
 import org.cyclonedx.model.LicenseChoice;
+import org.cyclonedx.model.Property;
+import org.cyclonedx.model.VersionFilter;
 import org.cyclonedx.model.license.Expression;
 
 public class LicenseChoiceSerializer
     extends StdSerializer<LicenseChoice>
 {
-  public LicenseChoiceSerializer() {
-    this(LicenseChoice.class);
+  private final boolean isXml;
+
+  private final Version version;
+
+  public LicenseChoiceSerializer(final boolean isXml, final Version version) {
+    this(LicenseChoice.class, isXml, version);
   }
 
-  public LicenseChoiceSerializer(final Class<LicenseChoice> t) {
+  public LicenseChoiceSerializer(final Class<LicenseChoice> t, boolean isXml, final Version version) {
     super(t);
+    this.isXml = isXml;
+    this.version = version;
   }
 
   @Override
@@ -50,26 +60,82 @@ public class LicenseChoiceSerializer
       return;
     }
 
-    if (gen instanceof ToXmlGenerator) {
-      serializeXml(licenseChoice, gen, provider);
+    if (isXml && gen instanceof ToXmlGenerator) {
+      ToXmlGenerator toXmlGenerator = (ToXmlGenerator) gen;
+      serializeXml(toXmlGenerator, licenseChoice);
     }
     else {
       serializeJson(licenseChoice, gen, provider);
     }
   }
 
-  private void serializeXml(
-      final LicenseChoice licenseChoice, final JsonGenerator gen, final SerializerProvider provider)
-      throws IOException
-  {
-      if (CollectionUtils.isNotEmpty(licenseChoice.getLicenses())) {
-        serializeLicensesToJsonArray(licenseChoice, gen, provider);
-      }
-      else if (licenseChoice.getExpression() != null &&
-          StringUtils.isNotBlank(licenseChoice.getExpression().getValue())) {
-        final ToXmlGenerator toXmlGenerator = (ToXmlGenerator) gen;
-        serializeExpressionToXml(licenseChoice, toXmlGenerator);
+    private void serializeXml(ToXmlGenerator toXmlGenerator, LicenseChoice lc) throws IOException {
+    if (CollectionUtils.isNotEmpty(lc.getLicenses())) {
+      toXmlGenerator.writeStartObject();
+      toXmlGenerator.writeFieldName("license");
+      toXmlGenerator.writeStartArray();
+      for (License l : lc.getLicenses()) {
+        serializeXmlAttributes(toXmlGenerator, l.getBomRef(), l.getAcknowledgement(), l);
+
+        if (StringUtils.isNotBlank(l.getId())) {
+          toXmlGenerator.writeStringField("id", l.getId());
+        }
+        else if (StringUtils.isNotBlank(l.getName())) {
+          toXmlGenerator.writeStringField("name", l.getName());
+        }
+
+        if (l.getLicensing() != null && shouldSerializeField(l, "licensing")) {
+          toXmlGenerator.writeObjectField("licensing", l.getLicensing());
+        }
+
+        if (l.getAttachmentText() != null) {
+          toXmlGenerator.writeObjectField("text", l.getAttachmentText());
+        }
+
+        if (StringUtils.isNotBlank(l.getUrl())) {
+          toXmlGenerator.writeStringField("url", l.getUrl());
+        }
+
+        if (CollectionUtils.isNotEmpty(l.getProperties()) && shouldSerializeField(l, "properties")) {
+          toXmlGenerator.writeFieldName("properties");
+          toXmlGenerator.writeStartObject();
+
+          for (Property property : l.getProperties()) {
+            toXmlGenerator.writeObjectField("property", property);
+          }
+          toXmlGenerator.writeEndObject();
+        }
+
+        toXmlGenerator.writeEndObject();
       }
+      toXmlGenerator.writeEndArray();
+      toXmlGenerator.writeEndObject();
+    }
+    else if (lc.getExpression() != null) {
+      serializeExpressionToXml(lc, toXmlGenerator);
+    }
+  }
+
+  private void serializeXmlAttributes(
+      final ToXmlGenerator toXmlGenerator,
+      final String bomRef,
+      final String acknowledgement,
+      final Object object) throws IOException
+  {
+    toXmlGenerator.writeStartObject();
+
+    if (StringUtils.isNotBlank(bomRef) && shouldSerializeField(object, "bomRef")) {
+      toXmlGenerator.setNextIsAttribute(true);
+      toXmlGenerator.writeFieldName("bom-ref");
+      toXmlGenerator.writeString(bomRef);
+      toXmlGenerator.setNextIsAttribute(false);
+    }
+    if (StringUtils.isNotBlank(acknowledgement) && shouldSerializeField(object, "acknowledgement")) {
+      toXmlGenerator.setNextIsAttribute(true);
+      toXmlGenerator.writeFieldName("acknowledgement");
+      toXmlGenerator.writeString(acknowledgement);
+      toXmlGenerator.setNextIsAttribute(false);
+    }
   }
 
   private void serializeJson(
@@ -85,59 +151,68 @@ public class LicenseChoiceSerializer
     }
   }
 
-  private void serializeLicensesToJsonArray(
-      final LicenseChoice licenseChoice, final JsonGenerator gen, final SerializerProvider provider)
-      throws IOException {
-    gen.writeStartArray();
-    for (License license : licenseChoice.getLicenses()) {
-      gen.writeStartObject();
-      provider.defaultSerializeField("license", license, gen);
-      gen.writeEndObject();
-    }
-    gen.writeEndArray();
-  }
-
   private void serializeExpressionToXml(
       final LicenseChoice licenseChoice, final ToXmlGenerator toXmlGenerator)
-      throws IOException {
+      throws IOException
+  {
     toXmlGenerator.writeStartObject();
     Expression expression = licenseChoice.getExpression();
     toXmlGenerator.writeFieldName("expression");
-
-    toXmlGenerator.writeStartObject();
-
-    if (StringUtils.isNotBlank(expression.getBomRef())) {
-      toXmlGenerator.setNextIsAttribute(true);
-      toXmlGenerator.writeFieldName("bom-ref");
-      toXmlGenerator.writeString(expression.getBomRef());
-      toXmlGenerator.setNextIsAttribute(false);
-    }
-    if (StringUtils.isNotBlank(expression.getAcknowledgement())) {
-      toXmlGenerator.setNextIsAttribute(true);
-      toXmlGenerator.writeFieldName("acknowledgement");
-      toXmlGenerator.writeString(expression.getAcknowledgement());
-      toXmlGenerator.setNextIsAttribute(false);
-    }
-
+    serializeXmlAttributes(toXmlGenerator, expression.getBomRef(), expression.getAcknowledgement(), expression);
     toXmlGenerator.setNextIsUnwrapped(true);
     toXmlGenerator.writeStringField("", expression.getValue());
     toXmlGenerator.writeEndObject();
     toXmlGenerator.writeEndObject();
   }
 
+  private void serializeLicensesToJsonArray(
+      final LicenseChoice licenseChoice, final JsonGenerator gen, final SerializerProvider provider)
+      throws IOException
+  {
+    gen.writeStartArray();
+    for (License license : licenseChoice.getLicenses()) {
+      gen.writeStartObject();
+      provider.defaultSerializeField("license", license, gen);
+      gen.writeEndObject();
+    }
+    gen.writeEndArray();
+  }
+
   private void serializeExpressionToJson(final LicenseChoice licenseChoice, final JsonGenerator gen)
       throws IOException {
     Expression expression = licenseChoice.getExpression();
     gen.writeStartArray();
     gen.writeStartObject();
     gen.writeStringField("expression", expression.getValue());
-    if (StringUtils.isNotBlank(expression.getAcknowledgement())) {
+    if (StringUtils.isNotBlank(expression.getAcknowledgement()) &&
+        shouldSerializeField(expression, "acknowledgement")) {
       gen.writeStringField("acknowledgement", expression.getAcknowledgement());
     }
-    if (StringUtils.isNotBlank(expression.getBomRef())) {
+    if (StringUtils.isNotBlank(expression.getBomRef()) && shouldSerializeField(expression, "bomRef")) {
       gen.writeStringField("bom-ref", expression.getBomRef());
     }
     gen.writeEndObject();
     gen.writeEndArray();
   }
+
+  private boolean shouldSerialize(Object obj) {
+    for (Field field : obj.getClass().getDeclaredFields()) {
+      VersionFilter filter = field.getAnnotation(VersionFilter.class);
+      if (filter != null && filter.value().getVersion() > version.getVersion()) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  private boolean shouldSerializeField(Object obj, String fieldName) {
+    try {
+      Field field = obj.getClass().getDeclaredField(fieldName);
+      VersionFilter filter = field.getAnnotation(VersionFilter.class);
+      return filter == null || filter.value().getVersion() <= version.getVersion();
+    } catch (NoSuchFieldException e) {
+      // If the field does not exist, assume it should be serialized
+      return true;
+    }
+  }
 }
diff --git a/src/main/java/org/cyclonedx/util/serializer/MetadataSerializer.java b/src/main/java/org/cyclonedx/util/serializer/MetadataSerializer.java
index c940f485d..c0c27033d 100644
--- a/src/main/java/org/cyclonedx/util/serializer/MetadataSerializer.java
+++ b/src/main/java/org/cyclonedx/util/serializer/MetadataSerializer.java
@@ -84,9 +84,9 @@ public class MetadataSerializer
       jsonGenerator.writeObjectField("supplier", metadata.getSupplier());
     }
 
-    if(metadata.getLicenseChoice() != null) {
+    if(metadata.getLicenses() != null) {
       jsonGenerator.writeFieldName("licenses");
-      new LicenseChoiceSerializer().serialize(metadata.getLicenseChoice(), jsonGenerator, serializerProvider);
+      new LicenseChoiceSerializer(isXml, version).serialize(metadata.getLicenses(), jsonGenerator, serializerProvider);
     }
 
     if(metadata.getProperties()!=null) {


Test patch:
diff --git a/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java b/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java
index 995e078ea..2d316e5e9 100644
--- a/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java
+++ b/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java
@@ -22,7 +22,14 @@ import com.fasterxml.jackson.databind.JsonNode;
 import org.apache.commons.io.IOUtils;
 import org.cyclonedx.generators.BomGeneratorFactory;
 import org.cyclonedx.generators.json.BomJsonGenerator;
+import org.cyclonedx.generators.xml.BomXmlGenerator;
 import org.cyclonedx.model.Bom;
+import org.cyclonedx.model.Component;
+import org.cyclonedx.model.License;
+import org.cyclonedx.model.LicenseChoice;
+import org.cyclonedx.model.Metadata;
+import org.cyclonedx.model.Service;
+import org.cyclonedx.model.license.Expression;
 import org.cyclonedx.parsers.JsonParser;
 import org.cyclonedx.parsers.XmlParser;
 import org.junit.jupiter.api.AfterEach;
@@ -41,7 +48,9 @@ import java.util.ArrayList;
 import java.util.stream.Stream;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class BomJsonGeneratorTest {
@@ -62,7 +71,7 @@ public class BomJsonGeneratorTest {
 
     @Test
     public void schema12GenerationTest() throws Exception {
-        Bom bom =  createCommonBom("/bom-1.2.xml");
+        Bom bom =  createCommonXmlBom("/bom-1.2.xml");
         BomJsonGenerator generator = BomGeneratorFactory.createJson(Version.VERSION_12, bom);
         assertEquals(Version.VERSION_12, generator.getSchemaVersion());
         File file = writeToFile(generator.toJsonString());
@@ -72,7 +81,7 @@ public class BomJsonGeneratorTest {
 
     @Test
     public void schema12JsonObjectGenerationTest() throws Exception {
-        Bom bom = createCommonBom("/bom-1.2.xml");
+        Bom bom = createCommonXmlBom("/bom-1.2.xml");
         BomJsonGenerator generator = BomGeneratorFactory.createJson(Version.VERSION_12, bom);
         JsonNode obj = generator.toJsonNode();
         assertNotNull(obj);
@@ -136,7 +145,7 @@ public class BomJsonGeneratorTest {
     public void testJsonGeneration(Version version, String bomXmlPath)
         throws Exception
     {
-        Bom bom = createCommonBom(bomXmlPath);
+        Bom bom = createCommonXmlBom(bomXmlPath);
         BomJsonGenerator generator = BomGeneratorFactory.createJson(version, bom);
 
         assertEquals(version, generator.getSchemaVersion());
@@ -161,7 +170,7 @@ public class BomJsonGeneratorTest {
 
     @Test
     public void schema14JBomLinkGenerationTest() throws Exception {
-        Bom bom = createCommonBom("/bom-1.4-bomlink.xml");
+        Bom bom = createCommonXmlBom("/bom-1.4-bomlink.xml");
         BomJsonGenerator generator = BomGeneratorFactory.createJson(Version.VERSION_14, bom);
         File file = writeToFile(generator.toJsonString());
         JsonParser parser = new JsonParser();
@@ -172,6 +181,190 @@ public class BomJsonGeneratorTest {
         assertEquals("urn:cdx:f08a6ccd-4dce-4759-bd84-c626675d60a7/1", bom2.getComponents().get(0).getExternalReferences().get(0).getUrl());
     }
 
+    @Test
+    public void testIssue408Regression_1_5() throws Exception {
+        Version version = Version.VERSION_15;
+        Bom bom = createCommonJsonBom("/regression/issue408-1.5.json");
+        assertLicenseInformation(bom, version);
+
+        BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom);
+        File loadedFile = writeToFile(generator.toXmlString());
+
+        XmlParser parser = new XmlParser();
+        assertTrue(parser.isValid(loadedFile, version));
+    }
+
+    @Test
+    public void testIssue408Regression_16To15() throws Exception {
+        Version version = Version.VERSION_15;
+        Bom bom = createCommonJsonBom("/regression/issue408.json");
+
+        BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom);
+        File loadedFile = writeToFile(generator.toXmlString());
+
+        XmlParser parser = new XmlParser();
+        assertTrue(parser.isValid(loadedFile, version));
+    }
+
+    @Test
+    public void testIssue408Regression_16To14() throws Exception {
+        Version version = Version.VERSION_14;
+        Bom bom = createCommonJsonBom("/regression/issue408.json");
+
+        BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom);
+        File loadedFile = writeToFile(generator.toXmlString());
+
+        XmlParser parser = new XmlParser();
+        assertTrue(parser.isValid(loadedFile, version));
+    }
+
+    @Test
+    public void testIssue408Regression() throws Exception {
+        Version version = Version.VERSION_16;
+        Bom bom = createCommonJsonBom("/regression/issue408.json");
+        assertLicenseInformation(bom, version);
+
+        BomJsonGenerator generator = BomGeneratorFactory.createJson(version, bom);
+        File loadedFile = writeToFile(generator.toJsonString());
+
+        JsonParser parser = new JsonParser();
+        assertTrue(parser.isValid(loadedFile, version));
+    }
+
+    @Test
+    public void testIssue408Regression_xmlToJson() throws Exception {
+        Version version = Version.VERSION_16;
+        Bom bom = createCommonXmlBom("/regression/issue408.xml");
+        assertLicenseInformation(bom, version);
+
+        BomJsonGenerator generator = BomGeneratorFactory.createJson(version, bom);
+        File loadedFile = writeToFile(generator.toJsonString());
+
+        JsonParser parser = new JsonParser();
+        assertTrue(parser.isValid(loadedFile, version));
+    }
+
+    private void assertLicenseInformation(Bom bom, Version version) {
+
+        //First Component
+        Component component = bom.getComponents().get(0);
+        assertNotNull(component);
+        assertNotNull(component.getLicenseChoice());
+        assertNotNull(component.getLicenses());
+        assertNotNull(component.getLicenses().getLicenses());
+        assertFalse(component.getLicenses().getLicenses().isEmpty());
+        assertNull(component.getLicenses().getExpression());
+
+        License license1 = component.getLicenses().getLicenses().get(0);
+        assertNotNull(license1);
+        assertNotNull(license1.getId());
+        assertNull(license1.getName());
+        if(version.getVersion() >= Version.VERSION_16.getVersion()) {
+            assertNotNull(license1.getAcknowledgement());
+        } else {
+            assertNull(license1.getAcknowledgement());
+        }
+        assertNotNull(license1.getBomRef());
+
+        License license2 = component.getLicenses().getLicenses().get(1);
+        assertNotNull(license2);
+        assertNotNull(license2.getName());
+        assertNull(license2.getId());
+        assertNull(license2.getAcknowledgement());
+        assertNull(license2.getBomRef());
+
+        //Second Component
+        Component component2 = bom.getComponents().get(1);
+        assertNotNull(component2);
+        assertNotNull(component2.getLicenseChoice());
+        assertNotNull(component2.getLicenses());
+        assertNull(component2.getLicenses().getLicenses());
+        assertNotNull(component2.getLicenses().getExpression());
+
+        Expression expression = component2.getLicenses().getExpression();
+        assertNotNull(expression.getValue());
+        if(version.getVersion() >= Version.VERSION_16.getVersion()) {
+            assertNotNull(expression.getAcknowledgement());
+        } else {
+            assertNull(expression.getAcknowledgement());
+        }
+        assertNotNull(expression.getBomRef());
+
+        //Third Component Evidence
+        Component component3 = bom.getComponents().get(2);
+        assertNotNull(component3);
+        LicenseChoice lcEvidence = component3.getEvidence().getLicenses();
+        assertNotNull(lcEvidence);
+        assertNotNull(lcEvidence.getLicenses());
+        assertFalse(lcEvidence.getLicenses().isEmpty());
+        assertNull(lcEvidence.getExpression());
+
+        License license4 = lcEvidence.getLicenses().get(0);
+        assertNotNull(license4);
+        assertNotNull(license4.getId());
+        assertNull(license4.getName());
+        assertNull(license4.getAcknowledgement());
+        assertNull(license4.getBomRef());
+        assertNotNull(license4.getUrl());
+
+        License license5 = lcEvidence.getLicenses().get(1);
+        assertNotNull(license5);
+        assertNotNull(license5.getId());
+        assertNull(license5.getName());
+        assertNull(license5.getAcknowledgement());
+        assertNull(license5.getBomRef());
+        assertNotNull(license5.getUrl());
+
+        //Services
+        Service service = bom.getServices().get(0);
+        assertNotNull(service);
+        LicenseChoice lcService = service.getLicenses();
+        assertNotNull(lcService);
+        assertNotNull(lcService.getLicenses());
+        assertFalse(lcService.getLicenses().isEmpty());
+        assertNull(lcService.getExpression());
+
+        License license6 = lcService.getLicenses().get(0);
+        assertNotNull(license6);
+        assertNull(license6.getId());
+        assertNotNull(license6.getName());
+        assertNull(license6.getAcknowledgement());
+        assertNull(license6.getBomRef());
+        assertNull(license6.getUrl());
+
+        License license7 = lcService.getLicenses().get(1);
+        assertNotNull(license7);
+        assertNull(license7.getId());
+        assertNotNull(license7.getName());
+        assertNull(license7.getAcknowledgement());
+        assertNull(license7.getBomRef());
+        assertNull(license7.getUrl());
+
+
+        //Metadata
+        Metadata metadata = bom.getMetadata();
+        assertNotNull(metadata);
+        assertNotNull(metadata.getLicenseChoice());
+        assertNotNull(metadata.getLicenses());
+        assertNotNull(metadata.getLicenses().getLicenses());
+        assertFalse(metadata.getLicenses().getLicenses().isEmpty());
+        assertNull(metadata.getLicenses().getExpression());
+
+        License license8 = metadata.getLicenses().getLicenses().get(0);
+        assertNotNull(license8);
+        assertNotNull(license8.getId());
+        assertNull(license8.getName());
+        assertNull(license8.getAcknowledgement());
+        assertNull(license8.getBomRef());
+
+        License license9 = metadata.getLicenses().getLicenses().get(1);
+        assertNotNull(license9);
+        assertNotNull(license9.getName());
+        assertNull(license9.getId());
+        assertNull(license9.getAcknowledgement());
+        assertNull(license9.getBomRef());
+    }
+
     private File writeToFile(String jsonString) throws Exception {
         try (FileWriter writer = new FileWriter(tempFile.getAbsolutePath())) {
             writer.write(jsonString);
@@ -179,9 +372,15 @@ public class BomJsonGeneratorTest {
         return tempFile;
     }
 
-    private Bom createCommonBom(String resource) throws Exception {
+    private Bom createCommonXmlBom(String resource) throws Exception {
         final byte[] bomBytes = IOUtils.toByteArray(this.getClass().getResourceAsStream(resource));
         XmlParser parser = new XmlParser();
         return parser.parse(bomBytes);
     }
+
+    private Bom createCommonJsonBom(String resource) throws Exception {
+        final byte[] bomBytes = IOUtils.toByteArray(this.getClass().getResourceAsStream(resource));
+        JsonParser parser = new JsonParser();
+        return parser.parse(bomBytes);
+    }
 }
diff --git a/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java b/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java
index 7a8c05e67..e30eb29f0 100644
--- a/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java
+++ b/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java
@@ -28,6 +28,9 @@ import org.cyclonedx.model.ExtensibleType;
 import org.cyclonedx.model.ExternalReference;
 import org.cyclonedx.model.License;
 import org.cyclonedx.model.LicenseChoice;
+import org.cyclonedx.model.Metadata;
+import org.cyclonedx.model.Service;
+import org.cyclonedx.model.license.Expression;
 import org.cyclonedx.parsers.JsonParser;
 import org.cyclonedx.parsers.XmlParser;
 import org.junit.jupiter.api.AfterEach;
@@ -125,7 +128,7 @@ public class BomXmlGeneratorTest {
 
     @Test
     public void schema12GenerationTest() throws Exception {
-        BomXmlGenerator generator = BomGeneratorFactory.createXml(Version.VERSION_12, createCommonBom("/bom-1.2.json"));
+        BomXmlGenerator generator = BomGeneratorFactory.createXml(Version.VERSION_12, createCommonJsonBom("/bom-1.2.json"));
         Document doc = generator.generate();
         testDocument(doc);
 
@@ -174,7 +177,7 @@ public class BomXmlGeneratorTest {
     public void testXmlGeneration(Version version, String bomXmlPath)
         throws Exception
     {
-        Bom bom = createCommonBom(bomXmlPath);
+        Bom bom = createCommonJsonBom(bomXmlPath);
         BomJsonGenerator generator = BomGeneratorFactory.createJson(version, bom);
 
         assertEquals(version, generator.getSchemaVersion());
@@ -291,7 +294,7 @@ public class BomXmlGeneratorTest {
 
     @Test
     public void schema14JBomLinkGenerationTest() throws Exception {
-        Bom bom = createCommonBom("/bom-1.4-bomlink.json");
+        Bom bom = createCommonJsonBom("/bom-1.4-bomlink.json");
         BomXmlGenerator generator = BomGeneratorFactory.createXml(Version.VERSION_14, bom);
         File file = writeToFile(generator.toXmlString());
         XmlParser parser = new XmlParser();
@@ -302,6 +305,191 @@ public class BomXmlGeneratorTest {
         assertEquals("urn:cdx:f08a6ccd-4dce-4759-bd84-c626675d60a7/1", bom2.getComponents().get(0).getExternalReferences().get(0).getUrl());
     }
 
+    @Test
+    public void testIssue408Regression_1_5() throws Exception {
+        Version version = Version.VERSION_15;
+        Bom bom = createCommonBomXml("/regression/issue408-1.5.xml");
+        assertLicenseInformation(bom, version);
+
+        BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom);
+        File loadedFile = writeToFile(generator.toXmlString());
+
+        XmlParser parser = new XmlParser();
+        assertTrue(parser.isValid(loadedFile, version));
+    }
+
+    @Test
+    public void testIssue408Regression_16To15() throws Exception {
+        Version version = Version.VERSION_15;
+        Bom bom = createCommonBomXml("/regression/issue408.xml");
+
+        BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom);
+        File loadedFile = writeToFile(generator.toXmlString());
+
+        XmlParser parser = new XmlParser();
+        assertTrue(parser.isValid(loadedFile, version));
+    }
+
+    @Test
+    public void testIssue408Regression_16To14() throws Exception {
+        Version version = Version.VERSION_14;
+        Bom bom = createCommonBomXml("/regression/issue408.xml");
+
+        BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom);
+        File loadedFile = writeToFile(generator.toXmlString());
+
+        XmlParser parser = new XmlParser();
+        assertTrue(parser.isValid(loadedFile, version));
+    }
+
+    @Test
+    public void testIssue408Regression() throws Exception {
+        Version version = Version.VERSION_16;
+        Bom bom = createCommonBomXml("/regression/issue408.xml");
+        assertLicenseInformation(bom, version);
+
+        BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom);
+        File loadedFile = writeToFile(generator.toXmlString());
+
+        XmlParser parser = new XmlParser();
+        assertTrue(parser.isValid(loadedFile, version));
+    }
+
+    @Test
+    public void testIssue408Regression_jsonToXml() throws Exception {
+        Version version = Version.VERSION_16;
+        Bom bom = createCommonJsonBom("/regression/issue408.json");
+        assertLicenseInformation(bom, version);
+
+        BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom);
+        File loadedFile = writeToFile(generator.toXmlString());
+
+        XmlParser parser = new XmlParser();
+        assertTrue(parser.isValid(loadedFile, version));
+    }
+
+    private void assertLicenseInformation(Bom bom, Version version) {
+
+        //First Component
+        Component component = bom.getComponents().get(0);
+        assertNotNull(component);
+        assertNotNull(component.getLicenseChoice());
+        assertNotNull(component.getLicenses());
+        assertNotNull(component.getLicenses().getLicenses());
+        assertFalse(component.getLicenses().getLicenses().isEmpty());
+        assertNull(component.getLicenses().getExpression());
+
+        License license1 = component.getLicenses().getLicenses().get(0);
+        assertNotNull(license1);
+        assertNotNull(license1.getId());
+        assertNull(license1.getName());
+
+        if(version.getVersion() >= Version.VERSION_16.getVersion()) {
+            assertNotNull(license1.getAcknowledgement());
+        } else {
+            assertNull(license1.getAcknowledgement());
+        }
+        assertNotNull(license1.getBomRef());
+
+        License license2 = component.getLicenses().getLicenses().get(1);
+        assertNotNull(license2);
+        assertNotNull(license2.getName());
+        assertNull(license2.getId());
+        assertNull(license2.getAcknowledgement());
+        assertNull(license2.getBomRef());
+
+        //Second Component
+        Component component2 = bom.getComponents().get(1);
+        assertNotNull(component2);
+        assertNotNull(component2.getLicenseChoice());
+        assertNotNull(component2.getLicenses());
+        assertNull(component2.getLicenses().getLicenses());
+        assertNotNull(component2.getLicenses().getExpression());
+
+        Expression expression = component2.getLicenses().getExpression();
+        assertNotNull(expression.getValue());
+        if(version.getVersion() >= Version.VERSION_16.getVersion()) {
+            assertNotNull(expression.getAcknowledgement());
+        } else {
+            assertNull(expression.getAcknowledgement());
+        }
+        assertNotNull(expression.getBomRef());
+
+        //Third Component Evidence
+        Component component3 = bom.getComponents().get(2);
+        assertNotNull(component3);
+        LicenseChoice lcEvidence = component3.getEvidence().getLicenses();
+        assertNotNull(lcEvidence);
+        assertNotNull(lcEvidence.getLicenses());
+        assertFalse(lcEvidence.getLicenses().isEmpty());
+        assertNull(lcEvidence.getExpression());
+
+        License license4 = lcEvidence.getLicenses().get(0);
+        assertNotNull(license4);
+        assertNotNull(license4.getId());
+        assertNull(license4.getName());
+        assertNull(license4.getAcknowledgement());
+        assertNull(license4.getBomRef());
+        assertNotNull(license4.getUrl());
+
+        License license5 = lcEvidence.getLicenses().get(1);
+        assertNotNull(license5);
+        assertNotNull(license5.getId());
+        assertNull(license5.getName());
+        assertNull(license5.getAcknowledgement());
+        assertNull(license5.getBomRef());
+        assertNotNull(license5.getUrl());
+
+        //Services
+        Service service = bom.getServices().get(0);
+        assertNotNull(service);
+        LicenseChoice lcService = service.getLicenses();
+        assertNotNull(lcService);
+        assertNotNull(lcService.getLicenses());
+        assertFalse(lcService.getLicenses().isEmpty());
+        assertNull(lcService.getExpression());
+
+        License license6 = lcService.getLicenses().get(0);
+        assertNotNull(license6);
+        assertNull(license6.getId());
+        assertNotNull(license6.getName());
+        assertNull(license6.getAcknowledgement());
+        assertNull(license6.getBomRef());
+        assertNull(license6.getUrl());
+
+        License license7 = lcService.getLicenses().get(1);
+        assertNotNull(license7);
+        assertNull(license7.getId());
+        assertNotNull(license7.getName());
+        assertNull(license7.getAcknowledgement());
+        assertNull(license7.getBomRef());
+        assertNull(license7.getUrl());
+
+
+        //Metadata
+        Metadata metadata = bom.getMetadata();
+        assertNotNull(metadata);
+        assertNotNull(metadata.getLicenseChoice());
+        assertNotNull(metadata.getLicenses());
+        assertNotNull(metadata.getLicenses().getLicenses());
+        assertFalse(metadata.getLicenses().getLicenses().isEmpty());
+        assertNull(metadata.getLicenses().getExpression());
+
+        License license8 = metadata.getLicenses().getLicenses().get(0);
+        assertNotNull(license8);
+        assertNotNull(license8.getId());
+        assertNull(license8.getName());
+        assertNull(license8.getAcknowledgement());
+        assertNull(license8.getBomRef());
+
+        License license9 = metadata.getLicenses().getLicenses().get(1);
+        assertNotNull(license9);
+        assertNotNull(license9.getName());
+        assertNull(license9.getId());
+        assertNull(license9.getAcknowledgement());
+        assertNull(license9.getBomRef());
+    }
+
     private File writeToFile(String xmlString) throws Exception {
         try (FileWriter writer = new FileWriter(tempFile.getAbsolutePath())) {
             writer.write(xmlString);
@@ -315,7 +503,7 @@ public class BomXmlGeneratorTest {
         return parser.parse(bomBytes);
     }
 
-    private Bom createCommonBom(String resource) throws Exception {
+    private Bom createCommonJsonBom(String resource) throws Exception {
         final byte[] bomBytes = IOUtils.toByteArray(this.getClass().getResourceAsStream(resource));
         JsonParser parser = new JsonParser();
         return parser.parse(bomBytes);
diff --git a/src/test/java/org/cyclonedx/parsers/AbstractParserTest.java b/src/test/java/org/cyclonedx/parsers/AbstractParserTest.java
index e49955402..ef60b0ce5 100644
--- a/src/test/java/org/cyclonedx/parsers/AbstractParserTest.java
+++ b/src/test/java/org/cyclonedx/parsers/AbstractParserTest.java
@@ -622,6 +622,7 @@ public class AbstractParserTest
     assertEquals(ServiceData.Flow.BI_DIRECTIONAL, data.get(2).getFlow());
     assertEquals("public", data.get(2).getClassification());
     assertNotNull(s.getLicense());
+    assertNotNull(s.getLicenses());
     assertEquals(1, s.getLicense().getLicenses().size());
     assertEquals("Partner license", s.getLicense().getLicenses().get(0).getName());
     assertEquals(2, s.getExternalReferences().size());
@@ -811,9 +812,11 @@ public class AbstractParserTest
     //License
     if (version.getVersion() > Version.VERSION_12.getVersion()) {
       assertNotNull(metadata.getLicenseChoice());
+      assertNotNull(metadata.getLicenses());
     }
     else {
       assertNull(metadata.getLicenseChoice());
+      assertNull(metadata.getLicenses());
     }
 
     //Tool
@@ -892,6 +895,7 @@ public class AbstractParserTest
 
     assertNull(metadata.getLifecycles());
     assertNull(metadata.getLicenseChoice());
+    assertNull(metadata.getLicenses());
     assertNull(metadata.getAuthors());
     assertNull(metadata.getComponent());
     assertNull(metadata.getManufacture());
diff --git a/src/test/java/org/cyclonedx/parsers/JsonParserTest.java b/src/test/java/org/cyclonedx/parsers/JsonParserTest.java
index 5ae86e52f..35be9b6a4 100644
--- a/src/test/java/org/cyclonedx/parsers/JsonParserTest.java
+++ b/src/test/java/org/cyclonedx/parsers/JsonParserTest.java
@@ -206,6 +206,7 @@ public class JsonParserTest
 
         //Assert Metadata License Choice
         assertEquals(2, bom.getMetadata().getLicenseChoice().getLicenses().size());
+        assertEquals(2, bom.getMetadata().getLicenses().getLicenses().size());
 
         // Dependencies
         assertEquals(2, bom.getDependencies().size());
@@ -261,7 +262,7 @@ public class JsonParserTest
         final Bom bom = getJsonBom("1.6/valid-license-id-1.6.json");
 
         assertNotNull(bom.getComponents());
-        LicenseChoice lc = bom.getComponents().get(0).getLicenseChoice();
+        LicenseChoice lc = bom.getComponents().get(0).getLicenses();
         assertNotNull(lc.getLicenses());
         assertEquals(1, lc.getLicenses().size());
 
@@ -276,7 +277,7 @@ public class JsonParserTest
         final Bom bom = getJsonBom("1.6/valid-license-expression-1.6.json");
 
         assertNotNull(bom.getComponents());
-        LicenseChoice lc = bom.getComponents().get(0).getLicenseChoice();
+        LicenseChoice lc = bom.getComponents().get(0).getLicenses();
         assertNotNull(lc.getExpression());
 
         Expression expression = lc.getExpression();
diff --git a/src/test/java/org/cyclonedx/parsers/XmlParserTest.java b/src/test/java/org/cyclonedx/parsers/XmlParserTest.java
index ba06af028..763b5c6b9 100644
--- a/src/test/java/org/cyclonedx/parsers/XmlParserTest.java
+++ b/src/test/java/org/cyclonedx/parsers/XmlParserTest.java
@@ -201,6 +201,7 @@ public class XmlParserTest
         assertEquals("An example application", c1.getDescription());
         assertEquals("Copyright Example Inc. All rights reserved.", c1.getCopyright());
         assertEquals("Apache-2.0", c1.getLicenseChoice().getLicenses().get(0).getId());
+        assertEquals("Apache-2.0", c1.getLicenses().getLicenses().get(0).getId());
         assertEquals(2, c1.getComponents().size());
     }
 
@@ -428,7 +429,7 @@ public class XmlParserTest
         final Bom bom = getXmlBom("1.6/valid-license-id-1.6.xml");
 
         assertNotNull(bom.getComponents());
-        LicenseChoice lc = bom.getComponents().get(0).getLicenseChoice();
+        LicenseChoice lc = bom.getComponents().get(0).getLicenses();
         assertNotNull(lc.getLicenses());
         assertEquals(1, lc.getLicenses().size());
 
@@ -443,7 +444,7 @@ public class XmlParserTest
         final Bom bom = getXmlBom("1.6/valid-license-expression-1.6.xml");
 
         assertNotNull(bom.getComponents());
-        LicenseChoice lc = bom.getComponents().get(0).getLicenseChoice();
+        LicenseChoice lc = bom.getComponents().get(0).getLicenses();
         assertNotNull(lc.getExpression());
 
         Expression expression = lc.getExpression();
diff --git a/src/test/resources/regression/issue408-1.5.json b/src/test/resources/regression/issue408-1.5.json
new file mode 100644
index 000000000..114cf87e5
--- /dev/null
+++ b/src/test/resources/regression/issue408-1.5.json
@@ -0,0 +1,70 @@
+{
+  "bomFormat": "CycloneDX",
+  "specVersion": "1.5",
+  "serialNumber": "urn:uuid:1624fa6f-aebe-4dba-8ead-f2c876c9b832",
+  "version": 1,
+  "metadata": {
+    "licenses": [
+      {
+        "id": "MIT"
+      },
+      {
+        "name": "MIT"
+      }
+    ]
+  },
+  "components": [
+    {
+      "type": "library",
+      "name": "test",
+      "licenses": [
+        {
+          "id": "MIT",
+          "bom-ref": "test-1"
+        },
+        {
+          "name": "MIT WITH Libtool-exception"
+        }
+      ]
+    },
+    {
+      "type": "library",
+      "name": "test",
+      "licenses": [
+        {
+          "expression": "CC0-1.0",
+          "bom-ref": "test"
+        }
+      ]
+    },
+    {
+      "type": "library",
+      "name": "test-1",
+      "evidence": {
+        "licenses": [
+          {
+            "id": "Apache-2.0",
+            "url": "http://www.apache.org/licenses/LICENSE-2.0"
+          },
+          {
+            "id": "LGPL-2.1-only",
+            "url": "https://opensource.org/licenses/LGPL-2.1"
+          }
+        ]
+      }
+    }
+  ],
+  "services": [
+    {
+      "name": "Stock ticker service",
+      "licenses": [
+        {
+          "name": "Partner license"
+        },
+        {
+          "name": "test"
+        }
+      ]
+    }
+  ]
+}
diff --git a/src/test/resources/regression/issue408-1.5.xml b/src/test/resources/regression/issue408-1.5.xml
new file mode 100644
index 000000000..0e01d71fb
--- /dev/null
+++ b/src/test/resources/regression/issue408-1.5.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<bom serialNumber="urn:uuid:7fec5e81-b204-4eec-a0d6-383dc39c4867" version="1" xmlns="http://cyclonedx.org/schema/bom/1.5">
+    <metadata>
+        <licenses>
+            <license>
+                <id>MIT</id>
+            </license>
+            <license>
+                <name>MIT</name>
+            </license>
+        </licenses>
+    </metadata>
+    <components>
+        <component type="library">
+            <name>test</name>
+            <licenses>
+                <license bom-ref="test-1">
+                    <id>MIT</id>
+                </license>
+                <license>
+                    <name>MIT WITH Libtool-exception</name>
+                </license>
+            </licenses>
+        </component>
+        <component type="library">
+            <name>test</name>
+            <licenses>
+                <expression bom-ref="test">CC0-1.0</expression>
+            </licenses>
+        </component>
+        <component type="library">
+            <name>test-1</name>
+            <evidence>
+                <licenses>
+                    <license>
+                        <id>Apache-2.0</id>
+                        <url>http://www.apache.org/licenses/LICENSE-2.0</url>
+                    </license>
+                    <license>
+                        <id>LGPL-2.1-only</id>
+                        <url>https://opensource.org/licenses/LGPL-2.1</url>
+                    </license>
+                </licenses>
+            </evidence>
+        </component>
+    </components>
+    <services>
+        <service bom-ref="b2a46a4b-8367-4bae-9820-95557cfe03a8">
+            <name>Stock ticker service</name>
+            <licenses>
+                <license>
+                    <name>Partner license</name>
+                </license>
+                <license>
+                    <name>test</name>
+                </license>
+            </licenses>
+        </service>
+    </services>
+</bom>
diff --git a/src/test/resources/regression/issue408.json b/src/test/resources/regression/issue408.json
new file mode 100644
index 000000000..368590ef4
--- /dev/null
+++ b/src/test/resources/regression/issue408.json
@@ -0,0 +1,76 @@
+{
+  "bomFormat": "CycloneDX",
+  "specVersion": "1.6",
+  "serialNumber": "urn:uuid:1624fa6f-aebe-4dba-8ead-f2c876c9b832",
+  "version": 1,
+  "metadata": {
+    "licenses": [
+      {
+        "id": "MIT"
+      },
+      {
+        "name": "MIT-test",
+        "properties": {
+          "name": "name",
+          "value": "value1"
+        }
+      }
+    ]
+  },
+  "components": [
+    {
+      "type": "library",
+      "name": "test",
+      "licenses": [
+        {
+          "id": "MIT",
+          "acknowledgement": "concluded",
+          "bom-ref": "test-1"
+        },
+        {
+          "name": "MIT WITH Libtool-exception"
+        }
+      ]
+    },
+    {
+      "type": "library",
+      "name": "test",
+      "licenses": [
+        {
+          "expression": "CC0-1.0",
+          "acknowledgement": "concluded",
+          "bom-ref": "test"
+        }
+      ]
+    },
+    {
+      "type": "library",
+      "name": "test-1",
+      "evidence": {
+        "licenses": [
+          {
+            "id": "Apache-2.0",
+            "url": "http://www.apache.org/licenses/LICENSE-2.0"
+          },
+          {
+            "id": "LGPL-2.1-only",
+            "url": "https://opensource.org/licenses/LGPL-2.1"
+          }
+        ]
+      }
+    }
+  ],
+  "services": [
+    {
+      "name": "Stock ticker service",
+      "licenses": [
+        {
+          "name": "Partner license"
+        },
+        {
+          "name": "test"
+        }
+      ]
+    }
+  ]
+}
diff --git a/src/test/resources/regression/issue408.xml b/src/test/resources/regression/issue408.xml
new file mode 100644
index 000000000..b75b1d93c
--- /dev/null
+++ b/src/test/resources/regression/issue408.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<bom serialNumber="urn:uuid:7fec5e81-b204-4eec-a0d6-383dc39c4867" version="1" xmlns="http://cyclonedx.org/schema/bom/1.6">
+    <metadata>
+        <licenses>
+            <license>
+                <id>MIT</id>
+            </license>
+            <license>
+                <name>MIT-test</name>
+                <properties>
+                    <property name="name">value1</property>
+                </properties>
+            </license>
+        </licenses>
+    </metadata>
+    <components>
+        <component type="library">
+            <name>test</name>
+            <licenses>
+                <license acknowledgement="concluded" bom-ref="test-1">
+                    <id>MIT</id>
+                </license>
+                <license>
+                    <name>MIT WITH Libtool-exception</name>
+                </license>
+            </licenses>
+        </component>
+        <component type="library">
+            <name>test</name>
+            <licenses>
+                <expression acknowledgement="concluded" bom-ref="test">CC0-1.0</expression>
+            </licenses>
+        </component>
+        <component type="library">
+            <name>test-1</name>
+            <evidence>
+                <licenses>
+                    <license>
+                        <id>Apache-2.0</id>
+                        <url>http://www.apache.org/licenses/LICENSE-2.0</url>
+                    </license>
+                    <license>
+                        <id>LGPL-2.1-only</id>
+                        <url>https://opensource.org/licenses/LGPL-2.1</url>
+                    </license>
+                </licenses>
+            </evidence>
+        </component>
+    </components>
+    <services>
+        <service bom-ref="b2a46a4b-8367-4bae-9820-95557cfe03a8">
+            <name>Stock ticker service</name>
+            <licenses>
+                <license>
+                    <name>Partner license</name>
+                </license>
+                <license>
+                    <name>test</name>
+                </license>
+            </licenses>
+        </service>
+    </services>
+</bom>