diff --git a/bindings/java/src/org/sleuthkit/datamodel/TimelineEventTypes.java b/bindings/java/src/org/sleuthkit/datamodel/TimelineEventTypes.java
index 0cade5baa5a5d2e6e1a7b70fa442b2cbee1c2fba..6444791cbf280a30ffa749c84a99f8b32148a0db 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/TimelineEventTypes.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/TimelineEventTypes.java
@@ -142,7 +142,7 @@ public TimelineEventDescriptionWithTime makeEventDescription(BlackboardArtifact
 			// Get the waypoint list "start time"
             GeoTrackPoints pointsList;
 			try {
-			pointsList = BlackboardJsonAttrUtil.fromAttribute(attribute, GeoTrackPoints.class);;
+			pointsList = BlackboardJsonAttrUtil.fromAttribute(attribute, GeoTrackPoints.class);
             } catch (BlackboardJsonAttrUtil.InvalidJsonException ex) {
                 throw new TskCoreException("Unable to parse track points in TSK_GEO_TRACKPOINTS attribute", ex);
             }			
diff --git a/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/attributes/BlackboardJsonAttrUtil.java b/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/attributes/BlackboardJsonAttrUtil.java
new file mode 100755
index 0000000000000000000000000000000000000000..eea08024be1196c31c01fabde368514ccc4f09f4
--- /dev/null
+++ b/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/attributes/BlackboardJsonAttrUtil.java
@@ -0,0 +1,118 @@
+/*
+ * Sleuth Kit Data Model
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier <at> sleuthkit <dot> org
+ *
+ * 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.
+ */
+package org.sleuthkit.datamodel.blackboardutils.attributes;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+import org.sleuthkit.datamodel.BlackboardAttribute;
+
+/**
+ * A utility for converting between JSON and artifact attributes of value type
+ * TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.JSON.
+ */
+public final class BlackboardJsonAttrUtil {
+
+	/**
+	 * Creates an attribute of a given type with the string value set to an
+	 * object of type T serialized to JSON.
+	 *
+	 * @param <T>        The type of the attribute value object to be
+	 *                   serailized.
+	 * @param attrType   The type of attribute to create.
+	 * @param moduleName The name of the module creating the attribute.
+	 * @param attrValue  The attribute value object.
+	 *
+	 * @return The BlackboardAttribute object.
+	 */
+	public static <T> BlackboardAttribute toAttribute(BlackboardAttribute.Type attrType, String moduleName, T attrValue) {
+		if (attrType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.JSON) {
+			throw new IllegalArgumentException(String.format("Attribute type %s does not have value type BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.JSON", attrType.getTypeName()));
+		}
+		return new BlackboardAttribute(attrType, moduleName, (new Gson()).toJson(attrValue));
+	}
+
+	/**
+	 * Creates an object of type T from the JSON in the string value of a
+	 * BlackboardAttribute with a value type of
+	 * TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.JSON.
+	 *
+	 * @param <T>   The type of the object to be created from the JSON.
+	 * @param attr  The attribute.
+	 * @param clazz the class object for class T.
+	 *
+	 * @return The T object from the attribute.
+	 *
+	 * @throws InvalidJsonException Thrown the JSON in an artifact attribute
+	 *                              cannot be deserialized to an object of the
+	 *                              specified type.
+	 */
+	public static <T> T fromAttribute(BlackboardAttribute attr, Class<T> clazz) throws InvalidJsonException {
+		BlackboardAttribute.Type attrType = attr.getAttributeType();
+		if (attrType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.JSON) {
+			throw new IllegalArgumentException(String.format("Attribute type %s does not have value type BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.JSON", attrType.getTypeName()));
+		}
+		String json = attr.getValueString();
+		if (json == null || json.isEmpty()) {
+			throw new InvalidJsonException("The string value (JSON) of the attribute is null or empty");
+		}
+		try {
+			T object = (new Gson()).fromJson(json, clazz);
+			return object;
+		} catch (JsonSyntaxException ex) {
+			throw new InvalidJsonException("The string value (JSON) of the attribute is of an unexpected type", ex);
+		}
+	}
+
+	/**
+	 * Constructs an exception to be thrown when the JSON in an artifact
+	 * attribute cannot be deserialized to an object of the specified type.
+	 */
+	public static class InvalidJsonException extends Exception {
+
+		private static final long serialVersionUID = 1L;
+
+		/**
+		 * Constructs an exception thrown when JSON in an artifact attribute
+		 * cannot be deserialized to an object of the specified type.
+		 *
+		 * @param message An error message.
+		 */
+		public InvalidJsonException(String message) {
+			super(message);
+		}
+
+		/**
+		 * Constructs an exception thrown when JSON in an artifact attribute
+		 * cannot be deserialized to an object of the specified type.
+		 *
+		 * @param message An error message.
+		 * @param cause   An excception that caused this exception to be thrown.
+		 */
+		public InvalidJsonException(String message, Throwable cause) {
+			super(message, cause);
+		}
+	}
+
+	/**
+	 * Prevents instantiation of this utility class.
+	 */
+	private BlackboardJsonAttrUtil() {
+	}
+
+}
diff --git a/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/attributes/GeoTrackPoints.java b/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/attributes/GeoTrackPoints.java
new file mode 100755
index 0000000000000000000000000000000000000000..fb61d5a53bd4234cf2d023a3e1a51fd0d20b807c
--- /dev/null
+++ b/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/attributes/GeoTrackPoints.java
@@ -0,0 +1,232 @@
+/*
+ * Sleuth Kit Data Model
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier <at> sleuthkit <dot> org
+ *
+ * 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.
+ */
+package org.sleuthkit.datamodel.blackboardutils.attributes;
+
+import com.google.gson.annotations.SerializedName;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * A GeoTrackPoints object is a collection of TrackPoint objects. A TrackPoint
+ * represents a track point, which is a location in a geographic coordinate
+ * system with latitude, longitude and altitude (elevation) axes.
+ *
+ * GeoTrackPoints objects are designed to be used as the string value of the
+ * TSK_GEO_TRACKPOINTS attribute of a TSK_GPS_TRACK artifact. TSK_GPS_TRACK
+ * artifacts are used to record a track, or path, of a GPS-enabled device as a
+ * connected series of track points.
+ *
+ */
+public class GeoTrackPoints implements Iterable<GeoTrackPoints.TrackPoint> {
+
+	private final List<TrackPoint> pointList;
+
+	/**
+	 * Constructs an empty GeoTrackPoints object.
+	 */
+	public GeoTrackPoints() {
+		pointList = new ArrayList<>();
+	}
+
+	/**
+	 * Adds a track point to this list of track points.
+	 *
+	 * @param trackPoint A track point.
+	 */
+	public void addPoint(TrackPoint trackPoint) {
+		if (trackPoint == null) {
+			throw new IllegalArgumentException("addPoint was passed a null track point");
+		}
+
+		pointList.add(trackPoint);
+	}
+
+	@Override
+	public Iterator<TrackPoint> iterator() {
+		return pointList.iterator();
+	}
+
+	/**
+	 * Returns whether or not this list of track points is empty.
+	 *
+	 * @return True or false.
+	 */
+	public boolean isEmpty() {
+		return pointList.isEmpty();
+	}
+
+	/**
+	 * Gets the earliest track point timestamp in this list of track points, if
+	 * timestamps are present.
+	 *
+	 * @return The timestamp in milliseconds from the Java epoch of
+	 *         1970-01-01T00:00:00Z, may be null or zero.
+	 */
+	public Long getStartTime() {
+		List<TrackPoint> orderedPoints = getTimeOrderedPoints();
+		if (orderedPoints != null) {
+			for (TrackPoint point : orderedPoints) {
+				if (point.getTimeStamp() != null) {
+					return point.getTimeStamp();
+				}
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Gets the latest track point timestamp in this list of track points, if
+	 * timestamps are present.
+	 *
+	 * @return The timestamp in milliseconds from the Java epoch of
+	 *         1970-01-01T00:00:00Z, may be null or zero.
+	 */
+	public Long getEndTime() {
+		List<TrackPoint> orderedPoints = getTimeOrderedPoints();
+		if (orderedPoints != null) {
+			for (int index = orderedPoints.size() - 1; index >= 0; index--) {
+				TrackPoint point = orderedPoints.get(index);
+				if (point.getTimeStamp() != null) {
+					return point.getTimeStamp();
+				}
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Gets this list of track points as a list ordered by track point
+	 * timestamp.
+	 *
+	 * @return The ordered list of track points.
+	 */
+	private List<TrackPoint> getTimeOrderedPoints() {
+		return pointList.stream().sorted().collect(Collectors.toCollection(ArrayList::new));
+	}
+
+	/**
+	 * A representation of a track point, which is a location in a geographic
+	 * coordinate system with latitude, longitude and altitude (elevation) axes.
+	 */
+	public final static class TrackPoint extends GeoWaypoints.Waypoint implements Comparable<TrackPoint> {
+
+		@SerializedName("TSK_GEO_VELOCITY")
+		private final Double velocity;
+		@SerializedName("TSK_DISTANCE_FROM_HOMEPOINT")
+		private final Double distanceFromHomePoint;
+		@SerializedName("TSK_DISTANCE_TRAVELED")
+		private final Double distanceTraveled;
+		@SerializedName("TSK_DATETIME")
+		private final Long timestamp;
+
+		/**
+		 * Constructs a representation of a track point, which is a location in
+		 * a geographic coordinate system with latitude, longitude and altitude
+		 * (elevation) axes.
+		 *
+		 * @param latitude              The latitude of the track point.
+		 * @param longitude             The longitude of the track point.
+		 * @param altitude              The altitude of the track point, may be
+		 *                              null.
+		 * @param name                  The name of the track point, may be
+		 *                              null.
+		 * @param velocity              The velocity of the device at the track
+		 *                              point in meters per second, may be null.
+		 * @param distanceFromHomePoint	The distance of the track point in
+		 *                              meters from an established home point,
+		 *                              may be null.
+		 * @param distanceTraveled      The distance the device has traveled in
+		 *                              meters at the time this track point was
+		 *                              created, may be null.
+		 * @param timestamp             The timestamp of the track point as
+		 *                              milliseconds from the Java epoch of
+		 *                              1970-01-01T00:00:00Z, may be null.
+		 */
+		public TrackPoint(Double latitude,
+				Double longitude,
+				Double altitude,
+				String name,
+				Double velocity,
+				Double distanceFromHomePoint,
+				Double distanceTraveled,
+				Long timestamp) {
+			super(latitude, longitude, altitude, name);
+			this.velocity = velocity;
+			this.distanceFromHomePoint = distanceFromHomePoint;
+			this.distanceTraveled = distanceTraveled;
+			this.timestamp = timestamp;
+		}
+
+		/**
+		 * Gets the velocity of the device at this track point in meters per
+		 * second, if known.
+		 *
+		 * @return The velocity in meters/sec, may be null or zero.
+		 */
+		public Double getVelocity() {
+			return velocity;
+		}
+
+		/**
+		 * Gets the distance of this track point from an established home point,
+		 * if known.
+		 *
+		 * @return The distance in meters, may be null or zero.
+		 */
+		public Double getDistanceFromHomePoint() {
+			return distanceFromHomePoint;
+		}
+
+		/**
+		 * Gets the distance the device has traveled in meters at the time this
+		 * track point was created, if known.
+		 *
+		 * @return The distance traveled in meters, may be null or zero.
+		 */
+		public Double getDistanceTraveled() {
+			return distanceTraveled;
+		}
+
+		/**
+		 * Gets the timestamp of this track point as milliseconds from the Java
+		 * epoch of 1970-01-01T00:00:00Z, if known.
+		 *
+		 * @return The timestamp, may be null or zero.
+		 */
+		public Long getTimeStamp() {
+			return timestamp;
+		}
+
+		@Override
+		public int compareTo(TrackPoint otherTP) {
+			Long otherTimeStamp = otherTP.getTimeStamp();
+
+			if (timestamp == null && otherTimeStamp != null) {
+				return -1;
+			} else if (timestamp != null && otherTimeStamp == null) {
+				return 1;
+			} else {
+				return timestamp.compareTo(otherTP.getTimeStamp());
+			}
+		}
+	}
+
+}
diff --git a/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/attributes/GeoWaypoints.java b/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/attributes/GeoWaypoints.java
new file mode 100755
index 0000000000000000000000000000000000000000..4e5f3ca192dd5816e26927e8b9b30951f5b1f412
--- /dev/null
+++ b/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/attributes/GeoWaypoints.java
@@ -0,0 +1,153 @@
+/*
+ * Sleuth Kit Data Model
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier <at> sleuthkit <dot> org
+ *
+ * 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.
+ */
+package org.sleuthkit.datamodel.blackboardutils.attributes;
+
+import com.google.gson.annotations.SerializedName;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A GeoWaypoints object is a collection of Waypoint objects. A Waypoint object
+ * represents a waypoint for a GPS-enabled device with a navigation capability.
+ * Every waypoint is a location, possibly named, in a geographic coordinate
+ * system with latitude, longitude and altitude (elevation) axes.
+ *
+ * GeoWaypoints objects are designed to be used as the string value of the
+ * TSK_GEO_WAYPOINTS attribute of a TSK_GPS_ROUTE artifact. TSK_GPS_ROUTE
+ * artifacts are used to record one or more waypoints linked together as a route
+ * to be navigated from waypoint to waypoint.
+ */
+public class GeoWaypoints implements Iterable<GeoWaypoints.Waypoint> {
+
+	private final List<Waypoint> points;
+
+	/**
+	 * Constructs an empty GeoWaypoints object.
+	 */
+	public GeoWaypoints() {
+		points = new ArrayList<>();
+	}
+
+	/**
+	 * Adds a waypoint to this list of waypoints.
+	 *
+	 * @param wayPoint A waypoint.
+	 */
+	public void addPoint(Waypoint wayPoint) {
+		if (wayPoint == null) {
+			throw new IllegalArgumentException("addPoint was passed a null waypoint");
+		}
+
+		points.add(wayPoint);
+	}
+
+	/**
+	 * Returns whether or not this list of waypoints is empty.
+	 *
+	 * @return True or false.
+	 */
+	public boolean isEmpty() {
+		return points.isEmpty();
+	}
+
+	@Override
+	public Iterator<Waypoint> iterator() {
+		return points.iterator();
+	}
+
+	/**
+	 * A representation of a waypoint, which is a a location, possibly named, in
+	 * a geographic coordinate system with latitude, longitude and altitude
+	 * (elevation) axes.
+	 */
+	public static class Waypoint {
+
+		@SerializedName("TSK_GEO_LATITUDE")
+		private final Double latitude;
+		@SerializedName("TSK_GEO_LONGITUDE")
+		private final Double longitude;
+		@SerializedName("TSK_GEO_ALTITUDE")
+		private final Double altitude;
+		@SerializedName("TSK_NAME")
+		private final String name;
+
+		/**
+		 * Constructs a representation of a waypoint, which is a a location,
+		 * possibly named, in a geographic coordinate system with latitude,
+		 * longitude and altitude (elevation) axes.
+		 *
+		 * @param latitude  The latitude of the waypoint.
+		 * @param longitude The longitude of the waypoint.
+		 * @param altitude  The altitude of the waypoint, may be null.
+		 * @param name      The name of the waypoint, may be null.
+		 */
+		public Waypoint(Double latitude, Double longitude, Double altitude, String name) {
+			if (latitude == null) {
+				throw new IllegalArgumentException("Constructor was passed null latitude");
+			}
+
+			if (longitude == null) {
+				throw new IllegalArgumentException("Constructor was passed null longitude");
+			}
+
+			this.latitude = latitude;
+			this.longitude = longitude;
+			this.altitude = altitude;
+			this.name = name;
+		}
+
+		/**
+		 * Gets the latitude of this waypoint.
+		 *
+		 * @return The latitude.
+		 */
+		public Double getLatitude() {
+			return latitude;
+		}
+
+		/**
+		 * Gets the longitude of this waypoint.
+		 *
+		 * @return The longitude.
+		 */
+		public Double getLongitude() {
+			return longitude;
+		}
+
+		/**
+		 * Gets the altitude of this waypoint, if available.
+		 *
+		 * @return The altitude, may be null or zero.
+		 */
+		public Double getAltitude() {
+			return altitude;
+		}
+
+		/**
+		 * Gets the name of this waypoint, if available.
+		 *
+		 * @return	The name, may be null or empty.
+		 */
+		public String getName() {
+			return name;
+		}
+	}
+
+}