Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java	(revision 27507)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java	(revision 27519)
@@ -11,4 +11,5 @@
 import java.util.Locale;
 import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
@@ -36,5 +37,5 @@
 public class BingAerialTileSource extends AbstractTMSTileSource {
     private static String API_KEY = "Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU";
-    private static Future<List<Attribution>> attributions;
+    private static volatile Future<List<Attribution>> attributions; // volatile is required for getAttribution(), see below.
     private static String imageUrlTemplate;
     private static Integer imageryZoomMax;
@@ -59,4 +60,7 @@
     @Override
     public String getTileUrl(int zoom, int tilex, int tiley) throws IOException {
+        // make sure that attribution is loaded. otherwise subdomains is null.
+        getAttribution();
+
         int t = (zoom + tilex + tiley) % subdomains.length;
         String subdomain = subdomains[t];
@@ -212,16 +216,28 @@
     }
 
+    protected List<Attribution> getAttribution() {
+        if (attributions == null) {
+            // see http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
+            synchronized (BingAerialTileSource.class) {
+                if (attributions == null) {
+                    attributions = Executors.newSingleThreadExecutor().submit(getAttributionLoaderCallable());
+                }
+            }
+        }
+        try {
+            return attributions.get(1000, TimeUnit.MILLISECONDS);
+        } catch (TimeoutException ex) {
+            System.err.println("Bing: attribution data is not yet loaded.");
+        } catch (ExecutionException ex) {
+            throw new RuntimeException(ex.getCause());
+        } catch (InterruptedException ign) {
+        }
+        return null;
+    }
+
     @Override
     public String getAttributionText(int zoom, Coordinate topLeft, Coordinate botRight) {
-        if (attributions == null) {
-            attributions = Executors.newSingleThreadExecutor().submit(getAttributionLoaderCallable());
-        }
-        try {
-            final List<Attribution> data;
-            try {
-                data = attributions.get(1000, TimeUnit.MILLISECONDS);
-            } catch (TimeoutException ex) {
-                return "Loading Bing attribution data...";
-            }
+        try {
+            final List<Attribution> data = getAttribution();
             if (data == null) {
                 return "Error loading Bing attribution data";
