diff --git a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/APIDiff.java b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/APIDiff.java
index 1ccdd5d82e06381339e8cfcc4caef31087db1548..2f990d2a38ad158fb4ee237f4f5773a9cac795de 100644
--- a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/APIDiff.java
+++ b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/APIDiff.java
@@ -7,6 +7,7 @@
 import japicmp.cmp.JApiCmpArchive;
 import japicmp.cmp.JarArchiveComparator;
 import japicmp.cmp.JarArchiveComparatorOptions;
+import japicmp.config.Options;
 import japicmp.model.JApiClass;
 import java.io.File;
 import java.io.FileFilter;
@@ -44,7 +45,7 @@ private static Set<String> getJars(File dir) {
                 .collect(Collectors.toSet());
     }
 
-    static Set<String> getPublicPackages(File jarFile) throws IOException, IllegalStateException {
+    private static Set<String> getPublicPackages(File jarFile) throws IOException, IllegalStateException {
         String publicPackageStr = ManifestLoader.loadFromJar(jarFile).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()));
@@ -56,15 +57,107 @@ static Set<String> getPublicPackages(File jarFile) throws IOException, IllegalSt
         }
     }
 
-    private static void getComparison(String prevVersion, String curVersion, File prevJar, File curJar) {
+    static void getComparison(String prevVersion, String curVersion, File prevJar, File curJar) throws IOException {
         JarArchiveComparatorOptions comparatorOptions = new JarArchiveComparatorOptions();
+        //comparatorOptions.setAccessModifier(AccessModifier.);
         comparatorOptions.getIgnoreMissingClasses().setIgnoreAllMissingClasses(true);
         JarArchiveComparator jarArchiveComparator = new JarArchiveComparator(comparatorOptions);
         List<JApiClass> jApiClasses = jarArchiveComparator.compare(
                 new JApiCmpArchive(prevJar, prevVersion),
                 new JApiCmpArchive(curJar, curVersion)
         );
+
+        Set<String> prevPublicApiPackages = getPublicPackages(prevJar);
+        Set<String> curPublicApiPackages = getPublicPackages(curJar);
+
+        // TODO handle diff in this list
+        Set<String> allPublicApiPackages = new HashSet<>();
+        allPublicApiPackages.addAll(prevPublicApiPackages);
+        allPublicApiPackages.addAll(curPublicApiPackages);
+        jApiClasses = jApiClasses.stream()
+                .filter(cls -> allPublicApiPackages.contains(cls.getNewClass().or(cls.getOldClass()).get().getPackageName()))
+                .collect(Collectors.toList());
+
+        Options options = Options.newDefault();
+        options.setOutputOnlyModifications(true);
+
         System.out.println("Comparing " + prevJar.getName());
-        System.out.println(jApiClasses);
+        ChangeOutputGenerator stdoutOutputGenerator = new ChangeOutputGenerator(options, jApiClasses);
+        String output = stdoutOutputGenerator.generate();
+        System.out.println(output);
     }
+
+    private static void generateOutput(Options options, List<JApiClass> jApiClasses, JarArchiveComparator jarArchiveComparator) {
+//        for (JApiClass cls: jApiClasses) {
+//            cls.is
+//        }
+//        
+        
+//        if (options.isSemanticVersioning()) {
+//            SemverOut semverOut = new SemverOut(options, jApiClasses);
+//            String output = semverOut.generate();
+//            System.out.println(output);
+//            return;
+//        }
+//        if (options.getXmlOutputFile().isPresent() || options.getHtmlOutputFile().isPresent()) {
+//            SemverOut semverOut = new SemverOut(options, jApiClasses);
+//            XmlOutputGeneratorOptions xmlOutputGeneratorOptions = new XmlOutputGeneratorOptions();
+//            xmlOutputGeneratorOptions.setCreateSchemaFile(true);
+//            xmlOutputGeneratorOptions.setSemanticVersioningInformation(semverOut.generate());
+//            XmlOutputGenerator xmlGenerator = new XmlOutputGenerator(jApiClasses, options, xmlOutputGeneratorOptions);
+//            try (XmlOutput xmlOutput = xmlGenerator.generate()) {
+//                XmlOutputGenerator.writeToFiles(options, xmlOutput);
+//            } catch (Exception e) {
+//                throw new JApiCmpException(JApiCmpException.Reason.IoException, "Could not close output streams: " + e.getMessage(), e);
+//            }
+//        }
+//        StdoutOutputGenerator stdoutOutputGenerator = new StdoutOutputGenerator(options, jApiClasses);
+//        String output = stdoutOutputGenerator.generate();
+//        System.out.println(output);
+
+//        if (options.isErrorOnBinaryIncompatibility()
+//                || options.isErrorOnSourceIncompatibility()
+//                || options.isErrorOnExclusionIncompatibility()
+//                || options.isErrorOnModifications()
+//                || options.isErrorOnSemanticIncompatibility()) {
+//            IncompatibleErrorOutput errorOutput = new IncompatibleErrorOutput(options, jApiClasses, jarArchiveComparator);
+//            errorOutput.generate();
+//        }
+    }
+
+    
+    
+//    enum ChangeType { CHANGE, ADD, REMOVE }
+//    
+//    public class ClassChangeDTO {
+//        private final String packageStr;
+//        private final String fullyQualifiedClassName;
+//        private final ChangeType changeType;
+//        
+//        private final ClassDataDTO prevClassRecord;
+//        private final ClassDataDTO currClassRecord;
+//        
+//        
+//    }
+//    
+//    public class ClassDataDTO {
+//        private final String packageStr;
+//        private final String fullyQualifiedClassName;
+//        private final AccessModifier accessModifier;
+//    }
+//    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
 }
diff --git a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ApiChangeDTO.java b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ApiChangeDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..2d3588f6a7a1da5e93efe198b703c1c5dfbd18da
--- /dev/null
+++ b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ApiChangeDTO.java
@@ -0,0 +1,153 @@
+/*
+ * 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.apiupdate;
+
+import japicmp.model.JApiChangeStatus;
+import java.util.Optional;
+
+/**
+ *
+ * @author gregd
+ */
+public class ApiChangeDTO {
+    //NEW, REMOVED, UNCHANGED, MODIFIED
+
+    public interface PublicApiChangeable {
+
+        PublicApiChangeType getChangeType();
+    }
+
+    public static class ClassChangeDTO {
+
+        private final JApiChangeStatus changeStatus;
+        private final Optional<String> oldDeclaration;
+        private final Optional<Long> oldSerialId;
+        
+        private final Optional<String> newDeclaration;
+        private final Optional<Long> newSerialId;
+
+        public ClassChangeDTO(JApiChangeStatus changeStatus, Optional<String> oldDeclaration, Optional<Long> oldSerialId, Optional<String> newDeclaration, Optional<Long> newSerialId) {
+            this.changeStatus = changeStatus;
+            this.oldDeclaration = oldDeclaration;
+            this.oldSerialId = oldSerialId;
+            this.newDeclaration = newDeclaration;
+            this.newSerialId = newSerialId;
+        }
+
+        public JApiChangeStatus getChangeStatus() {
+            return changeStatus;
+        }
+
+        public Optional<String> getOldDeclaration() {
+            return oldDeclaration;
+        }
+
+        public Optional<Long> getOldSerialId() {
+            return oldSerialId;
+        }
+
+        public Optional<String> getNewDeclaration() {
+            return newDeclaration;
+        }
+
+        public Optional<Long> getNewSerialId() {
+            return newSerialId;
+        }
+        
+        
+    }
+
+//    public static class SuperclassChangeDTO {
+//
+//        private final Optional<String> oldFullyQualifiedClassName;
+//        private final Optional<String> newFullyQualifiedClassName;
+//
+//        public SuperclassChangeDTO(Optional<String> oldFullyQualifiedClassName, Optional<String> newFullyQualifiedClassName) {
+//            this.oldFullyQualifiedClassName = oldFullyQualifiedClassName;
+//            this.newFullyQualifiedClassName = newFullyQualifiedClassName;
+//        }
+//
+//        public Optional<String> getOldFullyQualifiedClassName() {
+//            return oldFullyQualifiedClassName;
+//        }
+//
+//        public Optional<String> getNewFullyQualifiedClassName() {
+//            return newFullyQualifiedClassName;
+//        }
+//
+//    }
+//
+//    public static class InterfaceChangeDTO {
+//
+//        private final JApiChangeStatus changeStatus;
+//        private final String fullyQualifiedName;
+//
+//        public InterfaceChangeDTO(JApiChangeStatus changeStatus, String fullyQualifiedName) {
+//            this.changeStatus = changeStatus;
+//            this.fullyQualifiedName = fullyQualifiedName;
+//        }
+//
+//        public JApiChangeStatus getChangeStatus() {
+//            return changeStatus;
+//        }
+//
+//        public String getFullyQualifiedName() {
+//            return fullyQualifiedName;
+//        }
+//
+//    }
+    public static class MethodChangeDTO {
+
+        private final JApiChangeStatus changeStatus;
+        private final Optional<String> oldMethodDeclaration;
+        private final Optional<String> newMethodDeclaration;
+
+        public MethodChangeDTO(JApiChangeStatus changeStatus, Optional<String> oldMethodDeclaration, Optional<String> newMethodDeclaration) {
+            this.changeStatus = changeStatus;
+            this.oldMethodDeclaration = oldMethodDeclaration;
+            this.newMethodDeclaration = newMethodDeclaration;
+        }
+
+        public JApiChangeStatus getChangeStatus() {
+            return changeStatus;
+        }
+
+        public Optional<String> getOldMethodDeclaration() {
+            return oldMethodDeclaration;
+        }
+
+        public Optional<String> getNewMethodDeclaration() {
+            return newMethodDeclaration;
+        }
+
+    }
+
+    public static class FieldChangeDTO {
+
+        private final JApiChangeStatus changeStatus;
+        private final Optional<String> oldFieldDeclaration;
+        private final Optional<String> newFieldDeclaration;
+
+        public FieldChangeDTO(JApiChangeStatus changeStatus, Optional<String> oldFieldDeclaration, Optional<String> newFieldDeclaration) {
+            this.changeStatus = changeStatus;
+            this.oldFieldDeclaration = oldFieldDeclaration;
+            this.newFieldDeclaration = newFieldDeclaration;
+        }
+
+        public JApiChangeStatus getChangeStatus() {
+            return changeStatus;
+        }
+
+        public Optional<String> getOldFieldDeclaration() {
+            return oldFieldDeclaration;
+        }
+
+        public Optional<String> getNewFieldDeclaration() {
+            return newFieldDeclaration;
+        }
+
+    }
+
+}
diff --git a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ChangeOutputGenerator.java b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ChangeOutputGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..428b52e222ab6b55200cf4dfcbf26a281460106e
--- /dev/null
+++ b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ChangeOutputGenerator.java
@@ -0,0 +1,614 @@
+package org.sleuthkit.autopsy.apiupdate;
+
+import japicmp.cli.CliParser;
+import japicmp.config.Options;
+import japicmp.model.*;
+import japicmp.model.JApiAnnotationElementValue.Type;
+import static japicmp.model.JApiChangeStatus.MODIFIED;
+import static japicmp.model.JApiChangeStatus.NEW;
+import static japicmp.model.JApiChangeStatus.REMOVED;
+import static japicmp.model.JApiChangeStatus.UNCHANGED;
+import japicmp.output.OutputFilter;
+import japicmp.output.OutputGenerator;
+import javassist.bytecode.annotation.MemberValue;
+
+import java.util.Comparator;
+import java.util.List;
+
+import static japicmp.util.GenericTemplateHelper.haveGenericTemplateInterfacesChanges;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.commons.lang3.StringUtils;
+import org.sleuthkit.autopsy.apiupdate.ApiChangeDTO.FieldChangeDTO;
+import org.sleuthkit.autopsy.apiupdate.ApiChangeDTO.InterfaceChangeDTO;
+import org.sleuthkit.autopsy.apiupdate.ApiChangeDTO.SuperclassChangeDTO;
+
+// taken from https://raw.githubusercontent.com/siom79/japicmp/master/japicmp/src/main/java/japicmp/output/stdout/StdoutOutputGenerator.java
+public class ChangeOutputGenerator extends OutputGenerator<ApiChangeDTO> {
+
+    static final String NO_CHANGES = "No changes.";
+    static final String WARNING = "WARNING";
+
+    public ChangeOutputGenerator(Options options, List<JApiClass> jApiClasses) {
+        super(options, jApiClasses);
+    }
+
+    @Override
+    public String generate() {
+        OutputFilter outputFilter = new OutputFilter(options);
+        outputFilter.filter(jApiClasses);
+        StringBuilder sb = new StringBuilder();
+        sb.append(options.getDifferenceDescription()).append('\n');
+        if (options.getIgnoreMissingClasses().isIgnoreAllMissingClasses()) {
+            sb.append(WARNING).append(": You are using the option '").append(CliParser.IGNORE_MISSING_CLASSES)
+                    .append("', i.e. superclasses and interfaces that could not be found on the classpath are ignored.")
+                    .append(" Hence changes caused by these superclasses and interfaces are not reflected in the output.\n");
+        } else if (options.getIgnoreMissingClasses().getIgnoreMissingClassRegularExpression().size() > 0) {
+            sb.append(WARNING).append(": You have ignored certain classes, i.e. superclasses and interfaces that could not ")
+                    .append("be found on the classpath are ignored. Hence changes caused by these superclasses and interfaces are not reflected in the output.\n");
+        }
+        if (jApiClasses.size() > 0) {
+            for (JApiClass jApiClass : jApiClasses) {
+                processClass(sb, jApiClass);
+                processConstructors(sb, jApiClass);
+                processMethods(sb, jApiClass);
+                processAnnotations(sb, jApiClass, 1);
+            }
+        } else {
+            sb.append(NO_CHANGES);
+        }
+        return sb.toString();
+    }
+
+    private void processAnnotations(StringBuilder sb, JApiHasAnnotations jApiClass, int numberofTabs) {
+        List<JApiAnnotation> annotations = jApiClass.getAnnotations();
+        for (JApiAnnotation jApiAnnotation : annotations) {
+            appendAnnotation(sb, signs(jApiAnnotation), jApiAnnotation, numberofTabs);
+            List<JApiAnnotationElement> elements = jApiAnnotation.getElements();
+            for (JApiAnnotationElement jApiAnnotationElement : elements) {
+                appendAnnotationElement(sb, signs(jApiAnnotationElement), jApiAnnotationElement, numberofTabs + 1);
+            }
+        }
+    }
+
+    private void processConstructors(StringBuilder sb, JApiClass jApiClass) {
+        List<JApiConstructor> constructors = jApiClass.getConstructors();
+        for (JApiConstructor jApiConstructor : constructors) {
+            appendBehavior(sb, signs(jApiConstructor), jApiConstructor, "CONSTRUCTOR:");
+            processAnnotations(sb, jApiConstructor, 2);
+            processExceptions(sb, jApiConstructor, 2);
+            processGenericTemplateChanges(sb, jApiConstructor, 2);
+        }
+    }
+
+    private void processMethods(StringBuilder sb, JApiClass jApiClass) {
+        List<JApiMethod> methods = jApiClass.getMethods();
+        for (JApiMethod jApiMethod : methods) {
+            appendBehavior(sb, signs(jApiMethod), jApiMethod, "METHOD:");
+            processAnnotations(sb, jApiMethod, 2);
+            processExceptions(sb, jApiMethod, 2);
+            processGenericTemplateChanges(sb, jApiMethod, 2);
+        }
+    }
+
+    private void processExceptions(StringBuilder sb, JApiBehavior jApiBehavior, int indent) {
+        for (JApiException exception : jApiBehavior.getExceptions()) {
+            appendException(sb, signs(exception), exception, indent);
+        }
+    }
+
+    private void appendException(StringBuilder sb, String signs, JApiException jApiException, int indent) {
+        sb.append(tabs(indent)).append(signs).append(" ").append(jApiException.getChangeStatus()).append(" EXCEPTION: ").append(jApiException.getName()).append("\n");
+    }
+
+    private void processClass(StringBuilder sb, JApiClass jApiClass) {
+        appendClass(sb, signs(jApiClass), jApiClass);
+    }
+
+    private void appendBehavior(StringBuilder sb, String signs, JApiBehavior jApiBehavior, String classMemberType) {
+        sb.append("\t").append(signs).append(" ").append(jApiBehavior.getChangeStatus()).append(" ").append(classMemberType).append(" ")
+                .append(accessModifierAsString(jApiBehavior)).append(abstractModifierAsString(jApiBehavior)).append(staticModifierAsString(jApiBehavior))
+                .append(finalModifierAsString(jApiBehavior)).append(syntheticModifierAsString(jApiBehavior)).append(bridgeModifierAsString(jApiBehavior))
+                .append(returnType(jApiBehavior)).append(jApiBehavior.getName()).append("(");
+        int paramCount = 0;
+        for (JApiParameter jApiParameter : jApiBehavior.getParameters()) {
+            if (paramCount > 0) {
+                sb.append(", ");
+            }
+            sb.append(jApiParameter.getType());
+            appendGenericTypes(sb, jApiParameter);
+            paramCount++;
+        }
+        sb.append(")\n");
+    }
+
+    private void appendGenericTypes(StringBuilder sb, JApiHasGenericTypes jApiHasGenericTypes) {
+        if (!jApiHasGenericTypes.getNewGenericTypes().isEmpty() || !jApiHasGenericTypes.getOldGenericTypes().isEmpty()) {
+            if (jApiHasGenericTypes instanceof JApiCompatibility) {
+                List<JApiCompatibilityChange> compatibilityChanges = ((JApiCompatibility) jApiHasGenericTypes).getCompatibilityChanges();
+                appendGenericTypesForCompatibilityChanges(sb, jApiHasGenericTypes, compatibilityChanges);
+            }
+        }
+    }
+
+    private void appendGenericTypesForCompatibilityChanges(StringBuilder sb, JApiHasGenericTypes jApiHasGenericTypes, List<JApiCompatibilityChange> compatibilityChanges) {
+        if (compatibilityChanges.contains(JApiCompatibilityChange.METHOD_PARAMETER_GENERICS_CHANGED)
+                || compatibilityChanges.contains(JApiCompatibilityChange.METHOD_RETURN_TYPE_GENERICS_CHANGED)
+                || compatibilityChanges.contains(JApiCompatibilityChange.FIELD_GENERICS_CHANGED)
+                || compatibilityChanges.contains(JApiCompatibilityChange.CLASS_GENERIC_TEMPLATE_GENERICS_CHANGED)) {
+            appendGenericTypes(sb, false, jApiHasGenericTypes.getNewGenericTypes());
+            appendGenericTypes(sb, true, jApiHasGenericTypes.getOldGenericTypes());
+        } else {
+            if (!jApiHasGenericTypes.getNewGenericTypes().isEmpty()) {
+                appendGenericTypes(sb, false, jApiHasGenericTypes.getNewGenericTypes());
+            }
+            if (!jApiHasGenericTypes.getOldGenericTypes().isEmpty()) {
+                appendGenericTypes(sb, false, jApiHasGenericTypes.getOldGenericTypes());
+            }
+        }
+    }
+
+    private void appendGenericTypes(StringBuilder sb, boolean withChangeInParenthesis, List<JApiGenericType> genericTypes) {
+        if (!genericTypes.isEmpty()) {
+            if (withChangeInParenthesis) {
+                sb.append("(<- ");
+            }
+            sb.append("<");
+            int count = 0;
+            for (JApiGenericType genericType : genericTypes) {
+                if (count > 0) {
+                    sb.append(",");
+                }
+                appendGenericType(sb, genericType);
+                if (!genericType.getGenericTypes().isEmpty()) {
+                    appendGenericTypes(sb, false, genericType.getGenericTypes());
+                }
+                count++;
+            }
+            sb.append(">");
+            if (withChangeInParenthesis) {
+                sb.append(")");
+            }
+        }
+    }
+
+    private void appendGenericType(StringBuilder sb, JApiGenericType jApiGenericType) {
+        if (jApiGenericType.getGenericWildCard() == JApiGenericType.JApiGenericWildCard.NONE) {
+            sb.append(jApiGenericType.getType());
+        } else if (jApiGenericType.getGenericWildCard() == JApiGenericType.JApiGenericWildCard.UNBOUNDED) {
+            sb.append("?");
+        } else if (jApiGenericType.getGenericWildCard() == JApiGenericType.JApiGenericWildCard.EXTENDS) {
+            sb.append("? extends ").append(jApiGenericType.getType());
+        } else if (jApiGenericType.getGenericWildCard() == JApiGenericType.JApiGenericWildCard.SUPER) {
+            sb.append("? super ").append(jApiGenericType.getType());
+        }
+    }
+
+    private String returnType(JApiBehavior jApiBehavior) {
+        StringBuilder sb = new StringBuilder();
+        if (jApiBehavior instanceof JApiMethod) {
+            JApiMethod method = (JApiMethod) jApiBehavior;
+            JApiReturnType jApiReturnType = method.getReturnType();
+            if (jApiReturnType.getChangeStatus() == JApiChangeStatus.UNCHANGED) {
+                sb.append(jApiReturnType.getNewReturnType());
+                appendGenericTypes(sb, jApiReturnType);
+                sb.append(" ");
+            } else if (jApiReturnType.getChangeStatus() == JApiChangeStatus.MODIFIED) {
+                sb.append(jApiReturnType.getNewReturnType());
+                appendGenericTypes(sb, jApiReturnType);
+                sb.append(" (<-");
+                sb.append(jApiReturnType.getOldReturnType());
+                appendGenericTypes(sb, jApiReturnType);
+                sb.append(") ");
+            } else if (jApiReturnType.getChangeStatus() == JApiChangeStatus.NEW) {
+                sb.append(jApiReturnType.getNewReturnType());
+                appendGenericTypes(sb, jApiReturnType);
+                sb.append(" ");
+            } else {
+                sb.append(jApiReturnType.getOldReturnType());
+                appendGenericTypes(sb, jApiReturnType);
+                sb.append(" ");
+            }
+        }
+        return sb.toString();
+    }
+
+    private void appendAnnotation(StringBuilder sb, String signs, JApiAnnotation jApiAnnotation, int numberOfTabs) {
+        sb.append(String.format("%s%s %s ANNOTATION: %s\n", tabs(numberOfTabs), signs, jApiAnnotation.getChangeStatus(), jApiAnnotation.getFullyQualifiedName()));
+    }
+
+    private void appendAnnotationElement(StringBuilder sb, String signs, JApiAnnotationElement jApiAnnotationElement, int numberOfTabs) {
+        sb.append(String.format("%s%s %s ELEMENT: %s=", tabs(numberOfTabs), signs, jApiAnnotationElement.getChangeStatus(), jApiAnnotationElement.getName()));
+        Optional<MemberValue> oldValue = jApiAnnotationElement.getOldValue();
+        Optional<MemberValue> newValue = jApiAnnotationElement.getNewValue();
+        if (oldValue.isPresent() && newValue.isPresent()) {
+            if (jApiAnnotationElement.getChangeStatus() == JApiChangeStatus.UNCHANGED) {
+                sb.append(elementValueList2String(jApiAnnotationElement.getNewElementValues()));
+            } else if (jApiAnnotationElement.getChangeStatus() == JApiChangeStatus.REMOVED) {
+                sb.append(String.format("%s (-)", elementValueList2String(jApiAnnotationElement.getOldElementValues())));
+            } else if (jApiAnnotationElement.getChangeStatus() == JApiChangeStatus.NEW) {
+                sb.append(String.format("%s (+)", elementValueList2String(jApiAnnotationElement.getNewElementValues())));
+            } else if (jApiAnnotationElement.getChangeStatus() == JApiChangeStatus.MODIFIED) {
+                sb.append(String.format("%s (<- %s)", elementValueList2String(jApiAnnotationElement.getNewElementValues()), elementValueList2String(jApiAnnotationElement.getOldElementValues())));
+            }
+        } else if (!oldValue.isPresent() && newValue.isPresent()) {
+            sb.append(String.format("%s (+)", elementValueList2String(jApiAnnotationElement.getNewElementValues())));
+        } else if (oldValue.isPresent() && !newValue.isPresent()) {
+            sb.append(String.format("%s (-)", elementValueList2String(jApiAnnotationElement.getOldElementValues())));
+        } else {
+            sb.append(" n.a.");
+        }
+        sb.append("\n");
+    }
+
+    private void appendClass(StringBuilder sb, String signs, JApiClass jApiClass) {
+        sb.append(signs).append(" ").append(jApiClass.getChangeStatus()).append(" ")
+                .append(processClassType(jApiClass)).append(": ")
+                .append(accessModifierAsString(jApiClass))
+                .append(abstractModifierAsString(jApiClass))
+                .append(staticModifierAsString(jApiClass))
+                .append(finalModifierAsString(jApiClass))
+                .append(syntheticModifierAsString(jApiClass))
+                .append(jApiClass.getFullyQualifiedName()).append(" ")
+                .append(javaObjectSerializationStatus(jApiClass)).append("\n");
+
+        processGenericTemplateChanges(sb, jApiClass, 1);
+        processInterfaceChanges(sb, jApiClass);
+        processSuperclassChanges(sb, jApiClass);
+        processFieldChanges(sb, jApiClass);
+    }
+
+    private String getClassString(JApiClass clz, boolean oldClass) {
+        if (jApiClass.getChangeStatus() == NEW && oldClass || jApiClass.getChangeStatus() == REMOVED && !oldClass) {
+            return null;
+        }
+
+        String accessModifier = str(get(clz.getAccessModifier(), oldClass).orElse(null));
+        String abstractModifier = str(get(clz.getAbstractModifier(), oldClass).orElse(null));
+        String staticModifier = str(get(clz.getStaticModifier(), oldClass).orElse(null));
+        String finalModifier = str(get(clz.getFinalModifier(), oldClass).orElse(null));
+        String syntheticModifier = str(get(clz.getSyntheticModifier(), oldClass).orElse(null));
+
+        String name = clz.getFullyQualifiedName();
+
+        String genericModifier = strOpt(get(clz.getGenericTemplates();
+
+        String implementsModifier = TBD;
+        String extendsModifier = TBD;
+
+        return Stream.of(
+                accessModifier,
+                abstractModifier,
+                staticModifier,
+                finalModifier,
+                syntheticModifier,
+                "class",
+                name + (StringUtils.isBlank(genericModifier) ? "" : genericModifier),
+                implementsModifier,
+                extendsModifier)
+                .filter(StringUtils::isNotBlank)
+                .collect(Collectors.joining(" "));
+    }
+
+//    private List<GenericTemplateChangeDTO> getGenericTemplateChanges(JApiHasGenericTemplates jApiHasGenericTemplates) {
+//        return jApiHasGenericTemplates.getGenericTemplates().stream()
+//                .filter(template -> template.getChangeStatus() != JApiChangeStatus.UNCHANGED)
+//                .map(template -> new GenericTemplateChangeDTO())
+//                .collect(Collectors.toList());
+//        
+//        if (!genericTemplates.isEmpty()) {
+//            sb.append(tabs(numberOfTabs)).append("GENERIC TEMPLATES: ");
+//            genericTemplates.sort(Comparator.comparing(JApiGenericTemplate::getName));
+//            int count = 0;
+//            for (JApiGenericTemplate jApiGenericTemplate : genericTemplates) {
+//                if (count > 0) {
+//                    sb.append(", ");
+//                }
+//                count++;
+//                sb.append(signs(jApiGenericTemplate));
+//                if (sb.charAt(sb.length() - 1) != ' ') {
+//                    sb.append(" ");
+//                }
+//                sb.append(jApiGenericTemplate.getName()).append(":");
+//                JApiChangeStatus changeStatus = jApiGenericTemplate.getChangeStatus();
+//                if (changeStatus == JApiChangeStatus.NEW || changeStatus == JApiChangeStatus.UNCHANGED) {
+//                    sb.append(jApiGenericTemplate.getNewType());
+//                    if (jApiGenericTemplate instanceof JApiCompatibility) {
+//                        appendGenericTypesForCompatibilityChanges(sb, jApiGenericTemplate, ((JApiCompatibility) jApiHasGenericTemplates).getCompatibilityChanges());
+//                    }
+//                } else if (changeStatus == JApiChangeStatus.REMOVED) {
+//                    sb.append(jApiGenericTemplate.getOldType());
+//                    if (jApiGenericTemplate instanceof JApiCompatibility) {
+//                        appendGenericTypesForCompatibilityChanges(sb, jApiGenericTemplate, ((JApiCompatibility) jApiHasGenericTemplates).getCompatibilityChanges());
+//                    }
+//                } else {
+//                    sb.append(jApiGenericTemplate.getNewType());
+//                    appendGenericTypes(sb, false, jApiGenericTemplate.getNewGenericTypes());
+//                    sb.append(" (<-").append(jApiGenericTemplate.getOldType());
+//                    appendGenericTypes(sb, false, jApiGenericTemplate.getOldGenericTypes());
+//                    sb.append(")");
+//                }
+//                if (!jApiGenericTemplate.getOldInterfaceTypes().isEmpty() || !jApiGenericTemplate.getNewInterfaceTypes().isEmpty()) {
+//                    if (haveGenericTemplateInterfacesChanges(jApiGenericTemplate.getOldInterfaceTypes(), jApiGenericTemplate.getNewInterfaceTypes())) {
+//                        appendGenericTemplatesInterfaces(sb, jApiGenericTemplate, false, true);
+//                        sb.append(" (<-");
+//                        appendGenericTemplatesInterfaces(sb, jApiGenericTemplate, true, false);
+//                        sb.append(")");
+//                    } else {
+//                        appendGenericTemplatesInterfaces(sb, jApiGenericTemplate, false, true);
+//                    }
+//                }
+//            }
+//            sb.append("\n");
+//        }
+//    }
+//    private void appendGenericTemplatesInterfaces(StringBuilder sb, JApiGenericTemplate jApiGenericTemplate, boolean printOld, boolean printNew) {
+//        if (printOld) {
+//            for (JApiGenericType jApiGenericType : jApiGenericTemplate.getOldInterfaceTypes()) {
+//                sb.append(" & ");
+//                sb.append(jApiGenericType.getType());
+//                appendGenericTypes(sb, false, jApiGenericType.getGenericTypes());
+//            }
+//        }
+//        if (printNew) {
+//            for (JApiGenericType jApiGenericType : jApiGenericTemplate.getNewInterfaceTypes()) {
+//                sb.append(" & ");
+//                sb.append(jApiGenericType.getType());
+//                appendGenericTypes(sb, false, jApiGenericType.getGenericTypes());
+//            }
+//        }
+//    }
+//    private String str(JApiHasGenericTemplates jApiHasGenericTemplates) {
+//        jApiHasGenericTemplates.getGenericTemplates().get(0).g
+//    }
+    private FieldChangeDTO getFieldChange(JApiField field) {
+        return new FieldChangeDTO(
+                field.getChangeStatus(),
+                Optional.ofNullable(getFieldString(field, true)),
+                Optional.ofNullable(getFieldString(field, false)));
+    }
+
+    private String getFieldString(JApiField field, boolean oldField) {
+        if (field.getChangeStatus() == NEW && oldField || field.getChangeStatus() == REMOVED && !oldField) {
+            return null;
+        }
+
+        String accessModifier = str(get(field.getAccessModifier(), oldField).orElse(null));
+        String staticModifier = str(get(field.getStaticModifier(), oldField).orElse(null));
+        String finalModifier = str(get(field.getFinalModifier(), oldField).orElse(null));
+        String syntheticModifier = str(get(field.getSyntheticModifier(), oldField).orElse(null));
+        String fieldType = strOpt(field.getType(), oldField).orElse("");
+        String name = field.getName();
+
+        return Stream.of(accessModifier, staticModifier, finalModifier, syntheticModifier, fieldType, name)
+                .filter(StringUtils::isNotBlank)
+                .collect(Collectors.joining(" "));
+    }
+
+    private String str(AccessModifier modifier) {
+        if (modifier == null || modifier == AccessModifier.PACKAGE_PROTECTED) {
+            return "";
+        } else {
+            return modifier.name().toLowerCase();
+        }
+    }
+
+    private String str(StaticModifier modifier) {
+        return (modifier == StaticModifier.STATIC)
+                ? "static"
+                : "";
+    }
+
+    private String str(FinalModifier modifier) {
+        return (modifier == FinalModifier.FINAL)
+                ? "final"
+                : "";
+    }
+
+    private String str(SyntheticModifier modifier) {
+        return (modifier == SyntheticModifier.SYNTHETIC)
+                ? "synthetic"
+                : "";
+    }
+
+    private String str(AbstractModifier modifier) {
+        return (modifier == AbstractModifier.ABSTRACT)
+                ? "abstract"
+                : "";
+    }
+
+    private Optional<String> strOpt(JApiType tp, boolean oldField) {
+        return oldField ? convert(tp.getOldTypeOptional()) : convert(tp.getNewTypeOptional());
+    }
+
+    private <T> Optional<T> get(JApiModifier<T> modifier, boolean oldField) {
+        return oldField ? convert(modifier.getOldModifier()) : convert(modifier.getNewModifier());
+    }
+
+//    private Optional<SuperclassChangeDTO> getSuperClassChange(JApiSuperclass jApiSuperclass) {
+//        return jApiSuperclass.getChangeStatus() != JApiChangeStatus.UNCHANGED
+//                ? Optional.ofNullable(new SuperclassChangeDTO(
+//                        convert(jApiSuperclass.getOldSuperclassName()),
+//                        convert(jApiSuperclass.getNewSuperclassName())))
+//                : Optional.empty();
+//    }
+//
+//    private List<InterfaceChangeDTO> getInterfaceChanges(JApiClass jApiClass) {
+//        return jApiClass.getInterfaces().stream()
+//                .filter(intfc -> intfc.getChangeStatus() != JApiChangeStatus.UNCHANGED)
+//                .map(intfc -> new InterfaceChangeDTO(intfc.getChangeStatus(), intfc.getFullyQualifiedName()))
+//                .collect(Collectors.toList());
+//    }
+    private <T> Optional<T> convert(japicmp.util.Optional<T> apiOpt) {
+        T val = apiOpt.or((T) null);
+        return java.util.Optional.ofNullable(val);
+    }
+
+//
+//    private void processClassFileFormatVersionChanges(StringBuilder sb, JApiClass jApiClass) {
+//        JApiClassFileFormatVersion classFileFormatVersion = jApiClass.getClassFileFormatVersion();
+//        sb.append(tabs(1))
+//                .append(signs(classFileFormatVersion))
+//                .append(" CLASS FILE FORMAT VERSION: ");
+//        if (classFileFormatVersion.getMajorVersionNew() != -1 && classFileFormatVersion.getMinorVersionNew() != -1) {
+//            sb.append(classFileFormatVersion.getMajorVersionNew()).append(".").append(classFileFormatVersion.getMinorVersionNew());
+//        } else {
+//            sb.append("n.a.");
+//        }
+//        sb.append(" <- ");
+//        if (classFileFormatVersion.getMajorVersionOld() != -1 && classFileFormatVersion.getMinorVersionOld() != -1) {
+//            sb.append(classFileFormatVersion.getMajorVersionOld()).append(".").append(classFileFormatVersion.getMinorVersionOld());
+//        } else {
+//            sb.append("n.a.");
+//        }
+//        sb.append("\n");
+//    }
+//    private String processClassType(JApiClass jApiClass) {
+//        JApiClassType classType = jApiClass.getClassType();
+//        switch (classType.getChangeStatus()) {
+//            case NEW:
+//                return classType.getNewType();
+//            case REMOVED:
+//                return classType.getOldType();
+//            case MODIFIED:
+//                return classType.getNewType() + " (<- " + classType.getOldType() + ") ";
+//            case UNCHANGED:
+//                return classType.getOldType();
+//        }
+//        return "n.a.";
+//    }
+//
+//    // TODO do we need this
+//    private String getJavaObjectSerializationStatus(JApiClass jApiClass) {
+//        return jApiClass.getJavaObjectSerializationCompatible().getDescription();
+//    }
+//    private String elementValueList2String(List<JApiAnnotationElementValue> values) {
+//        StringBuilder sb = new StringBuilder();
+//        for (JApiAnnotationElementValue value : values) {
+//            if (sb.length() > 0) {
+//                sb.append(",");
+//            }
+//            if (value.getName().isPresent()) {
+//                sb.append(value.getName().get()).append("=");
+//            }
+//            if (value.getType() != Type.Array && value.getType() != Type.Annotation) {
+//                if (value.getType() == Type.Enum) {
+//                    sb.append(value.getFullyQualifiedName()).append(".").append(value.getValueString());
+//                } else {
+//                    sb.append(value.getValueString());
+//                }
+//            } else {
+//                if (value.getType() == Type.Array) {
+//                    sb.append("{").append(elementValueList2String(value.getValues())).append("}");
+//                } else if (value.getType() == Type.Annotation) {
+//                    sb.append("@").append(value.getFullyQualifiedName()).append("(").append(elementValueList2String(value.getValues())).append(")");
+//                }
+//            }
+//        }
+//        return sb.toString();
+//    }
+//
+//    private String tabs(int numberOfTabs) {
+//        if (numberOfTabs <= 0) {
+//            return "";
+//        } else if (numberOfTabs == 1) {
+//            return "\t";
+//        } else if (numberOfTabs == 2) {
+//            return "\t\t";
+//        } else {
+//            StringBuilder sb = new StringBuilder();
+//            for (int i = 0; i < numberOfTabs; i++) {
+//                sb.append("\t");
+//            }
+//            return sb.toString();
+//        }
+//    }
+//    private String signs(JApiHasChangeStatus hasChangeStatus) {
+//        JApiChangeStatus changeStatus = hasChangeStatus.getChangeStatus();
+//        String retVal = "???";
+//        switch (changeStatus) {
+//            case UNCHANGED:
+//                retVal = "===";
+//                break;
+//            case NEW:
+//                retVal = "+++";
+//                break;
+//            case REMOVED:
+//                retVal = "---";
+//                break;
+//            case MODIFIED:
+//                retVal = "***";
+//                break;
+//        }
+//        boolean binaryCompatible = true;
+//        boolean sourceCompatible = true;
+//        if (hasChangeStatus instanceof JApiCompatibility) {
+//            JApiCompatibility jApiCompatibility = (JApiCompatibility) hasChangeStatus;
+//            binaryCompatible = jApiCompatibility.isBinaryCompatible();
+//            sourceCompatible = jApiCompatibility.isSourceCompatible();
+//        }
+//        if (binaryCompatible) {
+//            if (sourceCompatible) {
+//                retVal += " ";
+//            } else {
+//                retVal += "*";
+//            }
+//        } else {
+//            retVal += "!";
+//        }
+//        return retVal;
+//    } 
+//
+//    private String bridgeModifierAsString(JApiHasBridgeModifier modifier) {
+//        JApiModifier<BridgeModifier> bridgeModifier = modifier.getBridgeModifier();
+//        return modifierAsString(bridgeModifier, BridgeModifier.NON_BRIDGE);
+//    }
+//    private <T> String modifierAsString(JApiModifier<T> modifier, T notPrintValue) {
+//        if (modifier.getOldModifier().isPresent() && modifier.getNewModifier().isPresent()) {
+//            if (modifier.getChangeStatus() == JApiChangeStatus.MODIFIED) {
+//                return modifier.getNewModifier().get() + " (<- " + modifier.getOldModifier().get() + ") ";
+//            } else if (modifier.getChangeStatus() == JApiChangeStatus.NEW) {
+//                if (modifier.getNewModifier().get() != notPrintValue) {
+//                    return modifier.getNewModifier().get() + "(+) ";
+//                }
+//            } else if (modifier.getChangeStatus() == JApiChangeStatus.REMOVED) {
+//                if (modifier.getOldModifier().get() != notPrintValue) {
+//                    return modifier.getOldModifier().get() + "(-) ";
+//                }
+//            } else {
+//                if (modifier.getNewModifier().get() != notPrintValue) {
+//                    return modifier.getNewModifier().get() + " ";
+//                }
+//            }
+//        } else if (modifier.getOldModifier().isPresent()) {
+//            if (modifier.getOldModifier().get() != notPrintValue) {
+//                return modifier.getOldModifier().get() + "(-) ";
+//            }
+//        } else if (modifier.getNewModifier().isPresent()) {
+//            if (modifier.getNewModifier().get() != notPrintValue) {
+//                return modifier.getNewModifier().get() + "(+) ";
+//            }
+//        }
+//        return "";
+//    }
+//    private String fieldTypeChangeAsString(JApiField field) {
+//        JApiType type = field.getType();
+//        if (type.getOldTypeOptional().isPresent() && type.getNewTypeOptional().isPresent()) {
+//            if (type.getChangeStatus() == JApiChangeStatus.MODIFIED) {
+//                return type.getNewTypeOptional().get() + " (<- " + type.getOldTypeOptional().get() + ")";
+//            } else if (type.getChangeStatus() == JApiChangeStatus.NEW) {
+//                return type.getNewTypeOptional().get() + "(+)";
+//            } else if (type.getChangeStatus() == JApiChangeStatus.REMOVED) {
+//                return type.getOldTypeOptional().get() + "(-)";
+//            } else {
+//                return type.getNewTypeOptional().get();
+//            }
+//        } else if (type.getOldTypeOptional().isPresent() && !type.getNewTypeOptional().isPresent()) {
+//            return type.getOldTypeOptional().get();
+//        } else if (!type.getOldTypeOptional().isPresent() && type.getNewTypeOptional().isPresent()) {
+//            return type.getNewTypeOptional().get();
+//        }
+//        return "n.a.";
+//    }
+}
diff --git a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/Main.java b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/Main.java
index 77b91e61ebf91fe50fc4aa33ac247034fe64241c..ee0f93ce7fead28e847f2898d14e0eb96ae2085e 100644
--- a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/Main.java
+++ b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/Main.java
@@ -3,14 +3,11 @@
  */
 package org.sleuthkit.autopsy.apiupdate;
 
-import java.util.HashMap;
-import java.util.Map;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import org.apache.commons.cli.ParseException;
 import org.sleuthkit.autopsy.apiupdate.CLIProcessor.CLIArgs;
-import org.sleuthkit.autopsy.apiupdate.ModuleUpdates.ModuleVersionNumbers;
-import org.sleuthkit.autopsy.apiupdate.ModuleUpdates.ReleaseVal;
 
 /**
  *
@@ -19,7 +16,7 @@
 public class Main {
 
     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 -s C:\\Users\\gregd\\Documents\\Source\\autopsy".split(" ");
+        args = "-c C:\\Users\\gregd\\Desktop\\apidiff\\new -p C:\\Users\\gregd\\Desktop\\apidiff\\old -cv 4.21.0 -pv 4.20.0 -s C:\\Users\\gregd\\Documents\\Source\\autopsy".split(" ");
         CLIArgs cliArgs;
         try {
             cliArgs = CLIProcessor.parseCli(args);
@@ -33,31 +30,35 @@ public static void main(String[] args) {
             return;
         }
         
-        Map<String, ModuleVersionNumbers> versNums = Stream.of(
-                new ModuleVersionNumbers(
-                        "org.sleuthkit.autopsy.core", 
-                        new ModuleUpdates.SemVer(1,2,3),
-                        4,
-                        new ReleaseVal("org.sleuthkit.autopsy.core", 5)),
-                new ModuleVersionNumbers(
-                        "org.sleuthkit.autopsy.corelibs", 
-                        new ModuleUpdates.SemVer(6,7,8),
-                        9,
-                        new ReleaseVal("org.sleuthkit.autopsy.corelibs", 10)))
-                .collect(Collectors.toMap(v -> v.getModuleName(), v -> v, (v1, v2) -> v1));
-        
-        ModuleUpdates.setVersions(cliArgs.getSrcPath(), versNums);
+//        Map<String, ModuleVersionNumbers> versNums = Stream.of(
+//                new ModuleVersionNumbers(
+//                        "org.sleuthkit.autopsy.core", 
+//                        new ModuleUpdates.SemVer(1,2,3),
+//                        4,
+//                        new ReleaseVal("org.sleuthkit.autopsy.core", 5)),
+//                new ModuleVersionNumbers(
+//                        "org.sleuthkit.autopsy.corelibs", 
+//                        new ModuleUpdates.SemVer(6,7,8),
+//                        9,
+//                        new ReleaseVal("org.sleuthkit.autopsy.corelibs", 10)))
+//                .collect(Collectors.toMap(v -> v.getModuleName(), v -> v, (v1, v2) -> v1));
+//        
+//        ModuleUpdates.setVersions(cliArgs.getSrcPath(), versNums);
 
-//        for (String commonJarFileName : APIDiff.getCommonJars(cliArgs.getPreviousVersPath(), cliArgs.getCurrentVersPath())) {
-//            try {
+        for (String commonJarFileName : APIDiff.getCommonJars(cliArgs.getPreviousVersPath(), cliArgs.getCurrentVersPath())) {
+            try {
 //                ModuleVersionNumbers m = ModuleUpdates.getVersionsFromJar(cliArgs.getPreviousVersPath().toPath().resolve(commonJarFileName).toFile());
 //                System.out.println(MessageFormat.format("release: {0}, spec: {1}, implementation: {2}", m.getRelease().getFullReleaseStr(), m.getSpec().getSemVerStr(), m.getImplementation()));
-//                
-//            } catch (IOException ex) {
-//                Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
-//            }
-//            
-//        }
+                APIDiff.getComparison(
+                        cliArgs.getPreviousVersion(), 
+                        cliArgs.getCurrentVersion(), 
+                        cliArgs.getPreviousVersPath().toPath().resolve(commonJarFileName).toFile(),
+                        cliArgs.getCurrentVersPath().toPath().resolve(commonJarFileName).toFile());
+            } catch (IOException ex) {
+                Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
+            }
+            
+        }
         
         
         
diff --git a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/PublicApiChangeType.java b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/PublicApiChangeType.java
new file mode 100644
index 0000000000000000000000000000000000000000..d3379ac93ef1d497fc2c96acb7774e746d79388d
--- /dev/null
+++ b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/PublicApiChangeType.java
@@ -0,0 +1,13 @@
+/*
+ * 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.apiupdate;
+
+/**
+ *
+ * @author gregd
+ */
+public enum PublicApiChangeType {
+    NONE, COMPATIBLE_CHANGE, INCOMPATIBLE_CHANGE;
+}