Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java	(revision 31051)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java	(revision 31052)
@@ -49,6 +49,6 @@
     private static final Charset TAGS_CHARSET = Charset.forName("UTF-8");
 
-    public static final long FILE_AGE_ONE_DAY = 1000 * 60 * 60 * 24;
-    public static final long FILE_AGE_ONE_WEEK = FILE_AGE_ONE_DAY * 7;
+    private static final long DEFAULT_EXPIRES_TIME = 1000 * 60 * 60 * 24 * 7;
+
 
     protected String cacheDirBase;
@@ -57,5 +57,4 @@
 
     protected long maxCacheFileAge = Long.MAX_VALUE;  // max. age not limited
-    protected long recheckAfter = FILE_AGE_ONE_WEEK;
 
     public static File getDefaultCacheDir() throws SecurityException {
@@ -67,5 +66,5 @@
             log.log(Level.WARNING,
                     "Failed to access system property ''java.io.tmpdir'' for security reasons. Exception was: "
-                    + e.toString());
+                        + e.toString());
             throw e; // rethrow
         }
@@ -157,5 +156,7 @@
             tileCacheDir = getSourceCacheDir(tile.getSource());
 
-            if (loadTileFromFile(recheckAfter)) {
+            loadTagsFromFile();
+
+            if (isTileFileValid() && (isNoTileAtZoom() || loadTileFromFile())) {
                 log.log(Level.FINE, "TMS - found in tile cache: {0}", tile);
                 tile.setLoaded(true);
@@ -163,4 +164,5 @@
                 return;
             }
+
             TileJob job = new TileJob() {
 
@@ -172,5 +174,5 @@
                     } else {
                         // failed to download - use old cache file if available
-                        if (loadTileFromFile(maxCacheFileAge)) {
+                        if (isNoTileAtZoom() || loadTileFromFile()) {
                             tile.setLoaded(true);
                             tile.error = false;
@@ -243,8 +245,7 @@
                         break;
                     }
-                    if (loadTileFromFile(maxCacheFileAge)) {
-                        tileFile.setLastModified(now);
-                        return true;
-                    }
+                    loadTileFromFile();
+                    tileFile.setLastModified(now);
+                    return true;
                 }
 
@@ -286,33 +287,57 @@
         }
 
-        protected boolean loadTileFromFile(long maxAge) {
+        protected boolean isTileFileValid() {
+            Long expires = null;
             try {
-                tileFile = getTileFile();
-                if (!tileFile.exists())
+                expires = Long.parseLong(tile.getValue("expires"));
+            } catch (NumberFormatException e) {}
+
+            if(expires != null && !expires.equals(0L)) {
+                // check by expire date set by server
+                if (now > expires) {
+                    log.log(Level.FINE, "TMS - Tile has expired -> not valid {0}", tile);
                     return false;
-                loadTagsFromFile();
-
-                fileMtime = tileFile.lastModified();
-                if (now - fileMtime > maxAge)
-                    return false;
-
-                if ("no-tile".equals(tile.getValue("tile-info"))) {
-                    tile.setError("No tile at this zoom level");
-                    if (tileFile.exists()) {
-                        tileFile.delete();
-                    }
-                    tileFile = null;
-                } else {
-                    try (FileInputStream fin = new FileInputStream(tileFile)) {
-                        if (fin.available() == 0)
-                            throw new IOException("File empty");
-                        tile.loadImage(fin);
-                    }
-                }
+                }
+            } else {
+                // check by file modification date
+                if (tileFile.exists()) {
+                    // check for modification date only when tile file exists
+                    // so handle properly the case, when only tags file exists
+                    fileMtime = tileFile.lastModified();
+                    if (now - fileMtime > DEFAULT_EXPIRES_TIME) {
+                        log.log(Level.FINE, "TMS - Tile has expired, maximum file age reached {0}", tile);
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+
+        protected boolean isNoTileAtZoom() {
+            if ("no-tile".equals(tile.getValue("tile-info"))) {
+                // do not remove file - keep the information, that there is no tile, for further requests
+                // the code above will check, if this information is still valid
+                log.log(Level.FINE, "TMS - Tile valid, but no file, as no tiles at this level {0}", tile);
+                tile.setError("No tile at this zoom level");
                 return true;
-
+            }
+            return false;
+        }
+
+        protected boolean loadTileFromFile() {
+            if (!tileFile.exists())
+                return false;
+
+            try (FileInputStream fin = new FileInputStream(tileFile)) {
+                if (fin.available() == 0)
+                    throw new IOException("File empty");
+                tile.loadImage(fin);
+                return true;
             } catch (Exception e) {
                 log.log(Level.WARNING, "TMS - Error while loading image from tile cache: {0}; {1}", new Object[]{e.getMessage(), tile});
                 tileFile.delete();
+                File tileMetaData = getTagsFile();
+                if (tileMetaData.exists())
+                    tileMetaData.delete();
                 tileFile = null;
                 fileMtime = null;
@@ -405,7 +430,5 @@
             File file = getTileFile();
             file.getParentFile().mkdirs();
-            try (
-                FileOutputStream f = new FileOutputStream(file)
-            ) {
+            try (FileOutputStream f = new FileOutputStream(file)) {
                 f.write(rawData);
             } catch (Exception e) {
@@ -430,5 +453,6 @@
         }
 
-        protected void loadTagsFromFile() {
+        protected boolean loadTagsFromFile() {
+            tileFile = getTileFile();
             File tagsFile = getTagsFile();
             try (BufferedReader f = new BufferedReader(new InputStreamReader(new FileInputStream(tagsFile), TAGS_CHARSET))) {
@@ -445,4 +469,6 @@
                 System.err.println("Failed to load tile tags: " + e.getLocalizedMessage());
             }
+
+            return true;
         }
     }
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java	(revision 31051)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java	(revision 31052)
@@ -109,4 +109,25 @@
             tile.putValue("tile-info", str);
         }
+
+        Long lng = urlConn.getExpiration();
+        if (lng.equals(0L)) {
+            try {
+                str = urlConn.getHeaderField("Cache-Control");
+                if (str != null) {
+                    for (String token: str.split(",")) {
+                        if (token.startsWith("max-age=")) {
+                            lng = Math.min(
+                                    Long.parseLong(token.substring(8)),
+                                    86400 * 31 // cap max-age at one month
+                                    ) * 1000 +
+                                    System.currentTimeMillis();
+                        }
+                    }
+                }
+            } catch (NumberFormatException e) {} //ignore malformed Cache-Control headers
+        }
+        if (!lng.equals(0L)) {
+            tile.putValue("expires", lng.toString());
+        }
     }
 
