diff --git a/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java b/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java
index 47447f9..029a445 100644
--- a/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java
+++ b/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java
@@ -9,11 +9,12 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.FutureTask;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import java.util.regex.Pattern;
 
 import javax.imageio.ImageIO;
@@ -43,7 +44,7 @@
 public class BingAerialTileSource extends TMSTileSource {
 
     private static final String API_KEY = "Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU";
-    private static volatile Future<List<Attribution>> attributions; // volatile is required for getAttribution(), see below.
+    private static volatile List<Attribution> attributions; // volatile is required for getAttribution(), see below.
     private static String imageUrlTemplate;
     private static Integer imageryZoomMax;
     private static String[] subdomains;
@@ -52,6 +53,7 @@
     private static final Pattern quadkeyPattern = Pattern.compile("\\{quadkey\\}");
     private static final Pattern culturePattern = Pattern.compile("\\{culture\\}");
     private String brandLogoUri;
+    private ExecutorService executor;
 
     /**
      * Constructs a new {@code BingAerialTileSource}.
@@ -234,45 +236,37 @@ public String getTermsOfUseURL() {
         return "http://opengeodata.org/microsoft-imagery-details";
     }
 
-    protected Callable<List<Attribution>> getAttributionLoaderCallable() {
-        return new Callable<List<Attribution>>() {
-
-            @Override
-            public List<Attribution> call() throws Exception {
-                int waitTimeSec = 1;
-                while (true) {
-                    try {
-                        InputSource xml = new InputSource(getAttributionUrl().openStream());
-                        List<Attribution> r = parseAttributionText(xml);
-                        return r;
-                    } catch (IOException ex) {
-                        System.err.println("Could not connect to Bing API. Will retry in " + waitTimeSec + " seconds.");
-                        Thread.sleep(TimeUnit.SECONDS.toMillis(waitTimeSec));
-                        waitTimeSec *= 2;
-                    }
-                }
-            }
-        };
-    }
-
     protected List<Attribution> getAttribution() {
         if (attributions == null) {
             // see http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
             synchronized (BingAerialTileSource.class) {
                 if (attributions == null) {
-                  final FutureTask<List<Attribution>> loader = new FutureTask<>(getAttributionLoaderCallable());
-                  new Thread(loader, "bing-attribution-loader").start();
-                  attributions = loader;
+                  final Supplier<List<Attribution>> loader = new Supplier<List<Attribution>>() {
+
+                      @Override
+                      public List<Attribution> get() {
+                          int waitTimeSec = 1;
+                          while (true) {
+                              try {
+                                  InputSource xml = new InputSource(getAttributionUrl().openStream());
+                                  List<Attribution> r = parseAttributionText(xml);
+                                  return r;
+                              } catch (IOException ex) {
+                                  System.err.println("Could not connect to Bing API. Will retry in " + waitTimeSec + " seconds.");
+                                  try {
+                                      Thread.sleep(TimeUnit.SECONDS.toMillis(waitTimeSec));
+                                  } catch (InterruptedException ex1) {
+                                      Logger.getLogger(BingAerialTileSource.class.getName()).log(Level.SEVERE, null, ex1);
+                                  }
+                                  waitTimeSec *= 2;
+                              }
+                          }
+                      }
+                  };
+                  CompletableFuture.supplyAsync(loader, executor).thenAccept(attr -> attributions = attr);
                 }
             }
         }
-        try {
-            return attributions.get();
-        } catch (ExecutionException ex) {
-            throw new RuntimeException(ex.getCause());
-        } catch (InterruptedException ign) {
-            System.err.println("InterruptedException: " + ign.getMessage());
-        }
         return null;
     }
 
