diff --git a/parsely/src/main/java/com/parsely/parselyandroid/ParselyMetadata.java b/parsely/src/main/java/com/parsely/parselyandroid/ParselyMetadata.java
deleted file mode 100644
index 805902ab..00000000
--- a/parsely/src/main/java/com/parsely/parselyandroid/ParselyMetadata.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package com.parsely.parselyandroid;
-
-import androidx.annotation.Nullable;
-
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Represents post metadata to be passed to Parsely tracking.
- *
- * This class is used to attach a metadata block to a Parse.ly pageview
- * request. Pageview metadata is only required for URLs not accessible over the
- * internet (i.e. app-only content) or if the customer is using an "in-pixel" integration.
- * Otherwise, metadata will be gathered by Parse.ly's crawling infrastructure.
- */
-public class ParselyMetadata {
- public ArrayList authors, tags;
- public String link, section, thumbUrl, title;
- public Calendar pubDate;
-
- /**
- * Create a new ParselyMetadata object.
- *
- * @param authors The names of the authors of the content. Up to 10 authors are accepted.
- * @param link A post's canonical url.
- * @param section The category or vertical to which this content belongs.
- * @param tags User-defined tags for the content. Up to 20 are allowed.
- * @param thumbUrl URL at which the main image for this content is located.
- * @param title The title of the content.
- * @param pubDate The date this piece of content was published.
- */
- public ParselyMetadata(
- @Nullable ArrayList authors,
- @Nullable String link,
- @Nullable String section,
- @Nullable ArrayList tags,
- @Nullable String thumbUrl,
- @Nullable String title,
- @Nullable Calendar pubDate
- ) {
- this.authors = authors;
- this.link = link;
- this.section = section;
- this.tags = tags;
- this.thumbUrl = thumbUrl;
- this.title = title;
- this.pubDate = pubDate;
- }
-
- /**
- * Turn this object into a Map
- *
- * @return a Map object representing the metadata.
- */
- public Map toMap() {
- Map output = new HashMap<>();
- if (this.authors != null) {
- output.put("authors", this.authors);
- }
- if (this.link != null) {
- output.put("link", this.link);
- }
- if (this.section != null) {
- output.put("section", this.section);
- }
- if (this.tags != null) {
- output.put("tags", this.tags);
- }
- if (this.thumbUrl != null) {
- output.put("thumb_url", this.thumbUrl);
- }
- if (this.title != null) {
- output.put("title", this.title);
- }
- if (this.pubDate != null) {
- output.put("pub_date_tmsp", this.pubDate.getTimeInMillis() / 1000);
- }
- return output;
- }
-}
-
diff --git a/parsely/src/main/java/com/parsely/parselyandroid/ParselyMetadata.kt b/parsely/src/main/java/com/parsely/parselyandroid/ParselyMetadata.kt
new file mode 100644
index 00000000..201b4eb5
--- /dev/null
+++ b/parsely/src/main/java/com/parsely/parselyandroid/ParselyMetadata.kt
@@ -0,0 +1,64 @@
+package com.parsely.parselyandroid
+
+import java.util.Calendar
+
+/**
+ * Represents post metadata to be passed to Parsely tracking.
+ *
+ *
+ * This class is used to attach a metadata block to a Parse.ly pageview
+ * request. Pageview metadata is only required for URLs not accessible over the
+ * internet (i.e. app-only content) or if the customer is using an "in-pixel" integration.
+ * Otherwise, metadata will be gathered by Parse.ly's crawling infrastructure.
+ */
+open class ParselyMetadata
+/**
+ * Create a new ParselyMetadata object.
+ *
+ * @param authors The names of the authors of the content. Up to 10 authors are accepted.
+ * @param link A post's canonical url.
+ * @param section The category or vertical to which this content belongs.
+ * @param tags User-defined tags for the content. Up to 20 are allowed.
+ * @param thumbUrl URL at which the main image for this content is located.
+ * @param title The title of the content.
+ * @param pubDate The date this piece of content was published.
+ */(
+ private val authors: List? = null,
+ @JvmField internal val link: String? = null,
+ private val section: String? = null,
+ private val tags: List? = null,
+ private val thumbUrl: String? = null,
+ private val title: String? = null,
+ private val pubDate: Calendar? = null
+) {
+ /**
+ * Turn this object into a Map
+ *
+ * @return a Map object representing the metadata.
+ */
+ open fun toMap(): Map? {
+ val output: MutableMap = HashMap()
+ if (authors != null) {
+ output["authors"] = authors
+ }
+ if (link != null) {
+ output["link"] = link
+ }
+ if (section != null) {
+ output["section"] = section
+ }
+ if (tags != null) {
+ output["tags"] = tags
+ }
+ if (thumbUrl != null) {
+ output["thumb_url"] = thumbUrl
+ }
+ if (title != null) {
+ output["title"] = title
+ }
+ if (pubDate != null) {
+ output["pub_date_tmsp"] = pubDate.timeInMillis / 1000
+ }
+ return output
+ }
+}
diff --git a/parsely/src/main/java/com/parsely/parselyandroid/ParselyVideoMetadata.java b/parsely/src/main/java/com/parsely/parselyandroid/ParselyVideoMetadata.java
deleted file mode 100644
index 874ead34..00000000
--- a/parsely/src/main/java/com/parsely/parselyandroid/ParselyVideoMetadata.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.parsely.parselyandroid;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Map;
-
-/**
- * ParselyMetadata for video content.
- */
-public class ParselyVideoMetadata extends ParselyMetadata {
-
- public int durationSeconds;
-
- /**
- * Create a new ParselyVideoMetadata object.
- *
- * @param authors The names of the authors of the video. Up to 10 authors are accepted.
- * @param videoId Unique identifier for the video. Required.
- * @param section The category or vertical to which this video belongs.
- * @param tags User-defined tags for the video. Up to 20 are allowed.
- * @param thumbUrl URL at which the main image for this video is located.
- * @param title The title of the video.
- * @param pubDate The date this video was published.
- * @param durationSeconds Duration of the video in seconds. Required.
- */
- public ParselyVideoMetadata(
- @Nullable ArrayList authors,
- @NonNull String videoId,
- @Nullable String section,
- @Nullable ArrayList tags,
- @Nullable String thumbUrl,
- @Nullable String title,
- @Nullable Calendar pubDate,
- @NonNull int durationSeconds
- ) {
- super(authors, videoId, section, tags, thumbUrl, title, pubDate);
- if (videoId == null) {
- throw new NullPointerException("videoId cannot be null");
- }
- this.durationSeconds = durationSeconds;
- }
-
- /**
- * Turn this object into a Map
- *
- * @return a Map object representing the metadata.
- */
- public Map toMap() {
- Map output = super.toMap();
- output.put("duration", this.durationSeconds);
- return output;
- }
-}
diff --git a/parsely/src/main/java/com/parsely/parselyandroid/ParselyVideoMetadata.kt b/parsely/src/main/java/com/parsely/parselyandroid/ParselyVideoMetadata.kt
new file mode 100644
index 00000000..4a877e31
--- /dev/null
+++ b/parsely/src/main/java/com/parsely/parselyandroid/ParselyVideoMetadata.kt
@@ -0,0 +1,40 @@
+package com.parsely.parselyandroid
+
+import java.util.Calendar
+
+/**
+ * ParselyMetadata for video content.
+ */
+class ParselyVideoMetadata
+/**
+ * Create a new ParselyVideoMetadata object.
+ *
+ * @param authors The names of the authors of the video. Up to 10 authors are accepted.
+ * @param videoId Unique identifier for the video. Required.
+ * @param section The category or vertical to which this video belongs.
+ * @param tags User-defined tags for the video. Up to 20 are allowed.
+ * @param thumbUrl URL at which the main image for this video is located.
+ * @param title The title of the video.
+ * @param pubDate The timestamp in milliseconds this video was published.
+ * @param durationSeconds Duration of the video in seconds. Required.
+ */(
+ authors: List? = null,
+ videoId: String,
+ section: String? = null,
+ tags: List? = null,
+ thumbUrl: String? = null,
+ title: String? = null,
+ pubDate: Calendar? = null,
+ @JvmField internal val durationSeconds: Int
+) : ParselyMetadata(authors, videoId, section, tags, thumbUrl, title, pubDate) {
+ /**
+ * Turn this object into a Map
+ *
+ * @return a Map object representing the metadata.
+ */
+ override fun toMap(): Map? {
+ val output = super.toMap()?.toMutableMap()
+ output?.put("duration", durationSeconds)
+ return output
+ }
+}
diff --git a/parsely/src/test/java/com/parsely/parselyandroid/EventsBuilderTest.kt b/parsely/src/test/java/com/parsely/parselyandroid/EventsBuilderTest.kt
index e76210c2..8f57335b 100644
--- a/parsely/src/test/java/com/parsely/parselyandroid/EventsBuilderTest.kt
+++ b/parsely/src/test/java/com/parsely/parselyandroid/EventsBuilderTest.kt
@@ -4,8 +4,6 @@ import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.MapAssert
import org.junit.Before
import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.RobolectricTestRunner
internal class EventsBuilderTest {
private lateinit var sut: EventsBuilder
diff --git a/parsely/src/test/java/com/parsely/parselyandroid/ParselyMetadataTest.kt b/parsely/src/test/java/com/parsely/parselyandroid/ParselyMetadataTest.kt
new file mode 100644
index 00000000..3bf8b61e
--- /dev/null
+++ b/parsely/src/test/java/com/parsely/parselyandroid/ParselyMetadataTest.kt
@@ -0,0 +1,71 @@
+package com.parsely.parselyandroid
+
+import java.util.Calendar
+import kotlin.time.Duration.Companion.seconds
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+
+class ParselyMetadataTest {
+
+ @Test
+ fun `given metadata with complete set of data, when converting to map, then the map is as expected`() {
+ // given
+ val sut = ParselyMetadata(
+ authors,
+ link,
+ section,
+ tags,
+ thumbUrl,
+ title,
+ pubDate
+ )
+
+ // when
+ val map = sut.toMap()
+
+ // then
+ assertThat(map).isEqualTo(expectedParselyMetadataMap)
+ }
+
+ @Test
+ fun `given video metadata with complete set of data, when converting to map, then the map is as expected`() {
+ // given
+ val duration = 12
+ val sut = ParselyVideoMetadata(
+ authors,
+ link,
+ section,
+ tags,
+ thumbUrl,
+ title,
+ pubDate,
+ duration
+ )
+
+ // when
+ val map = sut.toMap()
+
+ // then
+ assertThat(map).isEqualTo(expectedParselyMetadataMap + ("duration" to duration))
+ }
+
+ companion object {
+ val authors = arrayListOf("first author", "second author")
+ val link = "sample link"
+ val section = "sample section"
+ val tags = arrayListOf("first tag", "second tag")
+ val thumbUrl = "sample thumb url"
+ val title = "sample title"
+ val pubDate = Calendar.getInstance().apply { set(2023, 0, 1) }
+
+ val expectedParselyMetadataMap = mapOf(
+ "authors" to authors,
+ "link" to link,
+ "section" to section,
+ "tags" to tags,
+ "thumb_url" to thumbUrl,
+ "title" to title,
+ "pub_date_tmsp" to pubDate.timeInMillis / 1000
+ )
+ }
+}