diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
index 16df4a34f8a1e871b835676df9b201fe820fa7d4..8b68e7e8c8e131fe7a8b684416ead5b58e605305 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java
@@ -95,6 +95,7 @@
 import org.sqlite.SQLiteDataSource;
 import org.sqlite.SQLiteJDBCLoader;
 import org.sleuthkit.datamodel.ContentStream.ContentProvider;
+import org.sleuthkit.datamodel.TimelineManager.TimelineEventAddedEvent;
 
 /**
  * Represents the case database with methods that provide abstractions for
@@ -13940,6 +13941,7 @@ public static final class CaseDbTransaction {
 		// Score changes are stored as a map keyed by objId to prevent duplicates.
 		private Map<Long, ScoreChange> scoreChangeMap = new HashMap<>();
 		private List<Host> hostsAdded = new ArrayList<>();
+		private List<TimelineEventAddedEvent> timelineEvents = new ArrayList<>();
 		private List<OsAccount> accountsChanged = new ArrayList<>();
 		private List<OsAccount> accountsAdded = new ArrayList<>();
 		private List<TskEvent.MergedAccountsPair> accountsMerged = new ArrayList<>();
@@ -13987,6 +13989,16 @@ CaseDbConnection getConnection() {
 		void registerScoreChange(ScoreChange scoreChange) {
 			scoreChangeMap.put(scoreChange.getObjectId(), scoreChange);
 		}
+		
+		/**
+		 * Register timeline event to be fired when transaction finishes.
+		 * @param timelineEvent The timeline event.
+		 */
+		void registerTimelineEvent(TimelineEventAddedEvent timelineEvent) {
+			if (timelineEvent != null) {
+				timelineEvents.add(timelineEvent);
+			}
+		}
 
 		/**
 		 * Saves a host that has been added as a part of this transaction.
@@ -14085,6 +14097,11 @@ public void commit() throws TskCoreException {
 						sleuthkitCase.fireTSKEvent(new TskEvent.AggregateScoresChangedEvent(entry.getKey(), ImmutableSet.copyOf(entry.getValue())));
 					}
 				}
+				if (!timelineEvents.isEmpty()) {
+					for (TimelineEventAddedEvent evt : timelineEvents) {
+						sleuthkitCase.fireTSKEvent(evt);
+					}
+				}
 				if (!hostsAdded.isEmpty()) {
 					sleuthkitCase.fireTSKEvent(new TskEvent.HostsAddedTskEvent(hostsAdded));
 				}
diff --git a/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java b/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java
index cf560705d3ad91e16ead90f9087e334191a62ec2..2970a49deb581dd69a04b743ac563ea7ccf132d0 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/TimelineManager.java
@@ -48,6 +48,7 @@
 import static org.sleuthkit.datamodel.CollectionUtils.isNotEmpty;
 import static org.sleuthkit.datamodel.CommManagerSqlStringUtils.buildCSVString;
 import org.sleuthkit.datamodel.SleuthkitCase.CaseDbConnection;
+import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
 import static org.sleuthkit.datamodel.SleuthkitCase.escapeSingleQuotes;
 
 /**
@@ -790,6 +791,54 @@ private Optional<TimelineEvent> addOtherEventDesc(BlackboardArtifact artifact) t
 
 		return addArtifactEvent(evtWDesc, evtType, artifact);
 	}
+	
+	
+	/**
+	 * Adds a timeline event to the database in a transaction.
+	 * @param eventType The event type.
+	 * @param shortDesc The short description.
+	 * @param medDesc The medium description.
+	 * @param longDesc The long description.
+	 * @param dataSourceId The data source id of the event.
+	 * @param contentId The content id of the event.
+	 * @param artifactId The artifact id of the event (can be null).
+	 * @param time Unix epoch offset time of the event in seconds.
+	 * @param hashHit True if a hash hit.
+	 * @param tagged True if tagged.
+	 * @param trans The transaction.
+	 * @return The added event.
+	 * @throws TskCoreException 
+	 */
+	@Beta
+	public TimelineEvent addTimelineEvent(
+			TimelineEventType eventType, String shortDesc, String medDesc, String longDesc,
+			long dataSourceId, long contentId, Long artifactId, long time,
+			boolean hashHit, boolean tagged,
+			CaseDbTransaction trans
+	) throws TskCoreException {
+		caseDB.acquireSingleUserCaseWriteLock();
+		try {
+			Long descriptionID = addEventDescription(dataSourceId, contentId, artifactId,
+					longDesc, medDesc, shortDesc, hashHit, tagged, trans.getConnection());
+
+			if (descriptionID == null) {
+				descriptionID = getEventDescription(dataSourceId, contentId, artifactId, longDesc, trans.getConnection());
+			}
+			if (descriptionID != null) {
+				long eventID = addEventWithExistingDescription(time, eventType, descriptionID, trans.getConnection());
+				return new TimelineEvent(eventID, descriptionID, contentId, artifactId, time, eventType,
+						longDesc, medDesc, shortDesc, hashHit, tagged);
+			} else {
+				// GVDTODO
+				throw new TskCoreException(String.format("Failed to get event description"));
+			}
+		} catch (DuplicateException dupEx) {
+			logger.log(Level.SEVERE, "Attempt to make file event duplicate.", dupEx);
+			return null;
+		} finally {
+			caseDB.releaseSingleUserCaseWriteLock();
+		}
+	}
 
 	/**
 	 * Add an event of the given type from the given artifact to the database.