Index: trunk/src/org/openstreetmap/josm/actions/SessionLoadAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/SessionLoadAction.java	(revision 18832)
+++ trunk/src/org/openstreetmap/josm/actions/SessionLoadAction.java	(revision 18833)
@@ -13,4 +13,5 @@
 import java.nio.file.StandardCopyOption;
 import java.util.Arrays;
+import java.util.EnumSet;
 import java.util.List;
 
@@ -34,4 +35,5 @@
 import org.openstreetmap.josm.io.session.SessionReader.SessionProjectionChoiceData;
 import org.openstreetmap.josm.io.session.SessionReader.SessionViewportData;
+import org.openstreetmap.josm.io.session.SessionWriter;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.openstreetmap.josm.tools.JosmRuntimeException;
@@ -206,5 +208,12 @@
                     viewport = reader.getViewport();
                     projectionChoice = reader.getProjectionChoice();
-                    SessionSaveAction.setCurrentSession(file, zip, reader.getLayers());
+                    final EnumSet<SessionWriter.SessionWriterFlags> flagSet = EnumSet.noneOf(SessionWriter.SessionWriterFlags.class);
+                    if (zip) {
+                        flagSet.add(SessionWriter.SessionWriterFlags.IS_ZIP);
+                    }
+                    if (reader.loadedPluginData()) {
+                        flagSet.add(SessionWriter.SessionWriterFlags.SAVE_PLUGIN_INFORMATION);
+                    }
+                    SessionSaveAction.setCurrentSession(file, reader.getLayers(), flagSet);
                 } finally {
                     if (tempFile) {
Index: trunk/src/org/openstreetmap/josm/actions/SessionSaveAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/SessionSaveAction.java	(revision 18832)
+++ trunk/src/org/openstreetmap/josm/actions/SessionSaveAction.java	(revision 18833)
@@ -18,4 +18,5 @@
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -57,6 +58,8 @@
 import org.openstreetmap.josm.gui.util.WindowGeometry;
 import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
+import org.openstreetmap.josm.io.session.PluginSessionExporter;
 import org.openstreetmap.josm.io.session.SessionLayerExporter;
 import org.openstreetmap.josm.io.session.SessionWriter;
+import org.openstreetmap.josm.plugins.PluginHandler;
 import org.openstreetmap.josm.spi.preferences.Config;
 import org.openstreetmap.josm.tools.GBC;
@@ -79,4 +82,5 @@
 
     private static final BooleanProperty SAVE_LOCAL_FILES_PROPERTY = new BooleanProperty("session.savelocal", true);
+    private static final BooleanProperty SAVE_PLUGIN_INFORMATION_PROPERTY = new BooleanProperty("session.saveplugins", false);
     private static final String TOOLTIP_DEFAULT = tr("Save the current session.");
 
@@ -89,4 +93,5 @@
     static File sessionFile;
     static boolean isZipSessionFile;
+    private static boolean pluginData;
     static List<WeakReference<Layer>> layersInSessionFile;
 
@@ -171,5 +176,5 @@
 
         boolean zipRequired = layersOut.stream().map(l -> exporters.get(l))
-                .anyMatch(ex -> ex != null && ex.requiresZip());
+                .anyMatch(ex -> ex != null && ex.requiresZip()) || pluginsWantToSave();
 
         saveAs = !doGetFile(saveAs, zipRequired);
@@ -241,5 +246,12 @@
         }
 
-        SessionWriter sw = new SessionWriter(layersOut, active, exporters, dependencies, isZipSessionFile);
+        final EnumSet<SessionWriter.SessionWriterFlags> flags = EnumSet.noneOf(SessionWriter.SessionWriterFlags.class);
+        if (pluginData || (Boolean.TRUE.equals(SAVE_PLUGIN_INFORMATION_PROPERTY.get()) && pluginsWantToSave())) {
+            flags.add(SessionWriter.SessionWriterFlags.SAVE_PLUGIN_INFORMATION);
+        }
+        if (isZipSessionFile) {
+            flags.add(SessionWriter.SessionWriterFlags.IS_ZIP);
+        }
+        SessionWriter sw = new SessionWriter(layersOut, active, exporters, dependencies, flags.toArray(new SessionWriter.SessionWriterFlags[0]));
         try {
             Notification savingNotification = showSavingNotification(sessionFile.getName());
@@ -435,5 +447,11 @@
             JCheckBox chkSaveLocal = new JCheckBox(tr("Save all local files to disk"), SAVE_LOCAL_FILES_PROPERTY.get());
             chkSaveLocal.addChangeListener(l -> SAVE_LOCAL_FILES_PROPERTY.put(chkSaveLocal.isSelected()));
-            op.add(chkSaveLocal);
+            op.add(chkSaveLocal, GBC.eol());
+            if (pluginsWantToSave()) {
+                JCheckBox chkSavePlugins = new JCheckBox(tr("Save plugin information to disk"), SAVE_PLUGIN_INFORMATION_PROPERTY.get());
+                chkSavePlugins.addChangeListener(l -> SAVE_PLUGIN_INFORMATION_PROPERTY.put(chkSavePlugins.isSelected()));
+                chkSavePlugins.setToolTipText(tr("Plugins may have additional information that can be saved"));
+                op.add(chkSavePlugins, GBC.eol());
+            }
             return op;
         }
@@ -510,8 +528,39 @@
      * @param zip if it is a zip session file
      * @param layers layers that are currently represented in the session file
-     */
+     * @deprecated since 18833, use {@link #setCurrentSession(File, List, SessionWriter.SessionWriterFlags...)} instead
+     */
+    @Deprecated
     public static void setCurrentSession(File file, boolean zip, List<Layer> layers) {
+        if (zip) {
+            setCurrentSession(file, layers, SessionWriter.SessionWriterFlags.IS_ZIP);
+        } else {
+            setCurrentSession(file, layers);
+        }
+    }
+
+    /**
+     * Sets the current session file and the layers included in that file
+     * @param file file
+     * @param layers layers that are currently represented in the session file
+     * @param flags The flags for the current session
+     * @since 18833
+     */
+    public static void setCurrentSession(File file, List<Layer> layers, SessionWriter.SessionWriterFlags... flags) {
+        final EnumSet<SessionWriter.SessionWriterFlags> flagSet = EnumSet.noneOf(SessionWriter.SessionWriterFlags.class);
+        flagSet.addAll(Arrays.asList(flags));
+        setCurrentSession(file, layers, flagSet);
+    }
+
+    /**
+     * Sets the current session file and the layers included in that file
+     * @param file file
+     * @param layers layers that are currently represented in the session file
+     * @param flags The flags for the current session
+     * @since 18833
+     */
+    public static void setCurrentSession(File file, List<Layer> layers, Set<SessionWriter.SessionWriterFlags> flags) {
         setCurrentLayers(layers);
-        setCurrentSession(file, zip);
+        setCurrentSession(file, flags.contains(SessionWriter.SessionWriterFlags.IS_ZIP));
+        pluginData = flags.contains(SessionWriter.SessionWriterFlags.SAVE_PLUGIN_INFORMATION);
     }
 
@@ -551,3 +600,16 @@
     }
 
+    /**
+     * Check to see if any plugins want to save their state
+     * @return {@code true} if the plugin wants to save their state
+     */
+    private static boolean pluginsWantToSave() {
+        for (PluginSessionExporter exporter : PluginHandler.load(PluginSessionExporter.class)) {
+            if (exporter.requiresSaving()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
 }
Index: trunk/src/org/openstreetmap/josm/io/session/GenericSessionExporter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/session/GenericSessionExporter.java	(revision 18832)
+++ trunk/src/org/openstreetmap/josm/io/session/GenericSessionExporter.java	(revision 18833)
@@ -32,4 +32,5 @@
 import org.openstreetmap.josm.gui.widgets.JosmTextField;
 import org.openstreetmap.josm.io.session.SessionWriter.ExportSupport;
+import org.openstreetmap.josm.plugins.PluginHandler;
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.ImageProvider;
@@ -211,5 +212,13 @@
     @Override
     public boolean requiresZip() {
-        return include.isSelected();
+        if (include.isSelected()) {
+            return true;
+        }
+        for (PluginSessionExporter exporter : PluginHandler.load(PluginSessionExporter.class)) {
+            if (exporter.requiresSaving()) {
+                return true;
+            }
+        }
+        return false;
     }
 
Index: trunk/src/org/openstreetmap/josm/io/session/PluginSessionExporter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/session/PluginSessionExporter.java	(revision 18833)
+++ trunk/src/org/openstreetmap/josm/io/session/PluginSessionExporter.java	(revision 18833)
@@ -0,0 +1,47 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.session;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Export arbitrary data from a plugin.
+ * @since 18833
+ */
+public interface PluginSessionExporter {
+    /**
+     * Get the filename to store the data in the archive
+     * @return The filename
+     * @see PluginSessionImporter#getFileName()
+     */
+    String getFileName();
+
+    /**
+     * Check to see if the specified exporter needs to save anything
+     * @return {@code true} if the exporter needs to save something
+     */
+    boolean requiresSaving();
+
+    /**
+     * Write data to a zip file
+     * @param zipOut The zip output stream
+     * @throws IOException see {@link ZipOutputStream#putNextEntry(ZipEntry)}
+     * @throws ZipException see {@link ZipOutputStream#putNextEntry(ZipEntry)}
+     */
+    default void writeZipEntries(ZipOutputStream zipOut) throws IOException {
+        if (requiresSaving()) {
+            final ZipEntry zipEntry = new ZipEntry(this.getFileName());
+            zipOut.putNextEntry(zipEntry);
+            this.write(zipOut);
+        }
+    }
+
+    /**
+     * Write the plugin data to a stream
+     * @param outputStream The stream to write to
+     */
+    void write(OutputStream outputStream);
+}
Index: trunk/src/org/openstreetmap/josm/io/session/PluginSessionImporter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/session/PluginSessionImporter.java	(revision 18833)
+++ trunk/src/org/openstreetmap/josm/io/session/PluginSessionImporter.java	(revision 18833)
@@ -0,0 +1,43 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.session;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Import arbitrary data for a plugin.
+ * @since 18833
+ */
+public interface PluginSessionImporter {
+    /**
+     * Get the filename that was used to store data in the archive.
+     * @return The filename
+     * @see PluginSessionExporter#getFileName()
+     */
+    String getFileName();
+
+    /**
+     * Read data from a file stream
+     * @param inputStream The stream to read
+     * @return {@code true} if the importer loaded data
+     */
+    boolean read(InputStream inputStream);
+
+    /**
+     * Read the data from a zip file
+     * @param zipFile The zipfile to read
+     * @return {@code true} if the importer loaded data
+     * @throws IOException if there was an issue reading the zip file. See {@link ZipFile#getInputStream(ZipEntry)}.
+     */
+    default boolean readZipFile(ZipFile zipFile) throws IOException {
+        final ZipEntry entry = zipFile.getEntry(this.getFileName());
+        if (entry != null) {
+            try (InputStream inputStream = zipFile.getInputStream(entry)) {
+                return this.read(inputStream);
+            }
+        }
+        return false;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/io/session/SessionReader.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/session/SessionReader.java	(revision 18832)
+++ trunk/src/org/openstreetmap/josm/io/session/SessionReader.java	(revision 18833)
@@ -40,4 +40,5 @@
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.projection.Projection;
+import org.openstreetmap.josm.gui.ExceptionDialogUtil;
 import org.openstreetmap.josm.gui.ExtendedDialog;
 import org.openstreetmap.josm.gui.MainApplication;
@@ -45,6 +46,8 @@
 import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
 import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.io.Compression;
 import org.openstreetmap.josm.io.IllegalDataException;
+import org.openstreetmap.josm.plugins.PluginHandler;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.openstreetmap.josm.tools.JosmRuntimeException;
@@ -157,4 +160,5 @@
     private URI sessionFileURI;
     private boolean zip; // true, if session file is a .joz file; false if it is a .jos file
+    private boolean pluginData; // true, if a plugin restored state from a .joz file. False otherwise.
     private ZipFile zipFile;
     private List<Layer> layers = new ArrayList<>();
@@ -193,5 +197,5 @@
         if (importerClass == null)
             return null;
-        SessionLayerImporter importer = null;
+        SessionLayerImporter importer;
         try {
             importer = importerClass.getConstructor().newInstance();
@@ -242,4 +246,13 @@
     public SessionProjectionChoiceData getProjectionChoice() {
         return projectionChoice;
+    }
+
+    /**
+     * Returns whether plugins loaded additonal data
+     * @return {@code true} if at least one plugin loaded additional data
+     * @since 18833
+     */
+    public boolean loadedPluginData() {
+        return this.pluginData;
     }
 
@@ -309,7 +322,7 @@
         /**
          * Return an InputStream for a URI from a .jos/.joz file.
-         *
+         * <p>
          * The following forms are supported:
-         *
+         * <p>
          * - absolute file (both .jos and .joz):
          *         "file:///home/user/data.osm"
@@ -352,5 +365,5 @@
         /**
          * Return a File for a URI from a .jos/.joz file.
-         *
+         * <p>
          * Returns null if the URI points to a file inside the zip archive.
          * In this case, inZipPath will be set to the corresponding path.
@@ -713,5 +726,5 @@
      * Show Dialog when there is an error for one layer.
      * Ask the user whether to cancel the complete session loading or just to skip this layer.
-     *
+     * <p>
      * This is expected to run in a worker thread (PleaseWaitRunnable), so invokeAndWait is
      * needed to block the current thread and wait for the result of the modal dialog from EDT.
@@ -743,4 +756,17 @@
     }
 
+    private void loadPluginData() {
+        if (!zip) {
+            return;
+        }
+        for (PluginSessionImporter importer : PluginHandler.load(PluginSessionImporter.class)) {
+            try {
+                this.pluginData |= importer.readZipFile(zipFile);
+            } catch (IOException ioException) {
+                GuiHelper.runInEDT(() -> ExceptionDialogUtil.explainException(ioException));
+            }
+        }
+    }
+
     /**
      * Loads session from the given file.
@@ -754,4 +780,5 @@
         try (InputStream josIS = createInputStream(sessionFile, zip)) {
             loadSession(josIS, sessionFile.toURI(), zip, progressMonitor);
+            this.postLoadTasks.add(this::loadPluginData);
         }
     }
Index: trunk/src/org/openstreetmap/josm/io/session/SessionWriter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/session/SessionWriter.java	(revision 18832)
+++ trunk/src/org/openstreetmap/josm/io/session/SessionWriter.java	(revision 18833)
@@ -10,4 +10,5 @@
 import java.nio.file.Files;
 import java.util.Collection;
+import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.List;
@@ -44,4 +45,5 @@
 import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
 import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
+import org.openstreetmap.josm.plugins.PluginHandler;
 import org.openstreetmap.josm.tools.JosmRuntimeException;
 import org.openstreetmap.josm.tools.Logging;
@@ -59,4 +61,19 @@
 public class SessionWriter {
 
+    /**
+     * {@link SessionWriter} options
+     * @since 18833
+     */
+    public enum SessionWriterFlags {
+        /**
+         * Use if the file to be written needs to be a zip file
+         */
+        IS_ZIP,
+        /**
+         * Use if there are plugins that want to save information
+         */
+        SAVE_PLUGIN_INFORMATION
+    }
+
     private static final Map<Class<? extends Layer>, Class<? extends SessionLayerExporter>> sessionLayerExporters = new HashMap<>();
 
@@ -66,4 +83,5 @@
     private final MultiMap<Layer, Layer> dependencies;
     private final boolean zip;
+    private final boolean plugins;
 
     private ZipOutputStream zipOut;
@@ -83,5 +101,5 @@
     /**
      * Register a session layer exporter.
-     *
+     * <p>
      * The exporter class must have a one-argument constructor with layerClass as formal parameter type.
      * @param layerClass layer class
@@ -121,9 +139,27 @@
     public SessionWriter(List<Layer> layers, int active, Map<Layer, SessionLayerExporter> exporters,
                 MultiMap<Layer, Layer> dependencies, boolean zip) {
+        this(layers, active, exporters, dependencies,
+                zip ? new SessionWriterFlags[] {SessionWriterFlags.IS_ZIP} : new SessionWriterFlags[0]);
+    }
+
+    /**
+     * Constructs a new {@code SessionWriter}.
+     * @param layers The ordered list of layers to save
+     * @param active The index of active layer in {@code layers} (starts at 0). Ignored if set to -1
+     * @param exporters The exporters to use to save layers
+     * @param dependencies layer dependencies
+     * @param flags The flags to use when writing data
+     * @since 18833
+     */
+    public SessionWriter(List<Layer> layers, int active, Map<Layer, SessionLayerExporter> exporters,
+                         MultiMap<Layer, Layer> dependencies, SessionWriterFlags... flags) {
         this.layers = layers;
         this.active = active;
         this.exporters = exporters;
         this.dependencies = dependencies;
-        this.zip = zip;
+        final EnumSet<SessionWriterFlags> flagSet = flags.length == 0 ? EnumSet.noneOf(SessionWriterFlags.class) :
+                EnumSet.of(flags[0], flags);
+        this.zip = flagSet.contains(SessionWriterFlags.IS_ZIP);
+        this.plugins = flagSet.contains(SessionWriterFlags.SAVE_PLUGIN_INFORMATION);
     }
 
@@ -219,5 +255,5 @@
      */
     public Document createJosDocument() throws IOException {
-        DocumentBuilder builder = null;
+        DocumentBuilder builder;
         try {
             builder = XmlUtils.newSafeDOMBuilder();
@@ -362,4 +398,9 @@
             zipOut.putNextEntry(entry);
             writeJos(doc, zipOut);
+            if (this.plugins) {
+                for (PluginSessionExporter exporter : PluginHandler.load(PluginSessionExporter.class)) {
+                    exporter.writeZipEntries(zipOut);
+                }
+            }
             Utils.close(zipOut);
         } else {
Index: trunk/src/org/openstreetmap/josm/plugins/PluginHandler.java
===================================================================
--- trunk/src/org/openstreetmap/josm/plugins/PluginHandler.java	(revision 18832)
+++ trunk/src/org/openstreetmap/josm/plugins/PluginHandler.java	(revision 18833)
@@ -33,4 +33,5 @@
 import java.util.Map.Entry;
 import java.util.Objects;
+import java.util.ServiceLoader;
 import java.util.Set;
 import java.util.TreeMap;
@@ -359,7 +360,19 @@
 
     /**
+     * Get a {@link ServiceLoader} for the specified service. This uses {@link #getJoinedPluginResourceCL()} as the
+     * class loader, so that we don't have to iterate through the {@link ClassLoader}s from {@link #getPluginClassLoaders()}.
+     * @param <S> The service type
+     * @param service The service class to look for
+     * @return The service loader
+     * @since 18833
+     */
+    public static <S> ServiceLoader<S> load(Class<S> service) {
+        return ServiceLoader.load(service, getJoinedPluginResourceCL());
+    }
+
+    /**
      * Removes deprecated plugins from a collection of plugins. Modifies the
      * collection <code>plugins</code>.
-     *
+     * <p>
      * Also notifies the user about removed deprecated plugins
      *
@@ -412,5 +425,5 @@
      * collection <code>plugins</code>. Also removes the plugin from the list
      * of plugins in the preferences, if necessary.
-     *
+     * <p>
      * Asks the user for every unmaintained plugin whether it should be removed.
      * @param parent The parent Component used to display warning popup
@@ -778,5 +791,5 @@
     /**
      * Get class loader to locate resources from plugins.
-     *
+     * <p>
      * It joins URLs of all plugins, to find images, etc.
      * (Not for loading Java classes - each plugin has a separate {@link PluginClassLoader}
@@ -931,5 +944,5 @@
      * Loads plugins from <code>plugins</code> which have the flag {@link PluginInformation#early} set to true
      * <i>and</i> a negative {@link PluginInformation#stage} value.
-     *
+     * <p>
      * This is meant for plugins that provide additional {@link javax.swing.LookAndFeel}.
      */
@@ -1041,5 +1054,5 @@
         try {
             monitor.beginTask(tr("Determining plugins to load..."));
-            Set<String> plugins = new HashSet<>(Config.getPref().getList("plugins", new LinkedList<String>()));
+            Set<String> plugins = new HashSet<>(Config.getPref().getList("plugins", new LinkedList<>()));
             Logging.debug("Plugins list initialized to {0}", plugins);
             String systemProp = Utils.getSystemProperty("josm.plugins");
@@ -1334,5 +1347,5 @@
     /**
      * Installs downloaded plugins. Moves files with the suffix ".jar.new" to the corresponding ".jar" files.
-     *
+     * <p>
      * If {@code dowarn} is true, this methods emits warning messages on the console if a downloaded
      * but not yet installed plugin .jar can't be be installed. If {@code dowarn} is false, the
