diff --git a/release_scripts/APIUpdate/.gitignore b/release_scripts/APIUpdate/.gitignore
new file mode 100644
index 0000000000..c41cc9e35e
--- /dev/null
+++ b/release_scripts/APIUpdate/.gitignore
@@ -0,0 +1 @@
+/target
\ No newline at end of file
diff --git a/release_scripts/APIUpdate/nbactions.xml b/release_scripts/APIUpdate/nbactions.xml
new file mode 100644
index 0000000000..e3b484bf66
--- /dev/null
+++ b/release_scripts/APIUpdate/nbactions.xml
@@ -0,0 +1,55 @@
+
+
+
+ run
+
+ jar
+
+
+ process-classes
+ org.codehaus.mojo:exec-maven-plugin:3.1.0:exec
+
+
+
+ ${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs}
+ --help
+ ${packageClassName}
+ java
+
+
+
+ debug
+
+ jar
+
+
+ process-classes
+ org.codehaus.mojo:exec-maven-plugin:3.1.0:exec
+
+
+ -agentlib:jdwp=transport=dt_socket,server=n,address=${jpda.address}
+ ${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs}
+ --help
+ ${packageClassName}
+ java
+ true
+
+
+
+ profile
+
+ jar
+
+
+ process-classes
+ org.codehaus.mojo:exec-maven-plugin:3.1.0:exec
+
+
+
+ ${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs}
+ ${packageClassName}
+ java
+ --help
+
+
+
diff --git a/release_scripts/APIUpdate/pom.xml b/release_scripts/APIUpdate/pom.xml
new file mode 100644
index 0000000000..1d38e4f5e2
--- /dev/null
+++ b/release_scripts/APIUpdate/pom.xml
@@ -0,0 +1,28 @@
+
+
+ 4.0.0
+ org.sleuthkit.autopsy.classpathsimplication
+ APIUpdate
+ 1.0
+ jar
+
+ UTF-8
+ 17
+ 17
+ org.sleuthkit.autopsy.classpathsimplication.apiupdate.APIUpdate
+
+
+
+ com.github.siom79.japicmp
+ japicmp
+ 0.17.2
+
+
+
+ commons-cli
+ commons-cli
+ 1.5.0
+
+
+
+
\ No newline at end of file
diff --git a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/classpathsimplication/apiupdate/APIUpdate.java b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/classpathsimplication/apiupdate/APIUpdate.java
new file mode 100644
index 0000000000..e387fd686c
--- /dev/null
+++ b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/classpathsimplication/apiupdate/APIUpdate.java
@@ -0,0 +1,137 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ */
+package org.sleuthkit.autopsy.classpathsimplication.apiupdate;
+
+import japicmp.cmp.JApiCmpArchive;
+import japicmp.cmp.JarArchiveComparator;
+import japicmp.cmp.JarArchiveComparatorOptions;
+import japicmp.model.JApiClass;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.MessageFormat;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import org.apache.commons.cli.ParseException;
+import org.sleuthkit.autopsy.classpathsimplication.apiupdate.CLIProcessor.CLIArgs;
+
+/**
+ *
+ * @author gregd
+ */
+public class APIUpdate {
+
+ public static void main(String[] args) {
+ args = "-c C:\\Users\\gregd\\Documents\\Source\\autopsy\\build\\cluster\\modules -p C:\\Users\\gregd\\Desktop\\prevVers -cv 4.21.0 -pv 4.20.0".split(" ");
+ CLIArgs cliArgs;
+ try {
+ cliArgs = CLIProcessor.parseCli(args);
+ if (cliArgs.isIsHelp()) {
+ CLIProcessor.printHelp(null);
+ System.exit(0);
+ }
+ } catch (ParseException ex) {
+ CLIProcessor.printHelp(ex);
+ System.exit(-1);
+ return;
+ }
+
+ for (String commonJarFileName : getCommonJars(cliArgs.getPreviousVersPath(), cliArgs.getCurrentVersPath())) {
+// getComparison(
+// cliArgs.getPreviousVersion(),
+// cliArgs.getCurrentVersion(),
+// cliArgs.getPreviousVersPath().toPath().resolve(commonJarFileName).toFile(),
+// cliArgs.getCurrentVersPath().toPath().resolve(commonJarFileName).toFile());
+ try {
+ Set pubPackages = getPublicPackages(cliArgs.getPreviousVersPath().toPath().resolve(commonJarFileName).toFile());
+ System.out.println(pubPackages);
+ } catch (IOException ex) {
+ Logger.getLogger(APIUpdate.class.getName()).log(Level.SEVERE, null, ex);
+ } catch (IllegalStateException ex) {
+ Logger.getLogger(APIUpdate.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ }
+
+ private static final FileFilter JAR_FILTER
+ = (File f) -> f.isFile() && (f.getName().toLowerCase().endsWith(".jar") || f.getName().toLowerCase().endsWith(".nbm"));
+
+ private static List getCommonJars(File prevDir, File currDir) {
+ Set prevJars = getJars(prevDir);
+ Set currJars = getJars(currDir);
+
+ Set commonJars = new HashSet<>(prevJars);
+ commonJars.retainAll(currJars);
+
+ // TODO how to handle different
+ return commonJars.stream().sorted().collect(Collectors.toList());
+ }
+
+ private static Set getJars(File dir) {
+ return Stream.of(dir.listFiles(JAR_FILTER))
+ .map(f -> f.getName())
+ .collect(Collectors.toSet());
+ }
+
+ private static Set getPublicPackages(File jarFile) throws IOException, IllegalStateException {
+ ZipFile zipFile = new ZipFile(jarFile);
+
+ Enumeration extends ZipEntry> entries = zipFile.entries();
+
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ if ("META-INF/MANIFEST.MF".equalsIgnoreCase(entry.getName())) {
+ InputStream stream = zipFile.getInputStream(entry);
+ Manifest manifest = new Manifest(stream);
+ Attributes attributes = manifest.getMainAttributes();
+ String publicPackageStr = attributes.getValue("OpenIDE-Module-Public-Packages");
+ if (publicPackageStr == null) {
+ throw new IllegalStateException(MessageFormat.format("Manifest for {0} does not have key of 'OpenIDE-Module-Public-Packages'", jarFile.getAbsolutePath()));
+ } else {
+ return Stream.of(publicPackageStr.split(","))
+ .map(String::trim)
+ .map(str -> str.endsWith(".*") ? str.substring(0, str.length() - 2) : str)
+ .collect(Collectors.toSet());
+ }
+ }
+ }
+
+ throw new FileNotFoundException("Could not find MANIFEST.MF in " + jarFile.getAbsolutePath());
+ }
+
+ private static void getComparison(String prevVersion, String curVersion, File prevJar, File curJar) {
+ JarArchiveComparatorOptions comparatorOptions = new JarArchiveComparatorOptions();
+ comparatorOptions.getIgnoreMissingClasses().setIgnoreAllMissingClasses(true);
+ JarArchiveComparator jarArchiveComparator = new JarArchiveComparator(comparatorOptions);
+ List jApiClasses = jarArchiveComparator.compare(
+ new JApiCmpArchive(prevJar, prevVersion),
+ new JApiCmpArchive(curJar, curVersion)
+ );
+ System.out.println("Comparing " + prevJar.getName());
+ System.out.println(jApiClasses);
+ }
+
+ private static void mainRun() {
+
+ // get public API diff's, for each jar
+ // limit to public packages
+ // one of the following:
+ // generate text output of difference
+ // update version numbers in manifest file/references accordingly
+ }
+
+}
diff --git a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/classpathsimplication/apiupdate/CLIProcessor.java b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/classpathsimplication/apiupdate/CLIProcessor.java
new file mode 100644
index 0000000000..0ac99e73ed
--- /dev/null
+++ b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/classpathsimplication/apiupdate/CLIProcessor.java
@@ -0,0 +1,166 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package org.sleuthkit.autopsy.classpathsimplication.apiupdate;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+/**
+ *
+ * @author gregd
+ */
+public class CLIProcessor {
+
+ static Option PREV_VERS_PATH_OPT = Option.builder()
+ .argName("path")
+ .desc("The path to the previous version jar files")
+ .hasArg(true)
+ .longOpt("prev-path")
+ .option("p")
+ .required(true)
+ .build();
+
+ static Option CUR_VERS_PATH_OPT = Option.builder()
+ .argName("path")
+ .desc("The path to the current version jar files")
+ .hasArg(true)
+ .longOpt("curr-path")
+ .option("c")
+ .required(true)
+ .build();
+
+ static Option PREV_VERS_OPT = Option.builder()
+ .argName("version")
+ .desc("The previous version number")
+ .hasArg(true)
+ .longOpt("prev-version")
+ .option("pv")
+ .required(true)
+ .build();
+
+ static Option CUR_VERS_OPT = Option.builder()
+ .argName("version")
+ .desc("The current version number")
+ .hasArg(true)
+ .longOpt("curr-version")
+ .option("cv")
+ .required(true)
+ .build();
+
+ static List