diff --git src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java
index 6db0ec3..d636de4 100644
--- src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java
+++ src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java
@@ -7,6 +7,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
+import java.net.URL;
 import java.net.URLConnection;
 import java.util.HashSet;
 import java.util.Map;
@@ -125,16 +126,21 @@ public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements
     @Override
     public void submit(ICachedLoaderListener listener) {
         boolean first = false;
-        String url = getUrl().toString();
-        if (url == null) {
+        URL url = getUrl();
+        String deduplicationKey = null;
+        if (url != null) {
+            // url might be null, for example when Bing Attribution is not loaded yet
+            deduplicationKey = url.toString();
+        }
+        if (deduplicationKey == null) {
             log.log(Level.WARNING, "No url returned for: {0}, skipping", getCacheKey());
             return;
         }
         synchronized (inProgress) {
-            Set<ICachedLoaderListener> newListeners = inProgress.get(url);
+            Set<ICachedLoaderListener> newListeners = inProgress.get(deduplicationKey);
             if (newListeners == null) {
                 newListeners = new HashSet<>();
-                inProgress.put(url, newListeners);
+                inProgress.put(deduplicationKey, newListeners);
                 first = true;
             }
             newListeners.add(listener);
@@ -161,7 +167,7 @@ public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements
     }
 
     /**
-     * 
+     *
      * @return checks if object from cache has sufficient data to be returned
      */
     protected boolean isObjectLoadable() {
@@ -170,7 +176,7 @@ public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements
     }
 
     /**
-     * 
+     *
      * @return cache object as empty, regardless of what remote resource has returned (ex. based on headers)
      */
     protected boolean cacheAsEmpty() {
@@ -263,6 +269,10 @@ public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements
         return true;
     }
 
+    /**
+     * @return true if object was successfully downloaded, false, if there was a loading failure
+     */
+
     private boolean loadObject() {
         try {
             // if we have object in cache, and host doesn't support If-Modified-Since nor If-None-Match
@@ -315,7 +325,7 @@ public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements
                     log.log(Level.FINE, "JCS - downloaded key: {0}, length: {1}, url: {2}",
                             new Object[] {getCacheKey(), raw.length, getUrl()});
                     return true;
-                } else {
+                } else  {
                     cacheData = createCacheEntry(new byte[]{});
                     cache.put(getCacheKey(), cacheData, attributes);
                     log.log(Level.FINE, "JCS - Caching empty object {0}", getUrl());
@@ -325,8 +335,7 @@ public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements
         } catch (FileNotFoundException e) {
             log.log(Level.FINE, "JCS - Caching empty object as server returned 404 for: {0}", getUrl());
             cache.put(getCacheKey(), createCacheEntry(new byte[]{}), attributes);
-            handleNotFound();
-            return true;
+            return handleNotFound();
         } catch (Exception e) {
             log.log(Level.WARNING, "JCS - Exception during download " + getUrl(), e);
         }
@@ -335,7 +344,10 @@ public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements
 
     }
 
-    protected abstract void handleNotFound();
+    /**
+     *  @return if we should treat this object as properly loaded
+     */
+    protected abstract boolean handleNotFound();
 
     protected abstract V createCacheEntry(byte[] content);
 
diff --git src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoader.java src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoader.java
index 7d40dac..d2709aa 100644
--- src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoader.java
+++ src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoader.java
@@ -18,7 +18,7 @@ import org.openstreetmap.josm.data.preferences.IntegerProperty;
 
 /**
  * @author Wiktor Niesiobędzki
- * 
+ *
  * Wrapper class that bridges between JCS cache and Tile Loaders
  *
  */
@@ -72,7 +72,7 @@ public class TMSCachedTileLoader implements TileLoader, CachedTileLoader, TileCa
 
     @Override
     public void addTile(Tile tile) {
-        createTileLoaderJob(tile).submit();
+        createTileLoaderJob(tile).getTile();
     }
 
     @Override
diff --git src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java
index 0159714..cb7754f 100644
--- src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java
+++ src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java
@@ -124,7 +124,7 @@ public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe
         if (cacheData != null) {
             byte[] content = cacheData.getContent();
             try {
-                return (content != null && content.length > 0) || cacheData.getImage() != null || cacheAsEmpty();
+                return content != null  || cacheData.getImage() != null || cacheAsEmpty();
             } catch (IOException e) {
                 log.log(Level.WARNING, "JCS TMS - error loading from cache for tile {0}: {1}", new Object[] {tile.getKey(), e.getMessage()});
             }
@@ -142,7 +142,7 @@ public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe
             tile.putValue("tile-info", "no-tile");
             return true;
         }
-        return false;
+        return false; // as there is no other cache to cache the Tile, also cache other empty requests
     }
 
     @Override
@@ -158,7 +158,7 @@ public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe
     @Override
     public void loadingFinished(CacheEntry object, boolean success) {
         try {
-            loadTile(object);
+            loadTile(object, success);
             if (listener != null) {
                 listener.tileLoadingFinished(tile, success);
             }
@@ -177,7 +177,7 @@ public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe
      * @return tile or null, if nothing (useful) was found in cache
      */
     public Tile getCachedTile() {
-        BufferedImageCacheEntry data = super.get();
+        BufferedImageCacheEntry data = get();
         if (isObjectLoadable()) {
             try {
                 loadTile(data);
@@ -192,7 +192,8 @@ public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe
         }
     }
 
-    private void loadTile(CacheEntry object) throws IOException {
+    // loads tile when calling back from cache
+    private void loadTile(CacheEntry object, boolean success) throws IOException {
         tile.finishLoading();
         if (object != null) {
             byte[] content = object.getContent();
@@ -200,11 +201,15 @@ public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe
                 tile.loadImage(new ByteArrayInputStream(content));
             }
         }
+        if (!success) {
+            tile.setError("Problem loading tile");
+        }
     }
 
+    // loads tile when geting stright from cache
     private void loadTile(BufferedImageCacheEntry object) throws IOException {
         tile.finishLoading();
-        if (object != null) {
+        if (cacheAsEmpty() || object != null) { // if cache as empty object, do not try to load image
             if (object.getImage() != null) {
                 tile.setImage(object.getImage());
             }
@@ -212,11 +217,17 @@ public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe
     }
 
     @Override
-    protected void handleNotFound() {
+    protected boolean handleNotFound() {
         tile.setError("No tile at this zoom level");
         tile.putValue("tile-info", "no-tile");
+        return true;
     }
 
+    /**
+     * For TMS use BaseURL as settings discovery, so for different paths, we will have different settings (useful for developer servers)
+     *
+     * @return base URL of TMS or server url as defined in super class
+     */
     @Override
     protected String getServerKey() {
         TileSource ts = tile.getSource();
