Index: src/org/openstreetmap/josm/actions/ExtensionFileFilter.java
===================================================================
--- src/org/openstreetmap/josm/actions/ExtensionFileFilter.java	(revision 7731)
+++ src/org/openstreetmap/josm/actions/ExtensionFileFilter.java	(working copy)
@@ -96,7 +96,8 @@
                 "org.openstreetmap.josm.io.OsmGzipExporter",
                 "org.openstreetmap.josm.io.OsmBzip2Exporter",
                 "org.openstreetmap.josm.io.GeoJSONExporter",
-                "org.openstreetmap.josm.io.WMSLayerExporter"
+                "org.openstreetmap.josm.io.WMSLayerExporter",
+                "org.openstreetmap.josm.io.NoteExporter"
         };
 
         for (String classname : exporterNames) {
Index: src/org/openstreetmap/josm/data/osm/NoteData.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/NoteData.java	(revision 7731)
+++ src/org/openstreetmap/josm/data/osm/NoteData.java	(working copy)
@@ -36,6 +36,11 @@
      */
     public NoteData(List<Note> notes) {
         noteList = notes;
+        for (Note note : notes) {
+            if (note.getId() <= newNoteId) {
+                newNoteId = note.getId() - 1;
+            }
+        }
     }
 
     /**
Index: src/org/openstreetmap/josm/gui/layer/NoteLayer.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/NoteLayer.java	(revision 7731)
+++ src/org/openstreetmap/josm/gui/layer/NoteLayer.java	(working copy)
@@ -8,6 +8,7 @@
 import java.awt.Point;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
+import java.io.File;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.List;
@@ -18,6 +19,7 @@
 import javax.swing.JToolTip;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.SaveActionBase;
 import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.data.notes.Note;
 import org.openstreetmap.josm.data.notes.Note.State;
@@ -28,6 +30,7 @@
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
 import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
 import org.openstreetmap.josm.gui.dialogs.NoteDialog;
+import org.openstreetmap.josm.io.NoteExporter;
 import org.openstreetmap.josm.io.XmlWriter;
 import org.openstreetmap.josm.tools.ColorHelper;
 
@@ -81,6 +84,17 @@
     }
 
     @Override
+    public boolean isSavable() {
+        return true;
+    }
+
+    @Override
+    public boolean requiresSaveToFile() {
+        Main.debug("associated notes file: " + getAssociatedFile());
+        return getAssociatedFile() != null && isModified();
+    }
+
+    @Override
     public void paint(Graphics2D g, MapView mv, Bounds box) {
         for (Note note : noteData.getNotes()) {
             Point p = mv.getPoint(note.getLatLon());
@@ -107,7 +121,7 @@
                 //closing a note creates an empty comment that we don't want to show
                 if (commentText != null && commentText.trim().length() > 0) {
                     sb.append(sep);
-                    String userName = comment.getUser().getName();
+                    String userName = XmlWriter.encode(comment.getUser().getName());
                     if (userName == null || userName.trim().length() == 0) {
                         userName = "&lt;Anonymous&gt;";
                     }
@@ -190,6 +204,8 @@
         actions.add(LayerListDialog.getInstance().createShowHideLayerAction());
         actions.add(LayerListDialog.getInstance().createDeleteLayerAction());
         actions.add(new LayerListPopup.InfoAction(this));
+        actions.add(new LayerSaveAction(this));
+        actions.add(new LayerSaveAsAction(this));
         return actions.toArray(new Action[actions.size()]);
     }
 
@@ -216,6 +232,11 @@
     }
 
     @Override
+    public File createAndOpenSaveFileChooser() {
+        return SaveActionBase.createAndOpenSaveFileChooser(tr("Save GPX file"), NoteExporter.FILE_FILTER);
+    }
+
+    @Override
     public void mousePressed(MouseEvent e) { }
 
     @Override
Index: src/org/openstreetmap/josm/io/NoteExporter.java
===================================================================
--- src/org/openstreetmap/josm/io/NoteExporter.java	(revision 0)
+++ src/org/openstreetmap/josm/io/NoteExporter.java	(working copy)
@@ -0,0 +1,48 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.ExtensionFileFilter;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.NoteLayer;
+
+/**
+ * Exporter to write note data to an XML file
+ */
+public class NoteExporter extends FileExporter {
+
+    /** File extension filter for .osn files */
+    public static final ExtensionFileFilter FILE_FILTER = new ExtensionFileFilter(
+            "osn", "osn", tr("Note Files") + " (*.osn)");
+
+    /** Create a new note exporter with the default .osn file filter */
+    public NoteExporter() {
+        super(FILE_FILTER);
+    }
+
+    @Override
+    public boolean acceptFile(File pathname, Layer layer) {
+        if (!(layer instanceof NoteLayer))
+            return false;
+        return super.acceptFile(pathname, layer);
+    }
+
+    @Override
+    public void exportData(File file, Layer layer) throws IOException {
+        Main.info("exporting notes to file: " + file);
+        if (layer instanceof NoteLayer) {
+            OutputStream os = new FileOutputStream(file);
+            NoteWriter writer = new NoteWriter(os);
+            writer.write(((NoteLayer) layer).getNoteData());
+            os.flush();
+            writer.close();
+        }
+    }
+}
Index: src/org/openstreetmap/josm/io/NoteReader.java
===================================================================
--- src/org/openstreetmap/josm/io/NoteReader.java	(revision 7731)
+++ src/org/openstreetmap/josm/io/NoteReader.java	(working copy)
@@ -136,6 +136,9 @@
             }
             if("comment".equals(qName)) {
                 User commentUser = User.createOsmUser(commentUid, commentUsername);
+                if (commentUid == 0) {
+                    commentUser = User.getAnonymous();
+                }
                 if(parseMode == NoteParseMode.API) {
                     commentIsNew = false;
                 }
@@ -164,6 +167,9 @@
             case "date_created":
                 thisNote.setCreatedAt(parseDate(NOTE_DATE_FORMAT, buffer.toString()));
                 break;
+            case "date_closed":
+                thisNote.setClosedAt(parseDate(NOTE_DATE_FORMAT, buffer.toString()));
+                break;
             case "date":
                 commentCreateDate = parseDate(NOTE_DATE_FORMAT, buffer.toString());
                 break;
Index: src/org/openstreetmap/josm/io/NoteWriter.java
===================================================================
--- src/org/openstreetmap/josm/io/NoteWriter.java	(revision 0)
+++ src/org/openstreetmap/josm/io/NoteWriter.java	(working copy)
@@ -0,0 +1,84 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io;
+
+import java.io.BufferedWriter;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+
+import org.openstreetmap.josm.data.notes.Note;
+import org.openstreetmap.josm.data.notes.NoteComment;
+import org.openstreetmap.josm.data.osm.NoteData;
+import org.openstreetmap.josm.data.osm.User;
+
+/**
+ * Class to write a collection of notes out to XML.
+ * The format is that of the note dump file with the addition of one
+ * attribute in the comment element to indicate if the comment is a new local
+ * comment that has not been uploaded to the OSM server yet.
+ */
+public class NoteWriter extends XmlWriter {
+
+    private final SimpleDateFormat ISO8601_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.ENGLISH);
+
+    /**
+     * Create a NoteWriter that will write to the given PrintWriter
+     * @param out PrintWriter to write XML to
+     */
+    public NoteWriter(PrintWriter out) {
+        super(out);
+    }
+
+    /**
+     * Create a NoteWriter that will write to a given OutputStream.
+     * @param out OutputStream to write XML to
+     */
+    public NoteWriter(OutputStream out) {
+        super(new PrintWriter(new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))));
+    }
+
+    /**
+     * Write notes to designated output target
+     * @param data Note collection to write
+     */
+    public void write(NoteData data) {
+        out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+        out.println("<osm-notes>");
+        for (Note note : data.getNotes()) {
+            out.print("  <note ");
+            out.print("id=\"" + note.getId() + "\" ");
+            out.print("lat=\"" + note.getLatLon().lat() + "\" ");
+            out.print("lon=\"" + note.getLatLon().lon() + "\" ");
+            out.print("created_at=\"" + ISO8601_FORMAT.format(note.getCreatedAt()) + "\" ");
+            if (note.getClosedAt() != null) {
+                out.print("closed_at=\"" + ISO8601_FORMAT.format(note.getClosedAt()) + "\" ");
+            }
+
+            out.println(">");
+            for (NoteComment comment : note.getComments()) {
+                writeComment(comment);
+            }
+            out.println("  </note>");
+        }
+
+        out.println("</osm-notes>");
+        out.flush();
+    }
+
+    private void writeComment(NoteComment comment) {
+        out.print("    <comment");
+        out.print(" action=\"" + comment.getNoteAction() + "\" ");
+        out.print("timestamp=\"" + ISO8601_FORMAT.format(comment.getCommentTimestamp()) + "\" ");
+        if (comment.getUser() != null && !comment.getUser().equals(User.getAnonymous())) {
+            out.print("uid=\"" + comment.getUser().getId() + "\" ");
+            out.print("user=\"" + encode(comment.getUser().getName()) + "\" ");
+        }
+        out.print("is_new=\"" + comment.getIsNew() + "\" ");
+        out.print(">");
+        out.print(encode(comment.getText(), false));
+        out.println("</comment>");
+    }
+}
