diff --git a/bindings/java/doxygen/communications.dox b/bindings/java/doxygen/communications.dox
index ccc56c9bee0fa666c4b2064c504b4e3a5d08849f..111241292a9f2ab9748545c6584440f591c08486 100644
--- a/bindings/java/doxygen/communications.dox
+++ b/bindings/java/doxygen/communications.dox
@@ -76,11 +76,36 @@ The final step is to store the relationships between the accounts.  You can do t
 The source of the relationship can be a device account (for things like call logs and contacts) if you are unsure about the specific account (such as phone number) associated with the device. 
 
 
-As an example, you can refer to some code in Autopsy.  Such as:
-- [Android Text Messages] (https://github.com/sleuthkit/autopsy/blob/develop/InternalPythonModules/android/textmessage.py)
+As an example, you can refer to some code in Autopsy, such as:
 - [Email Module addArtifact()] (https://github.com/sleuthkit/autopsy/blob/develop/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java)
 
 
+\section jni_com_comm_artifacts_helper Communication Artifacts Helper
+An alternative to individually creating artifacts, accounts and relationships is to use the org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper.  CommunicationArtifactsHelper provides APIs that create the artifact, create accounts, and create relationships between the accounts, all with a single API call.
+
+\subsection jni_com_comm_artifacts_helper_create_helper Creating a Communications Artifacts Helper
+To use the communication artifacts helper, you must first create a new instance of the helper for each source file from which you are extracting communications artifacts. To create a helper, use the constructor org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper.CommunicationArtifactsHelper().
+ When creating the helper, you must specify the account type for the accounts that will be created by this instance of the helper. Addtionally, you may specify the "self" account identifier - i.e. the application specific account identifier for the owner of the device, if it is known.
+If the self account is not known, you may omit it, in which case the helper uses the Device account as proxy for the self account.    
+
+\subsection jni_com_comm_artifacts_helper_add_contact Adding Contacts
+Use the org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper.addContact() method to add contacts.  
+The helper creates a TSK_CONTACT artifact. It also creates contact accounts for each of the specified contact method, and finally creates relationships between the contact accounts and the self account.
+
+\subsection jni_com_comm_artifacts_helper_add_calllog Adding Call logs
+Use the org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper.addCalllog() method to add call log.
+The helper creates a TSK_CALLLOG artifact. It also creates accounts for the caller and each of the callees, if specified.  Finally it creates a relationship between the caller and each of the callees.
+
+\subsection jni_com_comm_artifacts_helper_add_message Adding Messages
+Use the org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper.addMessage() method to add a message.
+The helper creates a TSK_MESSAGE artifact. It also creates accounts for the sender and each of the recipients, if specified.  Finally it creates a relationship between the sender and each of the recipients.
+
+\subsection jni_com_comm_artifacts_helper_add_attachments Adding Attachments to message
+Use the org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper.addAttachments() method to add org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments to a message.
+
+As an example, you can refer to some code in Autopsy, such as:
+- [Android Text Messages] (https://github.com/sleuthkit/autopsy/blob/develop/InternalPythonModules/android/textmessage.py)
+- [Facebook messenger Messages] (https://github.com/sleuthkit/autopsy/blob/develop/InternalPythonModules/android/fbmessenger.py)
 
 \section jni_com_schema Database Schema
 
diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java
index 745599c240e3ca8bb2f90e04bb7c04bdedabc597..608123381cc53ad04dbdb90911b3875b90f3178e 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitJNI.java
@@ -35,6 +35,8 @@
 import java.util.UUID;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import org.apache.commons.lang3.StringUtils;
 import org.sleuthkit.datamodel.TskData.TSK_FS_ATTR_TYPE_ENUM;
 import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
@@ -49,6 +51,8 @@
  */
 public class SleuthkitJNI {
 
+	private static final Logger logger = Logger.getLogger(SleuthkitJNI.class.getName());
+	
 	/**
 	 * Lock to protect against the TSK data structures being closed while
 	 * another thread is in the C++ code. Do not use this lock after obtaining
@@ -177,10 +181,16 @@ private static String getDefaultCaseIdentifier() throws TskCoreException {
 		 * @param caseIdentifier Unique identifier for the case.
 		 * 
 		 * @return the case handle cache
+		 * 
+		 * @throws TskCoreException If there is no cache for this case.
 		 */
-		private static CaseHandles getCaseHandles(String caseIdentifier) {
+		private static CaseHandles getCaseHandles(String caseIdentifier) throws TskCoreException {
 			synchronized (cacheLock) {
-				return caseHandlesCache.get(caseIdentifier);
+				if (caseHandlesCache.containsKey(caseIdentifier)) {
+					return caseHandlesCache.get(caseIdentifier);
+				}
+				// If the CaseHandles object isn't in there, it should mean the case has been closed.
+				throw new TskCoreException("No entry for case " + caseIdentifier + " in cache. Case may have been closed");
 			}
 		}
 		
@@ -228,16 +238,20 @@ private static boolean isImageInAnyCache(long imgHandle) {
 		 * @param fsHandle   The file system handle in which the file lives.
 		 */
 		private static void addFileHandle(String caseIdentifier, long fileHandle, long fsHandle) {
-			synchronized (cacheLock) {
-				// Add to collection of open file handles.
-				getCaseHandles(caseIdentifier).fileHandleCache.add(fileHandle);
-
-				// Add to map of file system to file handles.
-				if (getCaseHandles(caseIdentifier).fileSystemToFileHandles.containsKey(fsHandle)) {
-					getCaseHandles(caseIdentifier).fileSystemToFileHandles.get(fsHandle).add(fileHandle);
-				} else {
-					getCaseHandles(caseIdentifier).fileSystemToFileHandles.put(fsHandle, new ArrayList<Long>(Arrays.asList(fileHandle)));
+			try {
+				synchronized (cacheLock) {
+					// Add to collection of open file handles.
+					getCaseHandles(caseIdentifier).fileHandleCache.add(fileHandle);
+
+					// Add to map of file system to file handles.
+					if (getCaseHandles(caseIdentifier).fileSystemToFileHandles.containsKey(fsHandle)) {
+						getCaseHandles(caseIdentifier).fileSystemToFileHandles.get(fsHandle).add(fileHandle);
+					} else {
+						getCaseHandles(caseIdentifier).fileSystemToFileHandles.put(fsHandle, new ArrayList<>(Arrays.asList(fileHandle)));
+					}
 				}
+			} catch (TskCoreException ex) {
+				logger.log(Level.WARNING, "Error caching file handle for case {0}", caseIdentifier);
 			}
 		}
 
@@ -251,7 +265,11 @@ private static void removeFileHandle(long fileHandle, SleuthkitCase skCase) {
 			synchronized (cacheLock) {
 				// Remove from collection of open file handles.
 				if (skCase != null) {
-					getCaseHandles(skCase.getCaseHandleIdentifier()).fileHandleCache.remove(fileHandle);
+					try {
+						getCaseHandles(skCase.getCaseHandleIdentifier()).fileHandleCache.remove(fileHandle);
+					} catch (TskCoreException ex) {
+						// If the call to getCaseHandles() failed, we've already cleared the cache.
+					}
 				} else {
 					// If we don't know what case the handle is from, delete the first one we find
 					for (String caseIdentifier:caseHandlesCache.keySet()) {
@@ -882,8 +900,10 @@ private static long openImage(String[] imageFiles, int sSize, boolean useCache,
 	 * @param skCase      The case the image belongs to.
 	 * @param imagePaths  The complete list of paths for the image.
 	 * @param imageHandle The open image handle from TSK.
+	 * 
+	 * @throws TskCoreException If the new image could not be added to the cache
 	 */
-	private static void cacheImageHandle(SleuthkitCase skCase, List<String> imagePaths, long imageHandle) {
+	private static void cacheImageHandle(SleuthkitCase skCase, List<String> imagePaths, long imageHandle) throws TskCoreException {
 		
 		// Construct the hash key from the image paths
 		StringBuilder keyBuilder = new StringBuilder();
diff --git a/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/CommunicationArtifactsHelper.java b/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/CommunicationArtifactsHelper.java
index 1ec6227ecbf10788d652845edb0a32e41a7b0886..0f3859bac70a2cc7185acca62f98e87e6f8abf78 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/CommunicationArtifactsHelper.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/blackboardutils/CommunicationArtifactsHelper.java
@@ -166,7 +166,7 @@ public CommunicationArtifactsHelper(SleuthkitCase caseDb,
 	}
 
 	/**
-	 * Constructs a AppDB parser helper for the given DB file.
+	 * Constructs a communications artifacts helper for the given source file.
 	 *
 	 * This constructor is for modules that have the application specific
 	 * account information for the device owner to create a 'self' account.