diff --git a/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java b/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java
index b92d33debc572d378ac575621a7c302af202c7a1..524d2d99972cbcb57a54edf5eea0027702f1577e 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java
@@ -392,7 +392,7 @@ long addUnallocFsBlockFilesParent(long fsObjId, String name) {
 				logger.log(Level.SEVERE, "Error - root directory for file system ID {0} not found", fsObjId);
 				return -1;
 			}
-			VirtualDirectory dir = caseDb.addVirtualDirectory(fsIdToRootDir.get(fsObjId), name, trans);
+			VirtualDirectory dir = caseDb.addVirtualDirectoryJNI(fsIdToRootDir.get(fsObjId), name, trans);
 			return dir.getId();
 		} catch (TskCoreException ex) {
 			logger.log(Level.SEVERE, "Error creating virtual directory " + name + " under file system ID " + fsObjId, ex);
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
index c548f023b69b4fed510812b6eac77f352540ac64..af4eb9b739a4c62b2b114aedd3f31796ac2c8af2 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
@@ -11144,7 +11144,7 @@ long addFileJNI(long parentObjId,
 
 		Statement queryStatement = null;
 		try {
-			transaction.acquireSingleUserCaseWriteLock();
+			acquireSingleUserCaseWriteLock();
 			CaseDbConnection connection = transaction.getConnection();
 
 			// Insert a row for the local/logical file into the tsk_objects table.
@@ -11256,6 +11256,7 @@ long addFileJNI(long parentObjId,
 			throw new TskCoreException("Failed to add file system file", ex);
 		} finally {
 			closeStatement(queryStatement);
+			releaseSingleUserCaseWriteLock();
 		}
 	}
 	
@@ -11274,7 +11275,7 @@ long addFileJNI(long parentObjId,
 	void addLayoutFileRangeJNI(long objId, long byteStart, long byteLen, 
 			long seq, CaseDbTransaction transaction) throws TskCoreException {
 		try {
-			transaction.acquireSingleUserCaseWriteLock();
+			acquireSingleUserCaseWriteLock();
 			CaseDbConnection connection = transaction.getConnection();
 			
 			PreparedStatement prepStmt = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_LAYOUT_FILE);
@@ -11286,8 +11287,134 @@ void addLayoutFileRangeJNI(long objId, long byteStart, long byteLen,
 			connection.executeUpdate(prepStmt);
 		} catch (SQLException ex) {
 			throw new TskCoreException("Error adding layout range to file with obj ID " + objId, ex);
+		} finally {
+			releaseSingleUserCaseWriteLock();
 		}
 	}
+	
+	/**
+	 * Adds a virtual directory to the database and returns a VirtualDirectory
+	 * object representing it.
+	 *
+	 * Make sure the connection in transaction is used for all database
+	 * interactions called by this method
+	 *
+	 * @param parentId      the ID of the parent, or 0 if NULL
+	 * @param directoryName the name of the virtual directory to create
+	 * @param transaction   the transaction in the scope of which the operation
+	 *                      is to be performed, managed by the caller
+	 *
+	 * @return a VirtualDirectory object representing the one added to the
+	 *         database.
+	 *
+	 * @throws TskCoreException
+	 */
+	public VirtualDirectory addVirtualDirectoryJNI(long parentId, String directoryName, CaseDbTransaction transaction) throws TskCoreException {
+		if (transaction == null) {
+			throw new TskCoreException("Passed null CaseDbTransaction");
+		}
+
+		acquireSingleUserCaseWriteLock();
+		ResultSet resultSet = null;
+		try {
+			// Get the parent path.
+			CaseDbConnection connection = transaction.getConnection();
+
+			String parentPath;
+			Content parent = this.getAbstractFileById(parentId, connection);
+			if (parent instanceof AbstractFile) {
+				if (isRootDirectory((AbstractFile) parent, transaction)) {
+					parentPath = "/";
+				} else {
+					parentPath = ((AbstractFile) parent).getParentPath() + parent.getName() + "/"; //NON-NLS
+				}
+			} else {
+				// The parent was either null or not an abstract file
+				parentPath = "/";
+			}
+
+			// Insert a row for the virtual directory into the tsk_objects table.
+			long newObjId = addObject(parentId, TskData.ObjectType.ABSTRACTFILE.getObjectType(), connection);
+
+			// Insert a row for the virtual directory into the tsk_files table.
+			// INSERT INTO tsk_files (obj_id, fs_obj_id, name, type, has_path, dir_type, meta_type,
+			// dir_flags, meta_flags, size, ctime, crtime, atime, mtime, md5, known, mime_type, parent_path, data_source_obj_id,extension)
+			// VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?)
+			PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE);
+			statement.clearParameters();
+			statement.setLong(1, newObjId);
+
+			// If the parent is part of a file system, grab its file system ID
+			if (0 != parentId) {
+				long parentFs = this.getFileSystemId(parentId, connection);
+				if (parentFs != -1) {
+					statement.setLong(2, parentFs);
+				} else {
+					statement.setNull(2, java.sql.Types.BIGINT);
+				}
+			} else {
+				statement.setNull(2, java.sql.Types.BIGINT);
+			}
+
+			// name
+			statement.setString(3, directoryName);
+
+			//type
+			statement.setShort(4, TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType());
+			statement.setShort(5, (short) 1);
+
+			//flags
+			final TSK_FS_NAME_TYPE_ENUM dirType = TSK_FS_NAME_TYPE_ENUM.DIR;
+			statement.setShort(6, dirType.getValue());
+			final TSK_FS_META_TYPE_ENUM metaType = TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR;
+			statement.setShort(7, metaType.getValue());
+
+			//allocated
+			final TSK_FS_NAME_FLAG_ENUM dirFlag = TSK_FS_NAME_FLAG_ENUM.ALLOC;
+			statement.setShort(8, dirFlag.getValue());
+			final short metaFlags = (short) (TSK_FS_META_FLAG_ENUM.ALLOC.getValue()
+					| TSK_FS_META_FLAG_ENUM.USED.getValue());
+			statement.setShort(9, metaFlags);
+
+			//size
+			statement.setLong(10, 0);
+
+			//  nulls for params 11-14
+			statement.setNull(11, java.sql.Types.BIGINT);
+			statement.setNull(12, java.sql.Types.BIGINT);
+			statement.setNull(13, java.sql.Types.BIGINT);
+			statement.setNull(14, java.sql.Types.BIGINT);
+
+			statement.setNull(15, java.sql.Types.VARCHAR); // MD5
+			statement.setByte(16, FileKnown.UNKNOWN.getFileKnownValue()); // Known
+			statement.setNull(17, java.sql.Types.VARCHAR); // MIME type	
+
+			// parent path
+			statement.setString(18, parentPath);
+
+			// data source object id (same as object id if this is a data source)
+			long dataSourceObjectId;
+			if (0 == parentId) {
+				dataSourceObjectId = newObjId;
+			} else {
+				dataSourceObjectId = getDataSourceObjectId(connection, parentId);
+			}
+			statement.setLong(19, dataSourceObjectId);
+
+			//extension, since this is not really file we just set it to null
+			statement.setString(20, null);
+			connection.executeUpdate(statement);
+
+			return new VirtualDirectory(this, newObjId, dataSourceObjectId, directoryName, dirType,
+					metaType, dirFlag, metaFlags, null, FileKnown.UNKNOWN,
+					parentPath);
+		} catch (SQLException e) {
+			throw new TskCoreException("Error creating virtual directory '" + directoryName + "'", e);
+		} finally {
+			closeResultSet(resultSet);
+			releaseSingleUserCaseWriteLock();
+		}
+	}	
 
 	/**
 	 * Stores a pair of object ID and its type
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java
index 496d6fb51fda54d4f9d7da0f1bbb16f7454974b2..6697a50ae0a44e570c086785a370ddf5f3e710e9 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java
@@ -414,15 +414,18 @@ long addImageInfo(long deviceObjId, List<String> imageFilePaths, String timeZone
 			JniDbHelper dbHelper = new JniDbHelper(skCase);
 			try {
 				dbHelper.beginTransaction();
+				long startTime = System.currentTimeMillis();
 				long tskAutoDbPointer = initializeAddImgNat(caseDbPointer, dbHelper, timezoneLongToShort(timeZone), false, false, false);
 				runOpenAndAddImgNat(tskAutoDbPointer, UUID.randomUUID().toString(), imageFilePaths.toArray(new String[0]), imageFilePaths.size(), timeZone);				
 				long id = finishAddImgNat(tskAutoDbPointer);
+				long endTime = System.currentTimeMillis();
+				System.out.println("### addImage time: " + (endTime - startTime) + " ms");
 				skCase.addDataSourceToHasChildrenMap();
 				return id;
 			} catch (TskDataException ex) {
 				throw new TskCoreException("Error adding image to case database", ex);
 			} finally {
-				dbHelper.commitTransaction();
+				dbHelper.commitTransaction(); // TODO - is this right?
 			}
 		}
 
@@ -507,7 +510,7 @@ public void run(String deviceId, String[] imageFilePaths, int sectorSize) throws
 				getTSKReadLock();
 				try {
 					long imageHandle = 0;
-
+					long startTime = System.currentTimeMillis();
 					synchronized (this) {
 						if (0 != tskAutoDbPointer) {
 							throw new TskCoreException("Add image process already started");
@@ -524,6 +527,8 @@ public void run(String deviceId, String[] imageFilePaths, int sectorSize) throws
 					if (imageHandle != 0) {
 						runAddImgNat(tskAutoDbPointer, deviceId, imageHandle, timeZone, imageWriterPath);
 					}
+					long endTime = System.currentTimeMillis();
+					System.out.println("### addImage time: " + (endTime - startTime) + " ms");
 				} finally {
 					releaseTSKReadLock();
 				}