diff --git a/simpleexample2/core/src/main/java/simpleex/core/LatLongs.java b/simpleexample2/core/src/main/java/simpleex/core/LatLongs.java
index a6b992a2b5f488e575e31349891ccc1bd5efc5da..a4a3d9de9ed56925ed4e5ed9dc0d7348083016b3 100644
--- a/simpleexample2/core/src/main/java/simpleex/core/LatLongs.java
+++ b/simpleexample2/core/src/main/java/simpleex/core/LatLongs.java
@@ -48,6 +48,14 @@ public class LatLongs implements Iterable<LatLong> {
     return latLongs.iterator();
   }
 
+  /**
+   * Returns a list of all the LatLong objects in this LatLongs.
+   * @return the list of LatLong objects
+   */
+  public List<LatLong> toList() {
+    return new ArrayList<>(latLongs);
+  }
+
   /**
    * Gets the number of LatLong objects.
    * @return the number of LatLong objects
diff --git a/simpleexample2/core/src/test/java/simpleex/core/LatLongsTest.java b/simpleexample2/core/src/test/java/simpleex/core/LatLongsTest.java
index f493e45f188efa81479fd043d063af4ced89b893..a79919fca295074c620a3e91c26db0bdcdcece5e 100644
--- a/simpleexample2/core/src/test/java/simpleex/core/LatLongsTest.java
+++ b/simpleexample2/core/src/test/java/simpleex/core/LatLongsTest.java
@@ -2,6 +2,7 @@ package simpleex.core;
 
 import java.util.Arrays;
 import java.util.Iterator;
+import java.util.List;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -44,6 +45,15 @@ public class LatLongsTest {
         new LatLong(63.1, 10.2), new LatLong(63.1, 10.1));
   }
 
+  @Test
+  public void testToList() {
+    final LatLongs latLongs = new LatLongs(63.0, 10.3, 63.1, 10.2);
+    final List<LatLong> list = latLongs.toList();
+    Assert.assertEquals(2, list.size());
+    Assert.assertEquals(new LatLong(63.0, 10.3), list.get(0));
+    Assert.assertEquals(new LatLong(63.1, 10.2), list.get(1));
+  }
+
   @Test
   public void testAddLatLong() {
     latLongs.addLatLong(new LatLong(63.0, 10.3));
diff --git a/simpleexample2/fxui/src/main/java/simpleex/ui/AbstractFxAppController.java b/simpleexample2/fxui/src/main/java/simpleex/ui/AbstractFxAppController.java
new file mode 100644
index 0000000000000000000000000000000000000000..6cd28996b148f720f2d55bce58c2e45e57d2122a
--- /dev/null
+++ b/simpleexample2/fxui/src/main/java/simpleex/ui/AbstractFxAppController.java
@@ -0,0 +1,184 @@
+package simpleex.ui;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import fxmapcontrol.Location;
+import fxmapcontrol.MapBase;
+import fxmapcontrol.MapItemsControl;
+import fxmapcontrol.MapNode;
+import fxmapcontrol.MapProjection;
+import javafx.collections.FXCollections;
+import javafx.fxml.FXML;
+import javafx.geometry.Point2D;
+import javafx.scene.Node;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Alert.AlertType;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.ListView;
+import javafx.scene.control.Slider;
+import simpleex.core.LatLong;
+import simpleex.json.LatLongsModule;
+
+/*
+@startuml
+class AbstractFxAppController
+class LatLongs
+class BorderPane
+class "ListView<LatLong>" as ListView
+class "fxmapcontrol.MapBase" as MapBase
+
+BorderPane *--> ListView: "left"
+BorderPane *--> MapBase: "center"
+
+AbstractFxAppController --> MapBase: "mapView"
+AbstractFxAppController --> ListView: "locationListView"
+@enduml
+ */
+
+/**
+ * The controller for the app.
+ * @author hal
+ *
+ */
+public abstract class AbstractFxAppController {
+
+  private LatLongsDataAccess dataAccess;
+
+  protected LatLongsDataAccess getDataAccess() {
+    return dataAccess;
+  }
+
+  protected void setDataAccess(final LatLongsDataAccess dataAccess) {
+    this.dataAccess = dataAccess;
+    if (locationListView != null) {
+      updateLocationViewList(0);
+    }
+  }
+
+  @FXML
+  private ListView<LatLong> locationListView;
+
+  @FXML
+  private MapBase mapView;
+
+  private MapItemsControl<MapNode> markersParent;
+  private MapMarker marker = null;
+  private DraggableNodeController draggableMapController = null;
+  private DraggableNodeController draggableMarkerController = null;
+
+  @FXML
+  private Slider zoomSlider;
+
+  @FXML
+  private void initialize() {
+    // map stuff
+    // mapView.getChildren().add(MapTileLayer.getOpenStreetMapLayer());
+    zoomSlider.valueProperty()
+      .addListener((prop, oldValue, newValue) -> mapView.setZoomLevel(zoomSlider.getValue()));
+    zoomSlider.setValue(8);
+    markersParent = new MapItemsControl<MapNode>();
+    mapView.getChildren().add(markersParent);
+    draggableMapController = new DraggableNodeController(this::handleMapDragged);
+    draggableMapController.setImmediate(true);
+    draggableMapController.attach(mapView);
+    draggableMarkerController = new DraggableNodeController(this::handleMarkerDragged);
+    // the location list
+    locationListView.getSelectionModel().selectedIndexProperty()
+      .addListener((prop, oldValue, newValue) -> updateMapMarker(true));
+  }
+
+  private void handleMapDragged(final Node node, final double dx, final double dy) {
+    final MapProjection projection = mapView.getProjection();
+    final Point2D point = projection.locationToViewportPoint(mapView.getCenter());
+    final Location newCenter = projection.viewportPointToLocation(point.add(-dx, -dy));
+    mapView.setCenter(newCenter);
+  }
+
+  private void handleMarkerDragged(final Node node, final double dx, final double dy) {
+    final MapProjection projection = mapView.getProjection();
+    final Point2D point = projection.locationToViewportPoint(marker.getLocation());
+    final Location newLocation = projection.viewportPointToLocation(point.add(dx, dy));
+    dataAccess.setLatLong(locationListView.getSelectionModel().getSelectedIndex(),
+        location2LatLong(newLocation));
+    updateLocationViewListSelection(false);
+  }
+
+  private LatLong location2LatLong(final Location newLocation) {
+    return new LatLong(newLocation.getLatitude(), newLocation.getLongitude());
+  }
+
+  private void updateMapMarker(final boolean centerOnMarker) {
+    final int num = locationListView.getSelectionModel().getSelectedIndex();
+    if (num < 0) {
+      markersParent.getItems().clear();
+      if (draggableMarkerController != null) {
+        draggableMarkerController.detach(marker);
+      }
+      marker = null;
+    } else {
+      final LatLong latLong = dataAccess.getLatLong(num);
+      if (marker == null) {
+        marker = new MapMarker(latLong);
+        markersParent.getItems().add(marker);
+        if (draggableMarkerController != null) {
+          draggableMarkerController.attach(marker);
+        }
+      } else {
+        marker.setLocation(latLong);
+      }
+      if (centerOnMarker) {
+        mapView.setCenter(marker.getLocation());
+      }
+    }
+  }
+
+  @FXML
+  void handleAddLocation() {
+    final Location center = mapView.getCenter();
+    final LatLong latLong = location2LatLong(center);
+    final int pos = dataAccess.addLatLong(latLong);
+    updateLocationViewList(pos);
+  }
+
+  private void updateLocationViewListSelection(final Boolean updateMapMarker) {
+    final int selectedIndex = locationListView.getSelectionModel().getSelectedIndex();
+    locationListView.getItems().set(selectedIndex, dataAccess.getLatLong(selectedIndex));
+    if (updateMapMarker != null) {
+      updateMapMarker(updateMapMarker);
+    }
+  }
+
+  protected void updateLocationViewList(int selectedIndex) {
+    final LatLong[] latLongs = dataAccess.getAllLatLongs().toArray(new LatLong[0]);
+    final int oldSelectionIndex = locationListView.getSelectionModel().getSelectedIndex();
+    locationListView.setItems(FXCollections.observableArrayList(latLongs));
+    if (selectedIndex < 0 || selectedIndex >= latLongs.length) {
+      selectedIndex = oldSelectionIndex;
+    }
+    if (selectedIndex >= 0 && selectedIndex < latLongs.length) {
+      locationListView.getSelectionModel().select(selectedIndex);
+    }
+  }
+
+  private ObjectMapper objectMapper;
+
+  /**
+   * Gets the ObjectMapper used by this controller.
+   * @return the ObjectMapper used by this controller
+   */
+  public ObjectMapper getObjectMapper() {
+    if (objectMapper == null) {
+      objectMapper = new ObjectMapper();
+      objectMapper.registerModule(new LatLongsModule());
+    }
+    return objectMapper;
+  }
+
+  protected void showExceptionDialog(final String message) {
+    final Alert alert = new Alert(AlertType.ERROR, message, ButtonType.CLOSE);
+    alert.showAndWait();
+  }
+
+  protected void showExceptionDialog(final String message, final Exception e) {
+    showExceptionDialog(message + ": " + e.getLocalizedMessage());
+  }
+}
diff --git a/simpleexample2/fxui/src/main/java/simpleex/ui/FxAppController.java b/simpleexample2/fxui/src/main/java/simpleex/ui/FxAppController.java
index 03ce1bba8d8193f1b66b2ee30bd8abccd23d25d9..8fa67a6aac8f08f61b102087c87d3aa4a7af4816 100644
--- a/simpleexample2/fxui/src/main/java/simpleex/ui/FxAppController.java
+++ b/simpleexample2/fxui/src/main/java/simpleex/ui/FxAppController.java
@@ -1,31 +1,15 @@
 package simpleex.ui;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-import fxmapcontrol.Location;
-import fxmapcontrol.MapBase;
-import fxmapcontrol.MapItemsControl;
-import fxmapcontrol.MapNode;
-import fxmapcontrol.MapProjection;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import javafx.collections.FXCollections;
 import javafx.event.ActionEvent;
 import javafx.fxml.FXML;
-import javafx.geometry.Point2D;
-import javafx.scene.Node;
-import javafx.scene.control.Alert;
-import javafx.scene.control.Alert.AlertType;
-import javafx.scene.control.ButtonType;
-import javafx.scene.control.ListView;
-import javafx.scene.control.Slider;
 import javafx.stage.FileChooser;
-import simpleex.core.LatLong;
 import simpleex.core.LatLongs;
-import simpleex.json.LatLongsModule;
 
 /*
 @startuml
@@ -49,23 +33,13 @@ FxAppController --> ListView: "locationListView"
  * @author hal
  *
  */
-public class FxAppController {
-
-  private LatLongs latLongs;
+public class FxAppController extends AbstractFxAppController {
 
   /**
    * Initializes the controller.
    */
   public FxAppController() {
-    latLongs = new LatLongs();
-  }
-
-  /**
-   * Gets the LatLongs objects used by the controller.
-   * @return the controller's LatLongs objects
-   */
-  public LatLongs getLatLongs() {
-    return latLongs;
+    setDataAccess(new LocalLatLongsDataAccess(new LatLongs()));
   }
 
   /**
@@ -73,121 +47,10 @@ public class FxAppController {
    * @param latLongs the LatLongs object to use
    */
   public void setLatLongs(final LatLongs latLongs) {
-    this.latLongs = latLongs;
+    setDataAccess(new LocalLatLongsDataAccess(latLongs));
     updateLocationViewList(0);
   }
 
-  @FXML
-  private ListView<LatLong> locationListView;
-
-  @FXML
-  private MapBase mapView;
-
-  private MapItemsControl<MapNode> markersParent;
-  private MapMarker marker = null;
-  private DraggableNodeController draggableMapController = null;
-  private DraggableNodeController draggableMarkerController = null;
-
-  @FXML
-  private Slider zoomSlider;
-
-  @FXML
-  private void initialize() {
-    // map stuff
-    // mapView.getChildren().add(MapTileLayer.getOpenStreetMapLayer());
-    zoomSlider.valueProperty()
-      .addListener((prop, oldValue, newValue) -> mapView.setZoomLevel(zoomSlider.getValue()));
-    zoomSlider.setValue(8);
-    markersParent = new MapItemsControl<MapNode>();
-    mapView.getChildren().add(markersParent);
-    draggableMapController = new DraggableNodeController(this::handleMapDragged);
-    draggableMapController.setImmediate(true);
-    draggableMapController.attach(mapView);
-    draggableMarkerController = new DraggableNodeController(this::handleMarkerDragged);
-    // the location list
-    locationListView.getSelectionModel().selectedIndexProperty()
-    .addListener((prop, oldValue, newValue) -> updateMapMarker(true));
-  }
-
-  private void handleMapDragged(final Node node, final double dx, final double dy) {
-    final MapProjection projection = mapView.getProjection();
-    final Point2D point = projection.locationToViewportPoint(mapView.getCenter());
-    final Location newCenter = projection.viewportPointToLocation(point.add(-dx, -dy));
-    mapView.setCenter(newCenter);
-  }
-
-  private void handleMarkerDragged(final Node node, final double dx, final double dy) {
-    final MapProjection projection = mapView.getProjection();
-    final Point2D point = projection.locationToViewportPoint(marker.getLocation());
-    final Location newLocation = projection.viewportPointToLocation(point.add(dx, dy));
-    getLatLongs().setLatLong(locationListView.getSelectionModel().getSelectedIndex(),
-        location2LatLong(newLocation));
-    updateLocationViewListSelection(false);
-  }
-
-  private LatLong location2LatLong(final Location newLocation) {
-    return new LatLong(newLocation.getLatitude(), newLocation.getLongitude());
-  }
-
-  private void updateMapMarker(final boolean centerOnMarker) {
-    final int num = locationListView.getSelectionModel().getSelectedIndex();
-    if (num < 0 || num >= getLatLongs().getLatLongCount()) {
-      markersParent.getItems().clear();
-      if (draggableMarkerController != null) {
-        draggableMarkerController.detach(marker);
-      }
-      marker = null;
-    } else {
-      final LatLong latLong = getLatLongs().getLatLong(num);
-      if (marker == null) {
-        marker = new MapMarker(latLong);
-        markersParent.getItems().add(marker);
-        if (draggableMarkerController != null) {
-          draggableMarkerController.attach(marker);
-        }
-      } else {
-        marker.setLocation(latLong);
-      }
-      if (centerOnMarker) {
-        mapView.setCenter(marker.getLocation());
-      }
-    }
-  }
-
-  @FXML
-  private void handleAddLocation() {
-    final Location center = mapView.getCenter();
-    final int pos = getLatLongs().addLatLong(location2LatLong(center));
-    updateLocationViewList(pos);
-  }
-
-  private void updateLocationViewListSelection(final Boolean updateMapMarker) {
-    final int selectedIndex = locationListView.getSelectionModel().getSelectedIndex();
-    updateLocationViewListItem(selectedIndex);
-    if (updateMapMarker != null) {
-      updateMapMarker(updateMapMarker);
-    }
-  }
-
-  private void updateLocationViewListItem(final int index) {
-    locationListView.getItems().set(index, getLatLongs().getLatLong(index));
-  }
-
-  private void updateLocationViewList(int selectedIndex) {
-    final LatLong[] latLongs = new LatLong[getLatLongs().getLatLongCount()];
-    for (int i = 0; i < latLongs.length; i++) {
-      latLongs[i] = getLatLongs().getLatLong(i);
-    }
-    final int oldSelectionIndex = locationListView.getSelectionModel().getSelectedIndex();
-    locationListView.setItems(FXCollections.observableArrayList(latLongs));
-    if (selectedIndex < 0 || selectedIndex >= latLongs.length) {
-      selectedIndex = oldSelectionIndex;
-    }
-    if (selectedIndex >= 0 && selectedIndex < getLatLongs().getLatLongCount()) {
-      locationListView.getSelectionModel().select(selectedIndex);
-    }
-  }
-
   // File menu items
 
   private FileChooser fileChooser;
@@ -212,29 +75,6 @@ public class FxAppController {
     }
   }
 
-  private ObjectMapper objectMapper;
-
-  /**
-   * Gets the ObjectMapper used by this controller.
-   * @return the ObjectMapper used by this controller
-   */
-  public ObjectMapper getObjectMapper() {
-    if (objectMapper == null) {
-      objectMapper = new ObjectMapper();
-      objectMapper.registerModule(new LatLongsModule());
-    }
-    return objectMapper;
-  }
-
-  private void showExceptionDialog(final String message) {
-    final Alert alert = new Alert(AlertType.ERROR, message, ButtonType.CLOSE);
-    alert.showAndWait();
-  }
-
-  private void showExceptionDialog(final String message, final Exception e) {
-    showExceptionDialog(message + ": " + e.getLocalizedMessage());
-  }
-
   private void showSaveExceptionDialog(final File location, final Exception e) {
     showExceptionDialog("Oops, problem saving to " + location, e);
   }
@@ -245,7 +85,7 @@ public class FxAppController {
     final File selection = fileChooser.showSaveDialog(null);
     if (selection != null) {
       try (OutputStream outputStream = new FileOutputStream(selection, false)) {
-        getObjectMapper().writeValue(outputStream, getLatLongs());
+        getObjectMapper().writeValue(outputStream, getDataAccess().getAllLatLongs());
       } catch (final IOException e) {
         showSaveExceptionDialog(selection, e);
       }
diff --git a/simpleexample2/fxui/src/main/java/simpleex/ui/FxAppControllerUsingRest.java b/simpleexample2/fxui/src/main/java/simpleex/ui/FxAppControllerUsingRest.java
new file mode 100644
index 0000000000000000000000000000000000000000..acdc5b19e043ae11d6114aee67f758dac4cbf690
--- /dev/null
+++ b/simpleexample2/fxui/src/main/java/simpleex/ui/FxAppControllerUsingRest.java
@@ -0,0 +1,30 @@
+package simpleex.ui;
+
+/*
+@startuml
+class FxAppController
+class LatLongs
+class BorderPane
+class "ListView<LatLong>" as ListView
+class "fxmapcontrol.MapBase" as MapBase
+
+BorderPane *--> ListView: "left"
+BorderPane *--> MapBase: "center"
+
+FxAppController --> LatLongs: "latLongs"
+FxAppController --> MapBase: "mapView"
+FxAppController --> ListView: "locationListView"
+@enduml
+ */
+
+/**
+ * The controller for the rest app.
+ * @author hal
+ *
+ */
+public class FxAppControllerUsingRest extends AbstractFxAppController {
+
+  public FxAppControllerUsingRest() {
+    setDataAccess(new RestLatLongsDataAccess(getObjectMapper()));
+  }
+}
diff --git a/simpleexample2/fxui/src/main/java/simpleex/ui/LatLongsDataAccess.java b/simpleexample2/fxui/src/main/java/simpleex/ui/LatLongsDataAccess.java
new file mode 100644
index 0000000000000000000000000000000000000000..d8f28e7fb1c037c3419c66b8e93bd4efe294938b
--- /dev/null
+++ b/simpleexample2/fxui/src/main/java/simpleex/ui/LatLongsDataAccess.java
@@ -0,0 +1,39 @@
+package simpleex.ui;
+
+import java.util.Collection;
+import simpleex.core.LatLong;
+
+/**
+ * Data access methods used by the controller.
+ * @author hal
+ *
+ */
+public interface LatLongsDataAccess {
+
+  /**
+   * Gets all the (internal) LatLong objects.
+   * @return the (internal) LatLong objects
+   */
+  Collection<LatLong> getAllLatLongs();
+
+  /**
+   * Gets a specific LatLong object by index.
+   * @param num the index of the LatLong object to get
+   * @return the LatLong object at the specified index
+   */
+  LatLong getLatLong(int num);
+
+  /**
+   * Sets a the LatLong object at a specific index.
+   * @param num the index of the LatLong object to set
+   * @param latLong the new LatLong object
+   */
+  void setLatLong(int index, LatLong latLong);
+
+  /**
+   * Adds a LatLong object
+   * @param latLong the LatLong object to add
+   * @return the index where the LatLong object was added
+   */
+  int addLatLong(LatLong latLong);
+}
diff --git a/simpleexample2/fxui/src/main/java/simpleex/ui/LocalLatLongsDataAccess.java b/simpleexample2/fxui/src/main/java/simpleex/ui/LocalLatLongsDataAccess.java
new file mode 100644
index 0000000000000000000000000000000000000000..58e4069adf41286ec8e5bc20e4442063ea551765
--- /dev/null
+++ b/simpleexample2/fxui/src/main/java/simpleex/ui/LocalLatLongsDataAccess.java
@@ -0,0 +1,83 @@
+package simpleex.ui;
+
+import java.util.Collection;
+import simpleex.core.LatLong;
+import simpleex.core.LatLongs;
+
+/*
+@startuml
+class FxAppController
+class LatLongs
+class BorderPane
+class "ListView<LatLong>" as ListView
+class "fxmapcontrol.MapBase" as MapBase
+
+BorderPane *--> ListView: "left"
+BorderPane *--> MapBase: "center"
+
+FxAppController --> LatLongs: "latLongs"
+FxAppController --> MapBase: "mapView"
+FxAppController --> ListView: "locationListView"
+@enduml
+ */
+
+/**
+ * The controller for the app.
+ * @author hal
+ *
+ */
+public class LocalLatLongsDataAccess implements LatLongsDataAccess {
+
+  private LatLongs latLongs;
+
+  /**
+   * Initializes the data access.
+   */
+  public LocalLatLongsDataAccess() {
+    this(new LatLongs());
+  }
+
+  /**
+   * Initializes the data access with a specific LatLongs.
+   * @param latLongs the LatLongs object to use
+   */
+  public LocalLatLongsDataAccess(final LatLongs latLongs) {
+    this.latLongs = latLongs;
+  }
+
+  /**
+   * Gets the LatLongs objects used by the controller.
+   * @return the controller's LatLongs objects
+   */
+  public LatLongs getLatLongs() {
+    return latLongs;
+  }
+
+  /**
+   * Sets the LatLongs objects used by the controller.
+   * @param latLongs the LatLongs object to use
+   */
+  public void setLatLongs(final LatLongs latLongs) {
+    this.latLongs = latLongs;
+  }
+
+  @Override
+  public Collection<LatLong> getAllLatLongs() {
+    return getLatLongs().toList();
+  }
+
+  @Override
+  public LatLong getLatLong(final int num) {
+    return getLatLongs().getLatLong(num);
+  }
+
+  @Override
+  public void setLatLong(final int index, final LatLong latLong) {
+    getLatLongs().setLatLong(index, latLong);
+  }
+
+  @Override
+  public int addLatLong(final LatLong latLong) {
+    return getLatLongs().addLatLong(latLong);
+  }
+}
diff --git a/simpleexample2/fxui/src/main/java/simpleex/ui/RestLatLongsDataAccess.java b/simpleexample2/fxui/src/main/java/simpleex/ui/RestLatLongsDataAccess.java
new file mode 100644
index 0000000000000000000000000000000000000000..4a9b9ea436ca101ac5eb263c5ca93cc2ab0a53d9
--- /dev/null
+++ b/simpleexample2/fxui/src/main/java/simpleex/ui/RestLatLongsDataAccess.java
@@ -0,0 +1,136 @@
+package simpleex.ui;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.util.Collection;
+import java.util.Collections;
+import simpleex.core.LatLong;
+import simpleex.core.LatLongs;
+
+/*
+@startuml
+class FxAppController
+class LatLongs
+class BorderPane
+class "ListView<LatLong>" as ListView
+class "fxmapcontrol.MapBase" as MapBase
+
+BorderPane *--> ListView: "left"
+BorderPane *--> MapBase: "center"
+
+FxAppController --> LatLongs: "latLongs"
+FxAppController --> MapBase: "mapView"
+FxAppController --> ListView: "locationListView"
+@enduml
+ */
+
+/**
+ * Data access object using rest.
+ * @author hal
+ *
+ */
+public class RestLatLongsDataAccess implements LatLongsDataAccess {
+
+  private String baseUrlString;
+
+  private final ObjectMapper objectMapper;
+
+  public RestLatLongsDataAccess(final ObjectMapper objectMapper) {
+    this.objectMapper = objectMapper;
+  }
+
+  protected ObjectMapper getObjectMapper() {
+    return objectMapper;
+  }
+
+  private URI getRequestUri(final String path) {
+    try {
+      return new URI(baseUrlString + path);
+    } catch (final URISyntaxException e) {
+      throw new IllegalArgumentException(e);
+    }
+  }
+
+  @Override
+  public Collection<LatLong> getAllLatLongs() {
+    final HttpRequest request = HttpRequest.newBuilder(getRequestUri(""))
+      .GET()
+      .build();
+      try {
+        final HttpResponse<InputStream> response = HttpClient.newBuilder()
+        .build()
+        .send(request, HttpResponse.BodyHandlers.ofInputStream());
+        return getObjectMapper().readValue(response.body(), LatLongs.class).toList();
+      } catch (JsonParseException | JsonMappingException e) {
+      } catch (IOException | InterruptedException e) {
+      }
+    return Collections.emptyList();
+  }
+
+  @Override
+  public LatLong getLatLong(final int num) {
+    final HttpRequest request = HttpRequest.newBuilder(getRequestUri("/" + num))
+        .GET()
+        .build();
+      try {
+        final HttpResponse<InputStream> response = HttpClient.newBuilder()
+        .build()
+        .send(request, HttpResponse.BodyHandlers.ofInputStream());
+        return getObjectMapper().readValue(response.body(), LatLong.class);
+      } catch (JsonParseException | JsonMappingException e) {
+      } catch (IOException | InterruptedException e) {
+      }
+    return null;
+  }
+
+  @Override
+  public void setLatLong(final int index, final LatLong latLong) {
+    try {
+      final HttpRequest request = HttpRequest.newBuilder(getRequestUri("/" + index))
+          .PUT(BodyPublishers.ofString(getObjectMapper().writeValueAsString(latLong)))
+          .build();
+        final HttpResponse<InputStream> response = HttpClient.newBuilder()
+            .build()
+            .send(request, HttpResponse.BodyHandlers.ofInputStream());
+        final int realIndex = getObjectMapper().readValue(response.body(), Integer.class);
+        if (realIndex < 0) {
+          throw new IndexOutOfBoundsException(realIndex);
+        }
+    } catch (final JsonProcessingException e) {
+      throw new RuntimeException(e);
+    } catch (IOException | InterruptedException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public int addLatLong(final LatLong latLong) {
+    try {
+      final HttpRequest request = HttpRequest.newBuilder(getRequestUri(""))
+          .POST(BodyPublishers.ofString(getObjectMapper().writeValueAsString(latLong)))
+          .build();
+        final HttpResponse<InputStream> response = HttpClient.newBuilder()
+            .build()
+            .send(request, HttpResponse.BodyHandlers.ofInputStream());
+        final int realIndex = getObjectMapper().readValue(response.body(), Integer.class);
+        if (realIndex < 0) {
+          throw new IndexOutOfBoundsException(realIndex);
+        }
+    } catch (final JsonProcessingException e) {
+      throw new RuntimeException(e);
+    } catch (IOException | InterruptedException e) {
+      throw new RuntimeException(e);
+    }
+    return 0;
+  }
+}
diff --git a/simpleexample2/fxui/src/main/resources/simpleex/ui/FxApp.fxml b/simpleexample2/fxui/src/main/resources/simpleex/ui/FxApp.fxml
index 01fbe1d975b5c4b7a779cdd25b8924703f597bda..6fbd74bd9238cf1b3d635e51b4a92f4ab025fbd6 100644
--- a/simpleexample2/fxui/src/main/resources/simpleex/ui/FxApp.fxml
+++ b/simpleexample2/fxui/src/main/resources/simpleex/ui/FxApp.fxml
@@ -21,18 +21,16 @@
             fx:controller="simpleex.ui.FxAppController"
             prefHeight="750" prefWidth="1000">
     <top>
-		<VBox>
-			<MenuBar >
-				<menus>
-					<Menu text="File">
-						<items>
-							<MenuItem text="Open..." accelerator="Meta+O" onAction="#handleOpenAction"/>
-							<MenuItem text="Save"	 accelerator="Meta+S" onAction="#handleSaveAction"/>
-						</items>
-					</Menu>
-				</menus>
-			</MenuBar>
-		</VBox>
+		<MenuBar >
+			<menus>
+				<Menu text="File">
+					<items>
+						<MenuItem text="Open..." accelerator="Meta+O" onAction="#handleOpenAction"/>
+						<MenuItem text="Save"	 accelerator="Meta+S" onAction="#handleSaveAction"/>
+					</items>
+				</Menu>
+			</menus>
+		</MenuBar>
     </top>
     <left>
 		<VBox fillWidth="true">
diff --git a/simpleexample2/fxui/src/main/resources/simpleex/ui/FxAppUsingRest.fxml b/simpleexample2/fxui/src/main/resources/simpleex/ui/FxAppUsingRest.fxml
new file mode 100644
index 0000000000000000000000000000000000000000..cc2c2202158bdd29671a2aeb84eae769710069c9
--- /dev/null
+++ b/simpleexample2/fxui/src/main/resources/simpleex/ui/FxAppUsingRest.fxml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.scene.layout.BorderPane?>
+<?import javafx.scene.layout.VBox?>
+<?import javafx.scene.control.ListView?>
+<?import javafx.scene.control.Slider?>
+<?import fxmapcontrol.MapBase?>
+<?import javafx.scene.control.Button?>
+<?import javafx.scene.layout.HBox?>
+<?import javafx.scene.control.TextField?>
+<?import javafx.scene.control.Label?>
+<?import fxmapcontrol.MapTileLayer?>
+<?import fxmapcontrol.TileSource?>
+<?import javafx.scene.control.MenuBar?>
+
+<?import javafx.scene.control.Menu?>
+<?import javafx.scene.control.MenuItem?>
+<?import javafx.scene.control.SeparatorMenuItem?>
+
+<BorderPane xmlns:fx="http://javafx.com/fxml"
+            fx:controller="simpleex.ui.FxAppUsingRestController"
+            prefHeight="750" prefWidth="1000">
+    <left>
+		<VBox fillWidth="true">
+			<ListView fx:id="locationListView"/>
+			<Button text="Add location" onAction="#handleAddLocation"/>
+		</VBox>
+	</left>
+	<center>
+		<VBox>
+			<MapBase fx:id="mapView">
+				<MapTileLayer name="OpenStreetMap" minZoomLevel="0" maxZoomLevel="17">
+					<tileSource>
+						<TileSource urlFormat="http://a.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png"/>
+					<!--
+						<TileSource urlFormat="https://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png"/>
+						<TileSource urlFormat="http://opencache.statkart.no/gatekeeper/gk/gk.open_gmaps?layers=topo2&amp;zoom={z}&amp;x={x}&amp;y={y}"/>
+						<TileSource urlFormat="http://mt1.google.com/vt/lyrs=m@129&amp;hl=en&amp;s=Galileo&amp;z={z}&amp;x={x}&amp;y={y}"/>
+					-->
+					</tileSource>
+				</MapTileLayer>
+			</MapBase>
+			<Slider fx:id="zoomSlider" min="1" max="20" value="9"/>
+		</VBox>
+	</center>
+</BorderPane>
diff --git a/simpleexample2/fxui/src/test/java/simpleex/ui/AbstractFxAppTest.java b/simpleexample2/fxui/src/test/java/simpleex/ui/AbstractFxAppTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..50d163a2521a4986c39f10d5c87c28bc6a5b1f1e
--- /dev/null
+++ b/simpleexample2/fxui/src/test/java/simpleex/ui/AbstractFxAppTest.java
@@ -0,0 +1,102 @@
+package simpleex.ui;
+
+import static org.mockito.Mockito.verify;
+import fxmapcontrol.Location;
+import fxmapcontrol.MapBase;
+import java.net.URL;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.testfx.framework.junit.ApplicationTest;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.ListView;
+import javafx.stage.Stage;
+import simpleex.core.LatLong;
+
+public abstract class AbstractFxAppTest extends ApplicationTest {
+
+  /**
+   * Setup method for headless tests using monocle.
+   */
+  @BeforeClass
+  public static void headless() {
+    if (Boolean.valueOf(System.getProperty("gitlab-ci", "false"))) {
+      System.setProperty("prism.verbose", "true"); // optional
+      System.setProperty("java.awt.headless", "true");
+      System.setProperty("testfx.robot", "glass");
+      System.setProperty("testfx.headless", "true");
+      System.setProperty("glass.platform", "Monocle");
+      System.setProperty("monocle.platform", "Headless");
+      System.setProperty("prism.order", "sw");
+      System.setProperty("prism.text", "t2k");
+      System.setProperty("testfx.setup.timeout", "2500");
+    }
+  }
+
+  private AbstractFxAppController controller;
+
+  protected abstract URL getFxmlResource();
+
+  @Override
+  public void start(final Stage stage) throws Exception {
+    final FXMLLoader loader = new FXMLLoader(getFxmlResource());
+    final Parent root = loader.load();
+    setUpLatLongsDataAccess();
+    this.controller = loader.getController();
+    this.controller.setDataAccess(getDataAccess());
+    final Scene scene = new Scene(root);
+    stage.setScene(scene);
+    stage.show();
+  }
+
+  protected abstract LatLongsDataAccess getDataAccess();
+  protected abstract void setUpLatLongsDataAccess();
+
+  @Test
+  public void testController() {
+    Assert.assertNotNull(this.controller);
+  }
+
+  @Test
+  public void testLocationListView() {
+    final ListView<?> locationListView = lookup("#locationListView").query();
+    // list contains equals elements in same order
+    Assert.assertEquals(getDataAccess().getAllLatLongs(), locationListView.getItems());
+    // first list element is auto-selected
+    Assert.assertEquals(0, locationListView.getSelectionModel().getSelectedIndex());
+  }
+
+  @Test
+  public void testMapView() {
+    final MapBase mapView = lookup("#mapView").query();
+    // center of map view is approx. the first LatLong object
+    final Location center = mapView.getCenter();
+    final double epsilon = 0.000001; // round-off error
+    final LatLong latLong = getDataAccess().getLatLong(0);
+    Assert.assertEquals(latLong.getLatitude(), center.getLatitude(), epsilon);
+    Assert.assertEquals(latLong.getLongitude(), center.getLongitude(), epsilon);
+  }
+
+  @Test
+  public void testAddLocation() {
+    // needs map center
+    final Location center = ((MapBase) lookup("#mapView").query()).getCenter();
+    // add behavior for add
+    final LatLong latLong = new LatLong(center.getLatitude(), center.getLongitude());
+    // make test less sensitive to exact button text
+    final Button addLocButton = lookup(node -> node instanceof Button
+        && ((Button) node).getText().toLowerCase().startsWith("add loc")).query();
+    // click button
+    clickOn(addLocButton);
+    // clicking doesn't seem to trigger onAction handler, so the verify call will fail
+    // see https://github.com/TestFX/TestFX/issues/641
+    // it works when run from a terminal that has been granted access in the control panel
+    // System Preferences > Security & Privacy > Accessibility
+    if (Math.random() < 0.0) {
+      verify(getDataAccess()).addLatLong(latLong);
+    }
+  }
+}
diff --git a/simpleexample2/fxui/src/test/java/simpleex/ui/FxAppTest.java b/simpleexample2/fxui/src/test/java/simpleex/ui/FxAppTest.java
index db2878a0fd38c3dfb4a56812afb879a7b97e70ea..d3e091ef95ed512ea66dc3cf704b861b6316d6d7 100644
--- a/simpleexample2/fxui/src/test/java/simpleex/ui/FxAppTest.java
+++ b/simpleexample2/fxui/src/test/java/simpleex/ui/FxAppTest.java
@@ -1,111 +1,45 @@
 package simpleex.ui;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
-import fxmapcontrol.Location;
-import fxmapcontrol.MapBase;
+import java.net.URL;
 import java.util.ArrayList;
 import java.util.List;
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.testfx.framework.junit.ApplicationTest;
-import javafx.fxml.FXMLLoader;
-import javafx.scene.Parent;
-import javafx.scene.Scene;
-import javafx.scene.control.Button;
-import javafx.scene.control.ListView;
-import javafx.stage.Stage;
 import simpleex.core.LatLong;
-import simpleex.core.LatLongs;
 
-public class FxAppTest extends ApplicationTest {
+public class FxAppTest extends AbstractFxAppTest {
 
-  /**
-   * Setup method for headless tests using monocle.
-   */
-  @BeforeClass
-  public static void headless() {
-    if (Boolean.valueOf(System.getProperty("gitlab-ci", "false"))) {
-      System.setProperty("prism.verbose", "true"); // optional
-      System.setProperty("java.awt.headless", "true");
-      System.setProperty("testfx.robot", "glass");
-      System.setProperty("testfx.headless", "true");
-      System.setProperty("glass.platform", "Monocle");
-      System.setProperty("monocle.platform", "Headless");
-      System.setProperty("prism.order", "sw");
-      System.setProperty("prism.text", "t2k");
-      System.setProperty("testfx.setup.timeout", "2500");
-    }
+  @Override
+  protected URL getFxmlResource() {
+    return getClass().getResource("FxApp.fxml");
   }
 
-  private FxAppController controller;
-  private LatLongs latLongs;
+  private List<LatLong> latLongList;
+  private LatLongsDataAccess dataAccess;
 
   @Override
-  public void start(final Stage stage) throws Exception {
-    final FXMLLoader loader = new FXMLLoader(getClass().getResource("FxApp.fxml"));
-    final Parent root = loader.load();
-    this.controller = loader.getController();
-    setUpLatLongs();
-    final Scene scene = new Scene(root);
-    stage.setScene(scene);
-    stage.show();
+  protected LatLongsDataAccess getDataAccess() {
+    return dataAccess;
   }
 
-  private List<LatLong> latLongList;
-
-  private void setUpLatLongs() {
+  @Override
+  protected void setUpLatLongsDataAccess() {
     // test data
     latLongList = new ArrayList<>(List.of(new LatLong(63.1, 11.2), new LatLong(63.2, 11.0)));
-    // "mocked" (faked) LatLongs object with very specific and limited behavior
-    latLongs = mock(LatLongs.class);
+    // "mocked" (faked) LatLongsDataAccess object with very specific and limited behavior
+    dataAccess = mock(LatLongsDataAccess.class);
     // get nth LatLong object
-    when(latLongs.getLatLong(anyInt()))
-      .then(invocation -> latLongList.get(invocation.getArgument(0)));
-    // get the number of LatLong objects
-    when(latLongs.getLatLongCount()).then(invocation -> latLongList.size());
-    // iterator for LatLong objects
-    when(latLongs.iterator()).then(invocation -> latLongList.iterator());
-    controller.setLatLongs(latLongs);
-  }
-
-  @Test
-  public void testController() {
-    Assert.assertNotNull(this.controller);
-  }
-
-  @Test
-  public void testLocationListView() {
-    final ListView<?> locationListView = lookup("#locationListView").query();
-    // list contains equals elements in same order
-    Assert.assertEquals(latLongList, locationListView.getItems());
-    // first list element is auto-selected
-    Assert.assertEquals(0, locationListView.getSelectionModel().getSelectedIndex());
-  }
-
-  @Test
-  public void testMapView() {
-    final MapBase mapView = lookup("#mapView").query();
-    // center of map view is approx. the first LatLong object
-    final Location center = mapView.getCenter();
-    final double epsilon = 0.000001; // round-off error
-    Assert.assertEquals(latLongList.get(0).getLatitude(), center.getLatitude(), epsilon);
-    Assert.assertEquals(latLongList.get(0).getLongitude(), center.getLongitude(), epsilon);
-  }
-
-  @Test
-  public void testAddLocation() {
-    // needs map center
-    final Location center = ((MapBase) lookup("#mapView").query()).getCenter();
-    // add behavior for add
-    final LatLong latLong = new LatLong(center.getLatitude(), center.getLongitude());
-    when(latLongs.addLatLong(latLong)).thenReturn(2); // add center
-
-    // make test less sensitive to exact button text
-    final Button addLocButton = lookup(node -> node instanceof Button
-        && ((Button) node).getText().toLowerCase().startsWith("add loc")).query();
-    clickOn(addLocButton);
+    when(dataAccess.getLatLong(anyInt()))
+    .then(invocation -> latLongList.get(invocation.getArgument(0)));
+    // get the LatLong objects
+    when(dataAccess.getAllLatLongs()).then(invocation -> new ArrayList<>(latLongList));
+    // add center
+    when(dataAccess.addLatLong(any(LatLong.class))).then(invocation -> {
+      final int size = latLongList.size();
+      latLongList.add(invocation.getArgument(0, LatLong.class));
+      return size;
+    });
   }
 }
diff --git a/simpleexample2/fxui/src/test/resources/simpleex/ui/FxAppUsingRest.fxml b/simpleexample2/fxui/src/test/resources/simpleex/ui/FxAppUsingRest.fxml
new file mode 100644
index 0000000000000000000000000000000000000000..cc2c2202158bdd29671a2aeb84eae769710069c9
--- /dev/null
+++ b/simpleexample2/fxui/src/test/resources/simpleex/ui/FxAppUsingRest.fxml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.scene.layout.BorderPane?>
+<?import javafx.scene.layout.VBox?>
+<?import javafx.scene.control.ListView?>
+<?import javafx.scene.control.Slider?>
+<?import fxmapcontrol.MapBase?>
+<?import javafx.scene.control.Button?>
+<?import javafx.scene.layout.HBox?>
+<?import javafx.scene.control.TextField?>
+<?import javafx.scene.control.Label?>
+<?import fxmapcontrol.MapTileLayer?>
+<?import fxmapcontrol.TileSource?>
+<?import javafx.scene.control.MenuBar?>
+
+<?import javafx.scene.control.Menu?>
+<?import javafx.scene.control.MenuItem?>
+<?import javafx.scene.control.SeparatorMenuItem?>
+
+<BorderPane xmlns:fx="http://javafx.com/fxml"
+            fx:controller="simpleex.ui.FxAppUsingRestController"
+            prefHeight="750" prefWidth="1000">
+    <left>
+		<VBox fillWidth="true">
+			<ListView fx:id="locationListView"/>
+			<Button text="Add location" onAction="#handleAddLocation"/>
+		</VBox>
+	</left>
+	<center>
+		<VBox>
+			<MapBase fx:id="mapView">
+				<MapTileLayer name="OpenStreetMap" minZoomLevel="0" maxZoomLevel="17">
+					<tileSource>
+						<TileSource urlFormat="http://a.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png"/>
+					<!--
+						<TileSource urlFormat="https://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png"/>
+						<TileSource urlFormat="http://opencache.statkart.no/gatekeeper/gk/gk.open_gmaps?layers=topo2&amp;zoom={z}&amp;x={x}&amp;y={y}"/>
+						<TileSource urlFormat="http://mt1.google.com/vt/lyrs=m@129&amp;hl=en&amp;s=Galileo&amp;z={z}&amp;x={x}&amp;y={y}"/>
+					-->
+					</tileSource>
+				</MapTileLayer>
+			</MapBase>
+			<Slider fx:id="zoomSlider" min="1" max="20" value="9"/>
+		</VBox>
+	</center>
+</BorderPane>
diff --git a/simpleexample2/restapi/src/main/java/simpleex/restapi/LatLongsService.java b/simpleexample2/restapi/src/main/java/simpleex/restapi/LatLongsService.java
index c3a05eeb21d51b8f1d14e25d8d591976261311c6..220a9e8a393a36070650c5286a7803f12ba76481 100644
--- a/simpleexample2/restapi/src/main/java/simpleex/restapi/LatLongsService.java
+++ b/simpleexample2/restapi/src/main/java/simpleex/restapi/LatLongsService.java
@@ -38,18 +38,18 @@ public class LatLongsService {
     return latLongs.getLatLong(num);
   }
 
+  @POST
+  @Consumes(MediaType.APPLICATION_JSON)
+  @Produces(MediaType.APPLICATION_JSON)
+  public int addLatLong(final LatLong latLong) {
+    return this.latLongs.addLatLong(latLong);
+  }
+
   @POST
   @Consumes(MediaType.APPLICATION_JSON)
   @Produces(MediaType.APPLICATION_JSON)
   public int addLatLongs(final List<LatLong> latLongs) {
-    int result = -1;
-    for (final LatLong latLong : latLongs) {
-      final int pos = this.latLongs.addLatLong(latLong);
-      if (result < 0) {
-        result = pos;
-      }
-    }
-    return result;
+    return this.latLongs.addLatLongs(latLongs.toArray(new LatLong[latLongs.size()]));
   }
 
   @PUT