diff --git a/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java b/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java
index 18c09c20d8db47778709aa559bc2ca08904a472e..8497dc77569db7cbd4dfe3f96fae5d371f20a819 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java
@@ -150,6 +150,7 @@ private void addTables(Connection conn) throws TskCoreException {
 			createAnalysisResultsTables(stmt);
 			createTagTables(stmt);
 			createIngestTables(stmt);
+			createHostTables(stmt);
 			createAccountTables(stmt);
 			createEventTables(stmt);
 			createAttributeTables(stmt);
@@ -390,6 +391,15 @@ private void createIngestTables(Statement stmt) throws SQLException {
 				+ "FOREIGN KEY(ingest_module_id) REFERENCES ingest_modules(ingest_module_id) ON DELETE CASCADE);");
 	}
 	
+	private void createHostTables(Statement stmt) throws SQLException {
+
+		stmt.execute("CREATE TABLE tsk_hosts (id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
+				+ "name TEXT NOT NULL, " // host name
+				+ "status INTEGER DEFAULT 0, " // to indicate if the host was merged/deleted
+				+ "UNIQUE(name)) ");
+
+	}
+		
 	private void createAccountTables(Statement stmt) throws SQLException {
 		stmt.execute("CREATE TABLE account_types (account_type_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, "
 				+ "type_name TEXT UNIQUE NOT NULL, display_name TEXT NOT NULL)");
diff --git a/bindings/java/src/org/sleuthkit/datamodel/Host.java b/bindings/java/src/org/sleuthkit/datamodel/Host.java
new file mode 100644
index 0000000000000000000000000000000000000000..5ec09c99debbc6dbcbebaa622d92e94056bba7cf
--- /dev/null
+++ b/bindings/java/src/org/sleuthkit/datamodel/Host.java
@@ -0,0 +1,146 @@
+/*
+ * Sleuth Kit Data Model
+ *
+ * Copyright 2021-2021 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;
+
+import java.util.Objects;
+
+/**
+ *
+ * Encapsulates a host.
+ */
+public final class Host {
+
+	private final long id;
+	private final String name;
+	private HostStatus status;
+
+	Host(long id, String name) {
+		this(id, name, HostStatus.ACTIVE);
+	}
+
+	Host(long id, String name, HostStatus status) {
+		this.id = id;
+		this.name = name;
+		this.status = status;
+	}
+
+	/**
+	 * Gets the row id for the host.
+	 *
+	 * @return Row id.
+	 */
+	public long getId() {
+		return id;
+	}
+
+	/**
+	 * Gets the name for the host.
+	 *
+	 * @return Host name.
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * Gets the status for the host.
+	 *
+	 * @return Host status.
+	 */
+	public HostStatus getStatus() {
+		return status;
+	}
+	
+	/**
+	 * Sets the status for the host.
+	 *
+	 * @param status Host status.
+	 */
+	public void setStatus(HostStatus status) {
+		this.status = status;
+	}
+		
+	@Override
+	public int hashCode() {
+		int hash = 5;
+		hash = 67 * hash + (int) (this.id ^ (this.id >>> 32));
+		hash = 67 * hash + Objects.hashCode(this.name);
+		return hash;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj) {
+			return true;
+		}
+		if (obj == null) {
+			return false;
+		}
+		if (getClass() != obj.getClass()) {
+			return false;
+		}
+
+		final Host other = (Host) obj;
+		if (this.id != other.id) {
+			return false;
+		}
+
+		if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
+			return false;
+		}
+
+		return true;
+	}
+
+	/**
+	 * Encapsulates status of host row.
+	 */
+	public enum HostStatus {
+		ACTIVE(0, "Active"),
+		MERGED(1, "Merged"),
+		DELETED(2, "Deleted");
+		
+
+		private final int id;
+		private final String name;
+
+		HostStatus(int id, String name) {
+			this.id = id;
+			this.name = name;
+		}
+
+		public int getId() {
+			return id;
+		}
+
+		String getName() {
+			return name;
+		}
+
+		public static HostStatus fromID(int typeId) {
+			for (HostStatus type : HostStatus.values()) {
+				if (type.ordinal() == typeId) {
+					return type;
+				}
+			}
+			return null;
+		}
+	}
+	
+}
diff --git a/bindings/java/src/org/sleuthkit/datamodel/HostManager.java b/bindings/java/src/org/sleuthkit/datamodel/HostManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..f1fe3f49b91e7eb542bdd8b305025f13fb1d2862
--- /dev/null
+++ b/bindings/java/src/org/sleuthkit/datamodel/HostManager.java
@@ -0,0 +1,171 @@
+/*
+ * 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;
+
+import com.google.common.base.Strings;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.sleuthkit.datamodel.SleuthkitCase.CaseDbConnection;
+import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
+
+/**
+ * Responsible for creating/updating/retrieving Hosts.
+ *
+ */
+public final class HostManager {
+	
+	private static final Logger LOGGER = Logger.getLogger(HostManager.class.getName());
+
+	private final SleuthkitCase db;
+
+	/**
+	 * Construct a HostManager for the given SleuthkitCase.
+	 *
+	 * @param skCase The SleuthkitCase
+	 *
+	 */
+	HostManager(SleuthkitCase skCase) {
+		this.db = skCase;
+	}
+	
+	
+	/**
+	 * Get or create host with specified name.
+	 *
+	 * @param name	       Host name.
+	 * @param transaction Database transaction to use.
+	 *
+	 * @return Host with the specified name.
+	 *
+	 * @throws TskCoreException
+	 */
+	Host getOrCreateHost(String name, CaseDbTransaction transaction) throws TskCoreException  {
+		
+		// must have a name
+		if (Strings.isNullOrEmpty(name) ) {
+			throw new IllegalArgumentException("Host name is required.");
+		}
+
+		CaseDbConnection connection = transaction.getConnection();
+
+		// First search for host by name
+		Optional<Host> host = getHost(name, connection);
+		if (host.isPresent()) {
+			return host.get();
+		}
+
+		// couldn't find it, create a new host
+		return createHost(name, connection);
+	}
+	
+	/**
+	 * Get host with given name. 
+	 * 
+	 * @param name Host name to look for. 
+	 * @param connection Database connection to use.
+	 * 
+	 * @return Optional with host.  Optional.empty if no matching host is found.
+	 * 
+	 * @throws TskCoreException 
+	 */
+	Optional<Host> getHost(String name, CaseDbConnection connection ) throws TskCoreException {
+		
+		String queryString = "SELECT * FROM tsk_hosts"
+							+ " WHERE LOWER(name) = LOWER('" + name + "')";
+
+		try (Statement s = connection.createStatement();
+				ResultSet rs = connection.executeQuery(s, queryString)) {
+
+			if (!rs.next()) {
+				return Optional.empty();	// no match found
+			} else {
+				return Optional.of(new Host(rs.getLong("id"), rs.getString("name"), Host.HostStatus.fromID(rs.getInt("status"))));
+			}
+		} catch (SQLException ex) {
+			throw new TskCoreException(String.format("Error getting host with name = %s", name), ex);
+		}
+	}
+	
+
+	/**
+	 * Create a host with given name.
+	 * 
+	 * @param name Host name.
+	 * @param connection Database connection to use. 
+	 * @return Newly created host.
+	 * @throws TskCoreException 
+	 */
+	private Host createHost(String name, CaseDbConnection connection) throws TskCoreException {
+		db.acquireSingleUserCaseWriteLock();
+		try {
+			String hostInsertSQL = "INSERT INTO tsk_hosts(name) VALUES (?)"; // NON-NLS
+			PreparedStatement preparedStatement = connection.getPreparedStatement(hostInsertSQL, Statement.RETURN_GENERATED_KEYS);
+
+			preparedStatement.clearParameters();
+			preparedStatement.setString(1, name);
+
+			connection.executeUpdate(preparedStatement);
+
+			// Read back the row id
+			try (ResultSet resultSet = preparedStatement.getGeneratedKeys();) {
+				if (resultSet.next()) {
+					return new Host(resultSet.getLong(1), name); //last_insert_rowid()
+				} else {
+					throw new SQLException("Error executing  " + hostInsertSQL);
+				}
+			}
+		} catch (SQLException ex) {
+			LOGGER.log(Level.SEVERE, null, ex);
+			throw new TskCoreException(String.format("Error adding host with name = %s", name), ex);
+		} finally {
+			db.releaseSingleUserCaseWriteLock();
+		}
+	}
+	
+	/**
+	 * Get all hosts.
+	 * 
+	 * @return Collection of hosts.
+	 * @throws TskCoreException 
+	 */
+	Set<Host> getHosts() throws TskCoreException {
+		String queryString = "SELECT * FROM tsk_hosts";
+
+		Set<Host> hosts = new HashSet<>();
+		try (CaseDbConnection connection = this.db.getConnection();
+				Statement s = connection.createStatement();
+				ResultSet rs = connection.executeQuery(s, queryString)) {
+
+			while (rs.next()) {
+				hosts.add(new Host(rs.getLong("id"), rs.getString("name"), Host.HostStatus.fromID(rs.getInt("status"))));
+			}
+
+			return hosts;
+		} catch (SQLException ex) {
+			throw new TskCoreException(String.format("Error getting hosts"), ex);
+		}
+	}
+}
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
index 0ed5a405299f69681ecd1920a0dd0b27f4fd2c06..e534c08b2ab321558f9550d026552c4799b8a910 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
@@ -212,6 +212,7 @@ public class SleuthkitCase {
 	private CaseDbAccessManager dbAccessManager;
 	private TaggingManager taggingMgr;
 	private ScoringManager scoringManager;
+	private HostManager	hostManager;
 
 	private final Map<String, Set<Long>> deviceIdToDatasourceObjIdMap = new HashMap<>();
 
@@ -391,6 +392,7 @@ private void init() throws Exception {
 		dbAccessManager = new CaseDbAccessManager(this);
 		taggingMgr = new TaggingManager(this);
 		scoringManager = new ScoringManager(this);
+		hostManager = new HostManager(this);
 	}
 
 	/**
@@ -514,6 +516,17 @@ public ScoringManager getScoringManager() throws TskCoreException {
 		return scoringManager;
 	}
 	
+	/**
+	 * Gets the host manager for this case.
+	 *
+	 * @return The per case HostManager object.
+	 *
+	 * @throws org.sleuthkit.datamodel.TskCoreException
+	 */
+	public HostManager getHostManager() throws TskCoreException {
+		return hostManager;
+	}
+	
 	/**
 	 * Make sure the predefined artifact types are in the artifact types table.
 	 *
@@ -2242,14 +2255,22 @@ private CaseDbSchemaVersionNumber updateFromSchema8dot6toSchema8dot7(CaseDbSchem
 		acquireSingleUserCaseWriteLock();
 		try {
 			String dateDataType = "BIGINT";
+			String primaryKeyType = "BIGSERIAL";
 			if (this.dbType.equals(DbType.SQLITE)) {
 				dateDataType = "INTEGER";
+				primaryKeyType = "INTEGER";
 			}
 			statement.execute("ALTER TABLE data_source_info ADD COLUMN added_date_time "+ dateDataType);
 			statement.execute("ALTER TABLE data_source_info ADD COLUMN acquisition_tool_settings TEXT");
 			statement.execute("ALTER TABLE data_source_info ADD COLUMN acquisition_tool_name TEXT");
 			statement.execute("ALTER TABLE data_source_info ADD COLUMN acquisition_tool_version TEXT");
 
+			// create host table.
+			statement.execute("CREATE TABLE tsk_hosts (id " + primaryKeyType + " PRIMARY KEY, "
+				+ "name TEXT NOT NULL, " // host name
+				+ "status INTEGER DEFAULT 0, " // to indicate if the host was merged/deleted
+				+ "UNIQUE(name)) ");
+			
 			return new CaseDbSchemaVersionNumber(8, 7);
 
 		} finally {