diff --git data/maps.xsd data/maps.xsd
index d7b8c5d..2643b2d 100644
--- data/maps.xsd
+++ data/maps.xsd
@@ -670,6 +670,12 @@
 									</xs:all>
 								</xs:complexType>
 							</xs:element>
+							<xs:element name="no-tile-header" minOccurs="0" maxOccurs="unbounded">
+								<xs:complexType>
+									<xs:attribute name="name" type="xs:string" />
+									<xs:attribute name="value" type="xs:string" />
+								</xs:complexType>
+							</xs:element>
 						</xs:choice>
 					</xs:sequence>
 					<xs:attribute name="last-check" type="xs:date" use="optional" />
diff --git src/org/openstreetmap/gui/jmapviewer/Tile.java src/org/openstreetmap/gui/jmapviewer/Tile.java
index edd6554..bfd1eaa 100644
--- src/org/openstreetmap/gui/jmapviewer/Tile.java
+++ src/org/openstreetmap/gui/jmapviewer/Tile.java
@@ -331,4 +331,12 @@ public class Tile {
         loading = false;
         loaded = true;
     }
+
+    /**
+     *
+     * @return TileSource from which this tile comes
+     */
+    public TileSource getTileSource() {
+        return source;
+    }
 }
diff --git src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java
index b8680e8..d53d1d2 100644
--- src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java
+++ src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java
@@ -2,6 +2,8 @@
 package org.openstreetmap.gui.jmapviewer.interfaces;
 
 import java.io.IOException;
+import java.util.List;
+import java.util.Map;
 
 import org.openstreetmap.gui.jmapviewer.JMapViewer;
 
@@ -155,4 +157,15 @@ public interface TileSource extends Attributed {
      * @return [MIN_LAT..MAX_LAT]
      */
     double tileYToLat(int y, int zoom);
+
+    /**
+     * Determines, if the returned data from TileSource represent "no tile at this zoom level" situation. Detection
+     * algorithms differ per TileSource, so each TileSource should implement each own specific way.
+     *
+     * @param headers HTTP headers from response from TileSource server
+     * @param statusCode HTTP status code
+     * @param content byte array representing the data returned from the server
+     * @return true, if "no tile at this zoom level" situation detected
+     */
+    public boolean isNoTileAtZoom(Map<String, List<String>> headers, int statusCode, byte[] content);
 }
diff --git src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractOsmTileSource.java src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractOsmTileSource.java
index 90e892c..e3762f4 100644
--- src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractOsmTileSource.java
+++ src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractOsmTileSource.java
@@ -6,7 +6,7 @@ import java.awt.Image;
 import org.openstreetmap.gui.jmapviewer.Coordinate;
 
 /**
- * Abstract clas for OSM Tile sources
+ * Abstract class for OSM Tile sources
  */
 public abstract class AbstractOsmTileSource extends AbstractTMSTileSource {
 
@@ -23,9 +23,11 @@ public abstract class AbstractOsmTileSource extends AbstractTMSTileSource {
      * are safe for file names; can be null
      */
     public AbstractOsmTileSource(String name, String base_url, String id) {
-        super(name, base_url, id);
+        super(new TileSourceInfo(name, base_url, id));
+
     }
 
+    @Override
     public int getMaxZoom() {
         return 19;
     }
diff --git src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTMSTileSource.java src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTMSTileSource.java
index e3cfd19..027b550 100644
--- src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTMSTileSource.java
+++ src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTMSTileSource.java
@@ -2,6 +2,9 @@
 package org.openstreetmap.gui.jmapviewer.tilesources;
 
 import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 
 import org.openstreetmap.gui.jmapviewer.OsmMercator;
 
@@ -10,14 +13,16 @@ public abstract class AbstractTMSTileSource extends AbstractTileSource {
     protected String name;
     protected String baseUrl;
     protected String id;
+    private Map<String, String> noTileHeaders;
 
-    public AbstractTMSTileSource(String name, String base_url, String id) {
-        this.name = name;
-        this.baseUrl = base_url;
+    public AbstractTMSTileSource(TileSourceInfo info) {
+        this.name = info.getName();
+        this.baseUrl = info.getUrl();
         if(baseUrl.endsWith("/")) {
             baseUrl = baseUrl.substring(0,baseUrl.length()-1);
         }
-        this.id = id;
+        this.id = info.getUrl();
+        this.noTileHeaders = info.getNoTileHeaders();
     }
 
     @Override
@@ -122,4 +127,17 @@ public abstract class AbstractTMSTileSource extends AbstractTileSource {
     public double tileXToLon(int x, int zoom) {
         return OsmMercator.XToLon(x * OsmMercator.TILE_SIZE, zoom);
     }
+
+    @Override
+    public boolean isNoTileAtZoom(Map<String, List<String>> headers, int statusCode, byte[] content) {
+        if(noTileHeaders != null) {
+            for (Entry<String, String> searchEntry: noTileHeaders.entrySet()) {
+                List<String> headerVals = headers.get(searchEntry.getKey());
+                if (headerVals != null && headerVals.contains(searchEntry.getValue())) {
+                    return true;
+                }
+            }
+        }
+        return super.isNoTileAtZoom(headers, statusCode, content);
+    }
 }
diff --git src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTileSource.java src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTileSource.java
index 66456a6..bfce358 100644
--- src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTileSource.java
+++ src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTileSource.java
@@ -2,9 +2,11 @@
 package org.openstreetmap.gui.jmapviewer.tilesources;
 
 import java.awt.Image;
+import java.util.List;
+import java.util.Map;
 
-import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
 import org.openstreetmap.gui.jmapviewer.Coordinate;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
 
 abstract public class AbstractTileSource implements TileSource {
 
@@ -74,4 +76,8 @@ abstract public class AbstractTileSource implements TileSource {
         this.termsOfUseURL = termsOfUseURL;
     }
 
+    public boolean isNoTileAtZoom(Map<String, List<String>> headers, int statusCode, byte[] content) {
+        // default handler - when HTTP 404 is returned, then treat this situation as no tile at this zoom level
+        return statusCode == 404;
+    }
 }
diff --git src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java
index f8723b6..e2dce05 100644
--- src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java
+++ src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java
@@ -52,14 +52,11 @@ public class BingAerialTileSource extends AbstractTMSTileSource {
      * Constructs a new {@code BingAerialTileSource}.
      */
     public BingAerialTileSource() {
-        this("Bing");
+        super(new TileSourceInfo("Bing", null, null));
     }
 
-    /**
-     * Constructs a new {@code BingAerialTileSource}.
-     */
-    public BingAerialTileSource(String id) {
-        super("Bing Aerial Maps", "http://example.com/", id);
+    public BingAerialTileSource(TileSourceInfo info) {
+        super(info);
     }
 
     protected class Attribution {
diff --git src/org/openstreetmap/gui/jmapviewer/tilesources/ScanexTileSource.java src/org/openstreetmap/gui/jmapviewer/tilesources/ScanexTileSource.java
index be7128a..6acd716 100644
--- src/org/openstreetmap/gui/jmapviewer/tilesources/ScanexTileSource.java
+++ src/org/openstreetmap/gui/jmapviewer/tilesources/ScanexTileSource.java
@@ -45,8 +45,9 @@ public class ScanexTileSource extends TMSTileSource {
     /* IRS by default */
     private ScanexLayer Layer = ScanexLayer.IRS;
 
-    public ScanexTileSource(String name, String url, String id, int maxZoom) {
-        super(name, url, id, maxZoom);
+    public ScanexTileSource(TileSourceInfo info) {
+        super(info);
+        String url = info.getUrl();
 
         for (ScanexLayer layer : ScanexLayer.values()) {
             if (url.equalsIgnoreCase(layer.getName())) {
@@ -77,6 +78,7 @@ public class ScanexTileSource extends TMSTileSource {
         return this.Layer.getUri() + "&apikey=" + API_KEY + "&x=" + tilex + "&y=" + tiley + "&z=" + zoom;
     }
 
+    @Override
     public TileUpdate getTileUpdate() {
         return TileUpdate.IfNoneMatch;
     }
diff --git src/org/openstreetmap/gui/jmapviewer/tilesources/TMSTileSource.java src/org/openstreetmap/gui/jmapviewer/tilesources/TMSTileSource.java
index 23c792d..21f2611 100644
--- src/org/openstreetmap/gui/jmapviewer/tilesources/TMSTileSource.java
+++ src/org/openstreetmap/gui/jmapviewer/tilesources/TMSTileSource.java
@@ -1,20 +1,16 @@
 // License: GPL. For details, see Readme.txt file.
 package org.openstreetmap.gui.jmapviewer.tilesources;
 
+
 public class TMSTileSource extends AbstractTMSTileSource {
 
     protected int maxZoom;
     protected int minZoom = 0;
 
-    public TMSTileSource(String name, String url, String id, int maxZoom) {
-        super(name, url, id);
-        this.maxZoom = maxZoom;
-    }
-
-    public TMSTileSource(String name, String url, String id, int minZoom, int maxZoom) {
-        super(name, url, id);
-        this.minZoom = minZoom;
-        this.maxZoom = maxZoom;
+    public TMSTileSource(TileSourceInfo info) {
+        super(info);
+        minZoom = info.getMinZoom();
+        maxZoom = info.getMaxZoom();
     }
 
     @Override
diff --git src/org/openstreetmap/gui/jmapviewer/tilesources/TemplatedTMSTileSource.java src/org/openstreetmap/gui/jmapviewer/tilesources/TemplatedTMSTileSource.java
index 0b59c5d..4b43ff0 100644
--- src/org/openstreetmap/gui/jmapviewer/tilesources/TemplatedTMSTileSource.java
+++ src/org/openstreetmap/gui/jmapviewer/tilesources/TemplatedTMSTileSource.java
@@ -1,11 +1,11 @@
 // License: GPL. For details, see Readme.txt file.
 package org.openstreetmap.gui.jmapviewer.tilesources;
 
-import java.util.Map;
 import java.util.HashMap;
+import java.util.Map;
 import java.util.Random;
-import java.util.regex.Pattern;
 import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public class TemplatedTMSTileSource extends TMSTileSource {
 
@@ -27,20 +27,10 @@ public class TemplatedTMSTileSource extends TMSTileSource {
         PATTERN_SWITCH
     };
 
-    public TemplatedTMSTileSource(String name, String url, String id, int maxZoom) {
-        super(name, url, id, maxZoom);
-        handleTemplate();
-    }
-
-    public TemplatedTMSTileSource(String name, String url, String id, int minZoom, int maxZoom) {
-        super(name, url, id, minZoom, maxZoom);
-        handleTemplate();
-    }
-
-    public TemplatedTMSTileSource(String name, String url, String id, int minZoom, int maxZoom, String cookies) {
-        super(name, url, id, minZoom, maxZoom);
-        if (cookies != null) {
-            headers.put(COOKIE_HEADER, cookies);
+    public TemplatedTMSTileSource(TileSourceInfo info) {
+        super(info);
+        if (info.getCookies() != null) {
+            headers.put(COOKIE_HEADER, info.getCookies());
         }
         handleTemplate();
     }
diff --git src/org/openstreetmap/gui/jmapviewer/tilesources/TileSourceInfo.java src/org/openstreetmap/gui/jmapviewer/tilesources/TileSourceInfo.java
new file mode 100644
index 0000000..379c8f7
--- /dev/null
+++ src/org/openstreetmap/gui/jmapviewer/tilesources/TileSourceInfo.java
@@ -0,0 +1,104 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.gui.jmapviewer.tilesources;
+
+import java.util.Map;
+
+public class TileSourceInfo {
+    /** id for this imagery entry, optional at the moment */
+    protected String id;
+    /** URL of the imagery service */
+    protected  String url = null;
+
+    /** name of the imagery layer */
+    protected String name;
+
+    /** headers meaning, that there is no tile at this zoom level */
+    protected Map<String, String> notileHeaders;
+
+    /** minimum zoom level supported by the tile source */
+    protected int minZoom;
+
+    /** maximum zoom level supported by the tile source */
+    protected int maxZoom;
+
+    /** cookies that needs to be sent to tile source */
+    protected String cookies;
+
+
+    /**
+     * Create a TileSourceInfo class
+     *
+     * @param name
+     * @param base_url
+     * @param id
+     */
+    public TileSourceInfo(String name, String base_url, String id) {
+        this(name);
+        this.url = base_url;
+        this.id = id;
+    }
+
+    /**
+     * Create a TileSourceInfo class
+     *
+     * @param name
+     */
+    public TileSourceInfo(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Creates empty TileSourceInfo class
+     */
+    public TileSourceInfo() {
+    }
+
+    /**
+     *
+     * @return name of the tile source
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     *
+     * @return url of the tile source
+     */
+    public String getUrl() {
+        return url;
+    }
+
+    /**
+     *
+     * @return map of headers, that when set, means that this is "no tile at this zoom level" situation
+     */
+    public Map<String, String> getNoTileHeaders() {
+        return notileHeaders;
+    }
+
+    /**
+     *
+     * @return minimum zoom level supported by tile source
+     */
+    public int getMinZoom() {
+        return minZoom;
+    }
+
+    /**
+     *
+     * @return maximum zoom level supported by tile source
+     */
+    public int getMaxZoom() {
+        return maxZoom;
+    }
+
+    /**
+     *
+     * @return cookies to be sent along with request to tile source
+     */
+    public String getCookies() {
+        return cookies;
+    }
+
+}
diff --git src/org/openstreetmap/josm/data/Preferences.java src/org/openstreetmap/josm/data/Preferences.java
index a686728..fb766c7 100644
--- src/org/openstreetmap/josm/data/Preferences.java
+++ src/org/openstreetmap/josm/data/Preferences.java
@@ -13,6 +13,8 @@ import java.io.InputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.Field;
@@ -21,6 +23,7 @@ import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
@@ -37,6 +40,12 @@ import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonReader;
+import javax.json.JsonValue;
+import javax.json.JsonWriter;
 import javax.swing.JOptionPane;
 import javax.swing.UIManager;
 import javax.xml.XMLConstants;
@@ -1265,6 +1274,33 @@ public class Preferences {
         return vals;
     }
 
+    @SuppressWarnings("rawtypes")
+    private static String mapToJson(Map map) {
+        StringWriter stringWriter = new StringWriter();
+        try (JsonWriter writer = Json.createWriter(stringWriter)) {
+            JsonObjectBuilder object = Json.createObjectBuilder();
+            for(Object o: map.entrySet()) {
+                Entry e = (Entry) o;
+                object.add(e.getKey().toString(), e.getValue().toString());
+            }
+            writer.writeObject(object.build());
+        }
+        return stringWriter.toString();
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    private static Map mapFromJson(String s) {
+        Map ret = null;
+        try (JsonReader reader = Json.createReader(new StringReader(s))) {
+            JsonObject object = reader.readObject();
+            ret = new HashMap(object.size());
+            for (Entry<String, JsonValue> e: object.entrySet()) {
+                ret.put(e.getKey(), e.getValue().toString());
+            }
+        }
+        return ret;
+    }
+
     public static <T> Map<String,String> serializeStruct(T struct, Class<T> klass) {
         T structPrototype;
         try {
@@ -1284,7 +1320,12 @@ public class Preferences {
                 Object defaultFieldValue = f.get(structPrototype);
                 if (fieldValue != null) {
                     if (f.getAnnotation(writeExplicitly.class) != null || !Objects.equals(fieldValue, defaultFieldValue)) {
-                        hash.put(f.getName().replace("_", "-"), fieldValue.toString());
+                        String key = f.getName().replace("_", "-");
+                        if (fieldValue instanceof Map) {
+                            hash.put(key, mapToJson((Map) fieldValue));
+                        } else {
+                            hash.put(key, fieldValue.toString());
+                        }
                     }
                 }
             } catch (IllegalArgumentException | IllegalAccessException ex) {
@@ -1331,7 +1372,10 @@ public class Preferences {
                 }
             } else  if (f.getType() == String.class) {
                 value = key_value.getValue();
-            } else
+            } else if (f.getType().isAssignableFrom(Map.class)) {
+                value = mapFromJson(key_value.getValue());
+            }
+            else
                 throw new RuntimeException("unsupported preference primitive type");
 
             try {
diff --git src/org/openstreetmap/josm/data/cache/BufferedImageCacheEntry.java src/org/openstreetmap/josm/data/cache/BufferedImageCacheEntry.java
index 63a765f..ea9b51b 100644
--- src/org/openstreetmap/josm/data/cache/BufferedImageCacheEntry.java
+++ src/org/openstreetmap/josm/data/cache/BufferedImageCacheEntry.java
@@ -42,7 +42,7 @@ public class BufferedImageCacheEntry extends CacheEntry {
             if (img != null)
                 return img;
             byte[] content = getContent();
-            if (content != null) {
+            if (content != null && content.length > 0) {
                 img = ImageIO.read(new ByteArrayInputStream(content));
 
                 if (writtenToDisk)
diff --git src/org/openstreetmap/josm/data/cache/ICachedLoaderListener.java src/org/openstreetmap/josm/data/cache/ICachedLoaderListener.java
index fe26c09..01c5199 100644
--- src/org/openstreetmap/josm/data/cache/ICachedLoaderListener.java
+++ src/org/openstreetmap/josm/data/cache/ICachedLoaderListener.java
@@ -19,8 +19,9 @@ public interface ICachedLoaderListener {
      * LoadResult.REJECTED when job was rejected because of full queue
      *
      * @param data
+     * @param attributes
      * @param result
      */
-    public void loadingFinished(CacheEntry data, LoadResult result);
+    public void loadingFinished(CacheEntry data, CacheEntryAttributes attributes, LoadResult result);
 
 }
diff --git src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java
index b4a0fbd..a31fddb 100644
--- src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java
+++ src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java
@@ -9,6 +9,7 @@ import java.net.HttpURLConnection;
 import java.net.URL;
 import java.net.URLConnection;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Random;
 import java.util.Set;
@@ -223,7 +224,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() {
+    protected boolean cacheAsEmpty(Map<String, List<String>> headers, int statusCode, byte[] content) {
         return false;
     }
 
@@ -279,13 +280,13 @@ public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements
         }
         try {
             for (ICachedLoaderListener l: listeners) {
-                l.loadingFinished(cacheData, result);
+                l.loadingFinished(cacheData, attributes, result);
             }
         } catch (Exception e) {
             log.log(Level.WARNING, "JCS - Error while loading object from cache: {0}; {1}", new Object[]{e.getMessage(), getUrl()});
             Main.warn(e);
             for (ICachedLoaderListener l: listeners) {
-                l.loadingFinished(cacheData, LoadResult.FAILURE);
+                l.loadingFinished(cacheData, attributes, LoadResult.FAILURE);
             }
 
         }
@@ -328,7 +329,7 @@ public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements
                 log.log(Level.FINE, "JCS - cache entry verified using HEAD request: {0}", getUrl());
                 return true;
             }
-            URLConnection urlConn = getURLConnection();
+            HttpURLConnection urlConn = getURLConnection();
 
             if (isObjectLoadable()  &&
                     (now - attributes.getLastModification()) <= ABSOLUTE_EXPIRE_TIME_LIMIT) {
@@ -337,7 +338,7 @@ public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements
             if (isObjectLoadable() && attributes.getEtag() != null) {
                 urlConn.addRequestProperty("If-None-Match", attributes.getEtag());
             }
-            if (urlConn instanceof HttpURLConnection && ((HttpURLConnection)urlConn).getResponseCode() == 304) {
+            if (urlConn.getResponseCode() == 304) {
                 // If isModifiedSince or If-None-Match has been set
                 // and the server answers with a HTTP 304 = "Not Modified"
                 log.log(Level.FINE, "JCS - IfModifiedSince/Etag test: local version is up to date: {0}", getUrl());
@@ -358,13 +359,14 @@ public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements
             attributes = parseHeaders(urlConn);
 
             for (int i = 0; i < 5; ++i) {
-                if (urlConn instanceof HttpURLConnection && ((HttpURLConnection)urlConn).getResponseCode() == 503) {
+                if (urlConn.getResponseCode() == 503) {
                     Thread.sleep(5000+(new Random()).nextInt(5000));
                     continue;
                 }
                 byte[] raw = read(urlConn);
 
-                if (!cacheAsEmpty() && raw != null && raw.length > 0) {
+                if (!cacheAsEmpty(urlConn.getHeaderFields(), urlConn.getResponseCode(), raw) &&
+                        raw != null && raw.length > 0) {
                     cacheData = createCacheEntry(raw);
                     cache.put(getCacheKey(), cacheData, attributes);
                     log.log(Level.FINE, "JCS - downloaded key: {0}, length: {1}, url: {2}",
@@ -399,7 +401,6 @@ public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements
 
     private CacheEntryAttributes parseHeaders(URLConnection urlConn) {
         CacheEntryAttributes ret = new CacheEntryAttributes();
-        ret.setNoTileAtZoom("no-tile".equals(urlConn.getHeaderField("X-VE-Tile-Info")));
 
         Long lng = urlConn.getExpiration();
         if (lng.equals(0L)) {
diff --git src/org/openstreetmap/josm/data/imagery/ImageryInfo.java src/org/openstreetmap/josm/data/imagery/ImageryInfo.java
index e3c813f..608ebed 100644
--- src/org/openstreetmap/josm/data/imagery/ImageryInfo.java
+++ src/org/openstreetmap/josm/data/imagery/ImageryInfo.java
@@ -9,6 +9,7 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.TreeSet;
 import java.util.regex.Matcher;
@@ -20,6 +21,7 @@ import org.openstreetmap.gui.jmapviewer.Coordinate;
 import org.openstreetmap.gui.jmapviewer.interfaces.Attributed;
 import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTileSource;
 import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource.Mapnik;
+import org.openstreetmap.gui.jmapviewer.tilesources.TileSourceInfo;
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.data.Preferences.pref;
@@ -34,7 +36,7 @@ import org.openstreetmap.josm.tools.LanguageInfo;
  *
  * @author Frederik Ramm
  */
-public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
+public class ImageryInfo extends TileSourceInfo implements Comparable<ImageryInfo>, Attributed {
 
     /**
      * Type of imagery entry.
@@ -53,6 +55,7 @@ public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
         /** A WMS endpoint entry only stores the WMS server info, without layer, which are chosen later by the user. **/
         WMS_ENDPOINT("wms_endpoint");
 
+
         private final String typeString;
 
         private ImageryType(String urlString) {
@@ -150,16 +153,11 @@ public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
         }
     }
 
-    /** name of the imagery entry (gets translated by josm usually) */
-    private String name;
+
     /** original name of the imagery entry in case of translation call, for multiple languages English when possible */
     private String origName;
     /** (original) language of the translated name entry */
     private String langName;
-    /** id for this imagery entry, optional at the moment */
-    private String id;
-    /** URL of the imagery service */
-    private String url = null;
     /** whether this is a entry activated by default or not */
     private boolean defaultEntry = false;
     /** The data part of HTTP cookies header in case the service requires cookies to work */
@@ -198,6 +196,7 @@ public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
     /** icon used in menu */
     private String icon;
     // when adding a field, also adapt the ImageryInfo(ImageryInfo) constructor
+    private Map<String, String> noTileHeaders;
 
     /**
      * Auxiliary class to save an {@link ImageryInfo} object in the preferences.
@@ -224,6 +223,7 @@ public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
         @pref String projections;
         @pref String icon;
         @pref String description;
+        @pref Map<String, String> noTileHeaders;
 
         /**
          * Constructs a new empty WMS {@code ImageryPreferenceEntry}.
@@ -277,6 +277,9 @@ public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
                 }
                 projections = val.toString();
             }
+            if (i.noTileHeaders != null && !i.noTileHeaders.isEmpty()) {
+                noTileHeaders = i.noTileHeaders;
+            }
         }
 
         @Override
@@ -294,6 +297,7 @@ public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
      * Constructs a new WMS {@code ImageryInfo}.
      */
     public ImageryInfo() {
+        super();
     }
 
     /**
@@ -301,7 +305,7 @@ public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
      * @param name The entry name
      */
     public ImageryInfo(String name) {
-        this.name=name;
+        super(name);
     }
 
     /**
@@ -310,7 +314,7 @@ public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
      * @param url The entry extended URL
      */
     public ImageryInfo(String name, String url) {
-        this.name=name;
+        this(name);
         setExtendedUrl(url);
     }
 
@@ -321,7 +325,7 @@ public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
      * @param eulaAcceptanceRequired The EULA URL
      */
     public ImageryInfo(String name, String url, String eulaAcceptanceRequired) {
-        this.name=name;
+        this(name);
         setExtendedUrl(url);
         this.eulaAcceptanceRequired = eulaAcceptanceRequired;
     }
@@ -336,7 +340,7 @@ public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
      * @throws IllegalArgumentException if type refers to an unknown imagery type
      */
     public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies) {
-        this.name=name;
+        this(name);
         setExtendedUrl(url);
         ImageryType t = ImageryType.fromString(type);
         this.cookies=cookies;
@@ -348,6 +352,11 @@ public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
         }
     }
 
+    public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies, String id) {
+        this(name, url, type, eulaAcceptanceRequired, cookies);
+        setId(id);
+    }
+
     /**
      * Constructs a new {@code ImageryInfo} from an imagery preference entry.
      * @param e The imagery preference entry
@@ -389,6 +398,9 @@ public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
         termsOfUseURL = e.terms_of_use_url;
         countryCode = e.country_code;
         icon = e.icon;
+        if (e.noTileHeaders != null) {
+            noTileHeaders = e.noTileHeaders;
+        }
     }
 
     /**
@@ -687,6 +699,7 @@ public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
      * Returns the entry name.
      * @return The entry name
      */
+    @Override
     public String getName() {
         return this.name;
     }
@@ -758,6 +771,7 @@ public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
      * Returns the entry URL.
      * @return The entry URL
      */
+    @Override
     public String getUrl() {
         return this.url;
     }
@@ -790,6 +804,7 @@ public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
      * Return the data part of HTTP cookies header in case the service requires cookies to work
      * @return the cookie data part
      */
+    @Override
     public String getCookies() {
         return this.cookies;
     }
@@ -802,6 +817,7 @@ public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
      * Returns the maximum zoom level.
      * @return The maximum zoom level
      */
+    @Override
     public int getMaxZoom() {
         return this.defaultMaxZoom;
     }
@@ -810,6 +826,7 @@ public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
      * Returns the minimum zoom level.
      * @return The minimum zoom level
      */
+    @Override
     public int getMinZoom() {
         return this.defaultMinZoom;
     }
@@ -1025,4 +1042,13 @@ public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
         Capabilities capabilities = OsmApi.getOsmApi().getCapabilities();
         return capabilities != null && capabilities.isOnImageryBlacklist(this.url);
     }
+
+    public void setNoTileHeaders(Map<String, String> noTileHeaders) {
+       this.noTileHeaders = noTileHeaders;
+    }
+
+    @Override
+    public Map<String, String> getNoTileHeaders() {
+        return noTileHeaders;
+    }
 }
diff --git src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java
index 96f8b50..f641d17 100644
--- src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java
+++ src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java
@@ -148,6 +148,16 @@ public class ImageryLayerInfo {
         Collection<String> knownDefaults = Main.pref.getCollection("imagery.layers.default");
         Collection<String> newKnownDefaults = new TreeSet<>(knownDefaults);
         for (ImageryInfo def : defaultLayers) {
+            // temporary migration code, so all user preferences will get updated with new settings from JOSM site
+            if(def.getNoTileHeaders() != null) {
+                for(ImageryInfo i: layers) {
+                    if (isSimilar(def,  i)) {
+                        i.setNoTileHeaders(def.getNoTileHeaders());
+                        changed = true;
+                    }
+                }
+            }
+
             if (def.isDefaultEntry()) {
                 boolean isKnownDefault = false;
                 for (String url : knownDefaults) {
diff --git src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java
index a627c90..9a473d2 100644
--- src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java
+++ src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java
@@ -5,6 +5,7 @@ import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.net.URL;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -26,6 +27,7 @@ import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTMSTileSource;
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
 import org.openstreetmap.josm.data.cache.CacheEntry;
+import org.openstreetmap.josm.data.cache.CacheEntryAttributes;
 import org.openstreetmap.josm.data.cache.ICachedLoaderListener;
 import org.openstreetmap.josm.data.cache.JCSCachedTileLoaderJob;
 import org.openstreetmap.josm.data.preferences.IntegerProperty;
@@ -193,7 +195,7 @@ public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe
         if (cacheData != null) {
             byte[] content = cacheData.getContent();
             try {
-                return content != null  || cacheData.getImage() != null || cacheAsEmpty();
+                return content != null  || cacheData.getImage() != null || isNoTileAtZoom();
             } catch (IOException e) {
                 log.log(Level.WARNING, "JCS TMS - error loading from cache for tile {0}: {1}", new Object[] {tile.getKey(), e.getMessage()});
             }
@@ -202,12 +204,19 @@ public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe
     }
 
     private boolean isNoTileAtZoom() {
+        if (attributes == null) {
+            log.warning("Cache attributes are null");
+        }
         return attributes != null && attributes.isNoTileAtZoom();
     }
 
     @Override
-    protected boolean cacheAsEmpty() {
-        return isNoTileAtZoom();
+    protected boolean cacheAsEmpty(Map<String, List<String>> headers, int statusCode, byte[] content) {
+        if (tile.getTileSource().isNoTileAtZoom(headers, statusCode, content)) {
+            attributes.setNoTileAtZoom(true);
+            return true;
+        }
+        return false;
     }
 
     private boolean handleNoTileAtZoom() {
@@ -241,7 +250,8 @@ public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe
     }
 
     @Override
-    public void loadingFinished(CacheEntry object, LoadResult result) {
+    public void loadingFinished(CacheEntry object, CacheEntryAttributes attributes, LoadResult result) {
+        this.attributes = attributes; // as we might get notification from other object than our selfs, pass attributes along
         Set<TileLoaderListener> listeners;
         synchronized (inProgress) {
             listeners = inProgress.remove(getCacheKey());
@@ -315,10 +325,13 @@ public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe
 
     @Override
     protected boolean handleNotFound() {
+        if (tile.getSource().isNoTileAtZoom(null, 404, null)) {
             tile.setError("No tile at this zoom level");
             tile.putValue("tile-info", "no-tile");
             return true;
         }
+        return false;
+    }
 
     /**
      * For TMS use BaseURL as settings discovery, so for different paths, we will have different settings (useful for developer servers)
diff --git src/org/openstreetmap/josm/gui/layer/TMSLayer.java src/org/openstreetmap/josm/gui/layer/TMSLayer.java
index c749488..5e2073d 100644
--- src/org/openstreetmap/josm/gui/layer/TMSLayer.java
+++ src/org/openstreetmap/josm/gui/layer/TMSLayer.java
@@ -277,8 +277,8 @@ public class TMSLayer extends ImageryLayer implements ImageObserver, TileLoaderL
 
     private static class CachedAttributionBingAerialTileSource extends BingAerialTileSource {
 
-        public CachedAttributionBingAerialTileSource(String id) {
-            super(id);
+        public CachedAttributionBingAerialTileSource(ImageryInfo info) {
+            super(info);
         }
 
         class BingAttributionData extends CacheCustomContent<IOException> {
@@ -336,14 +336,15 @@ public class TMSLayer extends ImageryLayer implements ImageObserver, TileLoaderL
     public static TileSource getTileSource(ImageryInfo info) {
         if (info.getImageryType() == ImageryType.TMS) {
             checkUrl(info.getUrl());
-            TMSTileSource t = new TemplatedTMSTileSource(info.getName(), info.getUrl(), info.getId(), info.getMinZoom(), info.getMaxZoom(),
-                    info.getCookies());
+            TMSTileSource t = new TemplatedTMSTileSource(info);
             info.setAttribution(t);
             return t;
-        } else if (info.getImageryType() == ImageryType.BING)
-            return new CachedAttributionBingAerialTileSource(info.getId());
-        else if (info.getImageryType() == ImageryType.SCANEX) {
-            return new ScanexTileSource(info.getName(), info.getUrl(), info.getId(), info.getMaxZoom());
+        } else if (info.getImageryType() == ImageryType.BING) {
+            //return new CachedAttributionBingAerialTileSource(info.getId());
+            return new CachedAttributionBingAerialTileSource(info);
+        } else if (info.getImageryType() == ImageryType.SCANEX) {
+            //return new ScanexTileSource(info.getName(), info.getUrl(), info.getId(), info.getMaxZoom());
+            return new ScanexTileSource(info);
         }
         return null;
     }
diff --git src/org/openstreetmap/josm/io/imagery/ImageryReader.java src/org/openstreetmap/josm/io/imagery/ImageryReader.java
index 6978c5f..15ed396 100644
--- src/org/openstreetmap/josm/io/imagery/ImageryReader.java
+++ src/org/openstreetmap/josm/io/imagery/ImageryReader.java
@@ -5,7 +5,9 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Stack;
 
@@ -38,6 +40,7 @@ public class ImageryReader {
         CODE,
         BOUNDS,
         SHAPE,
+        NO_TILE,
         UNKNOWN,            // element is not recognized in the current context
     }
 
@@ -83,6 +86,7 @@ public class ImageryReader {
         // language of last element, does only work for simple ENTRY_ATTRIBUTE's
         private String lang;
         private List<String> projections;
+        private Map<String, String> noTileHeaders;
 
         @Override
         public void startDocument() {
@@ -94,6 +98,7 @@ public class ImageryReader {
             entry = null;
             bounds = null;
             projections = null;
+            noTileHeaders = null;
         }
 
         @Override
@@ -149,6 +154,10 @@ public class ImageryReader {
                 } else if ("projections".equals(qName)) {
                     projections = new ArrayList<>();
                     newState = State.PROJECTIONS;
+                } else if ("no-tile-header".equals(qName)) {
+                    noTileHeaders = new HashMap<>();
+                    noTileHeaders.put(atts.getValue("name"), atts.getValue("value"));
+                    newState = State.NO_TILE;
                 }
                 break;
             case BOUNDS:
@@ -307,6 +316,11 @@ public class ImageryReader {
                 entry.setServerProjections(projections);
                 projections = null;
                 break;
+            case NO_TILE:
+                entry.setNoTileHeaders(noTileHeaders);
+                noTileHeaders = null;
+                break;
+
             }
         }
     }
