diff --git a/src/org/openstreetmap/josm/data/Preferences.java b/src/org/openstreetmap/josm/data/Preferences.java
index 945396b..68f4cf3 100644
--- a/src/org/openstreetmap/josm/data/Preferences.java
+++ b/src/org/openstreetmap/josm/data/Preferences.java
@@ -36,10 +36,10 @@ import java.util.ResourceBundle;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
-import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.function.Predicate;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Stream;
 
 import javax.json.Json;
 import javax.json.JsonArray;
@@ -55,8 +55,11 @@ import javax.xml.stream.XMLStreamException;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.DoubleProperty;
+import org.openstreetmap.josm.data.preferences.IntegerProperty;
 import org.openstreetmap.josm.data.preferences.ListListSetting;
 import org.openstreetmap.josm.data.preferences.ListSetting;
+import org.openstreetmap.josm.data.preferences.LongProperty;
 import org.openstreetmap.josm.data.preferences.MapListSetting;
 import org.openstreetmap.josm.data.preferences.PreferencesReader;
 import org.openstreetmap.josm.data.preferences.PreferencesWriter;
@@ -67,8 +70,8 @@ import org.openstreetmap.josm.io.OnlineResource;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.openstreetmap.josm.tools.ColorHelper;
 import org.openstreetmap.josm.tools.I18n;
+import org.openstreetmap.josm.tools.ListenerList;
 import org.openstreetmap.josm.tools.MultiMap;
-import org.openstreetmap.josm.tools.SubclassFilteredCollection;
 import org.openstreetmap.josm.tools.Utils;
 import org.xml.sax.SAXException;
 
@@ -214,6 +217,13 @@ public class Preferences {
         }
     }
 
+    /**
+     * Old color interface
+     * <p>
+     * To be removed: end of 2016
+     * @deprecated Use a {@link ColorProperty} instead.
+     */
+    @Deprecated
     public interface ColorKey {
         String getColorName();
 
@@ -222,7 +232,9 @@ public class Preferences {
         Color getDefaultValue();
     }
 
-    private final CopyOnWriteArrayList<PreferenceChangedListener> listeners = new CopyOnWriteArrayList<>();
+    private final ListenerList<PreferenceChangedListener> listeners = ListenerList.create();
+
+    private final HashMap<String, ListenerList<PreferenceChangedListener>> keyListeners = new HashMap<>();
 
     /**
      * Adds a new preferences listener.
@@ -230,7 +242,7 @@ public class Preferences {
      */
     public void addPreferenceChangeListener(PreferenceChangedListener listener) {
         if (listener != null) {
-            listeners.addIfAbsent(listener);
+            listeners.addListener(listener);
         }
     }
 
@@ -239,13 +251,56 @@ public class Preferences {
      * @param listener The listener to remove
      */
     public void removePreferenceChangeListener(PreferenceChangedListener listener) {
-        listeners.remove(listener);
+        listeners.removeListener(listener);
+    }
+
+    /**
+     * Adds a listener that only listens to changes in one preference
+     * @param key The preference key to listen to
+     * @param listener The listener to add.
+     */
+    public void addKeyPreferenceChangeListener(String key, PreferenceChangedListener listener) {
+        listenersForKey(key).addListener(listener);
+    }
+
+    /**
+     * Adds a weak listener that only listens to changes in one preference
+     * @param key The preference key to listen to
+     * @param listener The listener to add.
+     */
+    public void addWeakKeyPreferenceChangeListener(String key, PreferenceChangedListener listener) {
+        listenersForKey(key).addWeakListener(listener);
+    }
+
+    private ListenerList<PreferenceChangedListener> listenersForKey(String key) {
+        ListenerList<PreferenceChangedListener> keyListener = keyListeners.get(key);
+        if (keyListener == null) {
+            keyListener = ListenerList.create();
+            keyListeners.put(key, keyListener);
+        }
+        return keyListener;
+    }
+
+    /**
+     * Removes a listener that only listens to changes in one preference
+     * @param key The preference key to listen to
+     * @param listener The listener to add.
+     */
+    public void removeKeyPreferenceChangeListener(String key, PreferenceChangedListener listener) {
+        ListenerList<PreferenceChangedListener> keyListener = keyListeners.get(key);
+        if (keyListener == null) {
+            throw new IllegalArgumentException("There are no listeners registered for " + key);
+        }
+        keyListener.removeListener(listener);
     }
 
     protected void firePreferenceChanged(String key, Setting<?> oldValue, Setting<?> newValue) {
-        PreferenceChangeEvent evt = new DefaultPreferenceChangeEvent(key, oldValue, newValue);
-        for (PreferenceChangedListener l : listeners) {
-            l.preferenceChanged(evt);
+        final PreferenceChangeEvent evt = new DefaultPreferenceChangeEvent(key, oldValue, newValue);
+        listeners.fireEvent(listener -> listener.preferenceChanged(evt));
+
+        ListenerList<PreferenceChangedListener> forKey = keyListeners.get(key);
+        if (forKey != null) {
+            forKey.fireEvent(listener -> listener.preferenceChanged(evt));
         }
     }
 
@@ -478,24 +533,49 @@ public class Preferences {
      * @return {@code true}, if something has changed (i.e. value is different than before)
      */
     public boolean put(final String key, String value) {
-        if (value != null && value.isEmpty()) {
-            value = null;
-        }
-        return putSetting(key, value == null ? null : new StringSetting(value));
+        return putSetting(key, value == null || value.isEmpty() ? null : new StringSetting(value));
     }
 
+    /**
+     * Set a boolean value for a certain setting.
+     * @param key the unique identifier for the setting
+     * @param value The new value
+     * @return {@code true}, if something has changed (i.e. value is different than before)
+     * @see BooleanProperty
+     */
     public boolean put(final String key, final boolean value) {
         return put(key, Boolean.toString(value));
     }
 
+    /**
+     * Set a boolean value for a certain setting.
+     * @param key the unique identifier for the setting
+     * @param value The new value
+     * @return {@code true}, if something has changed (i.e. value is different than before)
+     * @see IntegerProperty
+     */
     public boolean putInteger(final String key, final Integer value) {
         return put(key, Integer.toString(value));
     }
 
+    /**
+     * Set a boolean value for a certain setting.
+     * @param key the unique identifier for the setting
+     * @param value The new value
+     * @return {@code true}, if something has changed (i.e. value is different than before)
+     * @see DoubleProperty
+     */
     public boolean putDouble(final String key, final Double value) {
         return put(key, Double.toString(value));
     }
 
+    /**
+     * Set a boolean value for a certain setting.
+     * @param key the unique identifier for the setting
+     * @param value The new value
+     * @return {@code true}, if something has changed (i.e. value is different than before)
+     * @see LongProperty
+     */
     public boolean putLong(final String key, final Long value) {
         return put(key, Long.toString(value));
     }
@@ -505,16 +585,14 @@ public class Preferences {
      * @throws IOException if any I/O error occurs
      */
     public synchronized void save() throws IOException {
-        save(getPreferenceFile(),
-                new SubclassFilteredCollection<>(settingsMap.entrySet(), NO_DEFAULT_SETTINGS_ENTRY), false);
+        save(getPreferenceFile(), settingsMap.entrySet().stream().filter(NO_DEFAULT_SETTINGS_ENTRY), false);
     }
 
     public synchronized void saveDefaults() throws IOException {
-        save(getDefaultsCacheFile(), defaultsMap.entrySet(), true);
+        save(getDefaultsCacheFile(), defaultsMap.entrySet().stream(), true);
     }
 
-    protected void save(File prefFile, Collection<Entry<String, Setting<?>>> settings, boolean defaults) throws IOException {
-
+    protected void save(File prefFile, Stream<Entry<String, Setting<?>>> settings, boolean defaults) throws IOException {
         if (!defaults) {
             /* currently unused, but may help to fix configuration issues in future */
             putInteger("josm.version", Version.getInstance().getVersion());
@@ -718,11 +796,15 @@ public class Preferences {
 
     /**
      * Convenience method for accessing colour preferences.
+     * <p>
+     * To be removed: end of 2016
      *
      * @param colName name of the colour
      * @param def default value
      * @return a Color object for the configured colour, or the default value if none configured.
+     * @deprecated Use a {@link ColorProperty} instead.
      */
+    @Deprecated
     public synchronized Color getColor(String colName, Color def) {
         return getColor(colName, null, def);
     }
@@ -742,26 +824,31 @@ public class Preferences {
 
     /**
      * Returns the color for the given key.
+     * <p>
+     * To be removed: end of 2016
      * @param key The color key
      * @return the color
+     * @deprecated Use a {@link ColorProperty} instead.
      */
+    @Deprecated
     public Color getColor(ColorKey key) {
         return getColor(key.getColorName(), key.getSpecialName(), key.getDefaultValue());
     }
 
     /**
      * Convenience method for accessing colour preferences.
-     *
+     * <p>
+     * To be removed: end of 2016
      * @param colName name of the colour
      * @param specName name of the special colour settings
      * @param def default value
      * @return a Color object for the configured colour, or the default value if none configured.
+     * @deprecated Use a {@link ColorProperty} instead. You can replace this by: <code>new ColorProperty(colName, def).getChildColor(specName)</code>
      */
+    @Deprecated
     public synchronized Color getColor(String colName, String specName, Color def) {
         String colKey = ColorProperty.getColorKey(colName);
-        if (!colKey.equals(colName)) {
-            colornames.put(colKey, colName);
-        }
+        registerColor(colKey, colName);
         String colStr = specName != null ? get("color."+specName) : "";
         if (colStr.isEmpty()) {
             colStr = get("color." + colKey, ColorHelper.color2html(def, true));
@@ -773,6 +860,17 @@ public class Preferences {
         }
     }
 
+    /**
+     * Registers a color name conversion for the global color registry.
+     * @param colKey The key
+     * @param colName The name of the color.
+     */
+    public void registerColor(String colKey, String colName) {
+        if (!colKey.equals(colName)) {
+            colornames.put(colKey, colName);
+        }
+    }
+
     public synchronized Color getDefaultColor(String colKey) {
         StringSetting col = Utils.cast(defaultsMap.get("color."+colKey), StringSetting.class);
         String colStr = col == null ? null : col.getValue();
diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/PaintColors.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/PaintColors.java
index fb9f625..7406ff5 100644
--- a/src/org/openstreetmap/josm/data/osm/visitor/paint/PaintColors.java
+++ b/src/org/openstreetmap/josm/data/osm/visitor/paint/PaintColors.java
@@ -6,13 +6,13 @@ import static org.openstreetmap.josm.tools.I18n.marktr;
 import java.awt.Color;
 import java.util.List;
 
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.Preferences.ColorKey;
+import org.openstreetmap.josm.data.preferences.CachingProperty;
+import org.openstreetmap.josm.data.preferences.ColorProperty;
 import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
 import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.MapPaintSylesUpdateListener;
 import org.openstreetmap.josm.gui.mappaint.StyleSource;
 
-public enum PaintColors implements ColorKey {
+public enum PaintColors {
 
     INACTIVE(marktr("inactive"), Color.darkGray),
     SELECTED(marktr("selected"), Color.red),
@@ -33,11 +33,12 @@ public enum PaintColors implements ColorKey {
 
     private final String name;
     private final Color defaultColor;
+    private final CachingProperty<Color> property;
 
     private static volatile Color backgroundColorCache;
 
     private static final MapPaintSylesUpdateListener styleOverrideListener = new MapPaintSylesUpdateListener() {
-
+        //TODO: Listen to wireframe map mode changes.
         @Override
         public void mapPaintStylesUpdated() {
             backgroundColorCache = null;
@@ -54,33 +55,17 @@ public enum PaintColors implements ColorKey {
     }
 
     PaintColors(String name, Color defaultColor) {
+        property = new ColorProperty(name, defaultColor).cached();
         this.name = name;
         this.defaultColor = defaultColor;
     }
 
-    @Override
-    public String getColorName() {
-        return name;
-    }
-
-    @Override
     public Color getDefaultValue() {
-        return defaultColor;
-    }
-
-    @Override
-    public String getSpecialName() {
-        return null;
+        return property.getDefaultValue();
     }
 
     public Color get() {
-        return Main.pref.getColor(this);
-    }
-
-    public static void getColors() {
-        for (PaintColors c:values()) {
-            c.get();
-        }
+        return property.get();
     }
 
     public static Color getBackgroundColor() {
@@ -97,8 +82,9 @@ public enum PaintColors implements ColorKey {
             }
         }
         if (backgroundColorCache == null) {
-            backgroundColorCache = BACKGROUND.get();
+            return BACKGROUND.get();
+        } else {
+            return backgroundColorCache;
         }
-        return backgroundColorCache;
     }
 }
diff --git a/src/org/openstreetmap/josm/data/preferences/AbstractProperty.java b/src/org/openstreetmap/josm/data/preferences/AbstractProperty.java
index 1516d42..2a5bff8 100644
--- a/src/org/openstreetmap/josm/data/preferences/AbstractProperty.java
+++ b/src/org/openstreetmap/josm/data/preferences/AbstractProperty.java
@@ -2,12 +2,133 @@
 package org.openstreetmap.josm.data.preferences;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Preferences;
+import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
+import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
 
 /**
  * Captures the common functionality of preference properties
  * @param <T> The type of object accessed by this property
  */
 public abstract class AbstractProperty<T> {
+
+    private final class PreferenceChangedListenerAdapter implements PreferenceChangedListener {
+        private ValueChangeListener<? super T> listener;
+
+        public PreferenceChangedListenerAdapter(ValueChangeListener<? super T> listener) {
+            this.listener = listener;
+        }
+
+        @Override
+        public void preferenceChanged(PreferenceChangeEvent e) {
+            listener.valueChanged(new ValueChangeEvent<>(e, AbstractProperty.this));
+        }
+
+        /* (non-Javadoc)
+         * @see java.lang.Object#hashCode()
+         */
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + getOuterType().hashCode();
+            result = prime * result + ((listener == null) ? 0 : listener.hashCode());
+            return result;
+        }
+
+        /* (non-Javadoc)
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            PreferenceChangedListenerAdapter other = (PreferenceChangedListenerAdapter) obj;
+            if (!getOuterType().equals(other.getOuterType()))
+                return false;
+            if (listener == null) {
+                if (other.listener != null)
+                    return false;
+            } else if (!listener.equals(other.listener))
+                return false;
+            return true;
+        }
+
+        private AbstractProperty<T> getOuterType() {
+            return AbstractProperty.this;
+        }
+
+        @Override
+        public String toString() {
+            return "PreferenceChangedListenerAdapter [listener=" + listener + "]";
+        }
+    }
+
+    /**
+     * A listener that listens to changes in the properties value.
+     * @author michael
+     *
+     */
+    public interface ValueChangeListener<T> {
+        public void valueChanged(ValueChangeEvent<? extends T> e);
+    }
+
+    /**
+     * An event that is triggered if the value of a property changes.
+     * @author Michael Zangl
+     * @param <T>
+     * @since xxx
+     */
+    public static class ValueChangeEvent<T> {
+        private final PreferenceChangeEvent base;
+
+        private final AbstractProperty<T> source;
+
+        ValueChangeEvent(PreferenceChangeEvent base, AbstractProperty<T> source) {
+            this.base = base;
+            this.source = source;
+        }
+
+        /**
+         * Get the property that was changed
+         * @return The property.
+         */
+        public AbstractProperty<T> getProperty() {
+            return source;
+        }
+    }
+
+    /**
+     * An exception that is thrown if a preference value is invalid.
+     * @author Michael Zangl
+     */
+    public static class InvalidPreferenceValueException extends RuntimeException {
+
+        public InvalidPreferenceValueException() {
+            super();
+        }
+
+        public InvalidPreferenceValueException(String message, Throwable cause) {
+            super(message, cause);
+        }
+
+        public InvalidPreferenceValueException(String message) {
+            super(message);
+        }
+
+        public InvalidPreferenceValueException(Throwable cause) {
+            super(cause);
+        }
+    }
+
+    /**
+     * The preferences object this property is for.
+     */
+    protected final Preferences preferences;
     protected final String key;
     protected final T defaultValue;
 
@@ -18,11 +139,22 @@ public abstract class AbstractProperty<T> {
      * @since 5464
      */
     public AbstractProperty(String key, T defaultValue) {
+        // Main.pref should not change in production but may change during tests.
+        preferences = Main.pref;
         this.key = key;
         this.defaultValue = defaultValue;
     }
 
     /**
+     * Store the default value to {@link Preferences}.
+     */
+    protected void storeDefaultValue() {
+        if (getPreferences() != null) {
+            get();
+        }
+    }
+
+    /**
      * Replies the property key.
      * @return The property key
      */
@@ -35,7 +167,7 @@ public abstract class AbstractProperty<T> {
      * @return true if {@code Main.pref} contains this property.
      */
     public boolean isSet() {
-        return !Main.pref.get(key).isEmpty();
+        return !getPreferences().get(key).isEmpty();
     }
 
     /**
@@ -50,7 +182,7 @@ public abstract class AbstractProperty<T> {
      * Removes this property from JOSM preferences (i.e replace it by its default value).
      */
     public void remove() {
-        Main.pref.put(getKey(), String.valueOf(getDefaultValue()));
+        put(getDefaultValue());
     }
 
     /**
@@ -67,4 +199,80 @@ public abstract class AbstractProperty<T> {
      * @since 5464
      */
     public abstract boolean put(T value);
+
+    /**
+     * Gets the preferences used for this property.
+     * @return The preferences for this property.
+     * @since xxx
+     */
+    protected Preferences getPreferences() {
+        return preferences;
+    }
+
+    /**
+     * Adds a listener that listens only for changes to this preference key.
+     * @param listener The listener to add.
+     */
+    public void addListener(ValueChangeListener<? super T> listener) {
+        addListenerImpl(new PreferenceChangedListenerAdapter(listener));
+    }
+
+    protected void addListenerImpl(PreferenceChangedListener adapter) {
+        getPreferences().addKeyPreferenceChangeListener(getKey(), adapter);
+    }
+
+    /**
+     * Adds a weak listener that listens only for changes to this preference key.
+     * @param listener The listener to add.
+     */
+    public void addWeakListener(ValueChangeListener<? super T> listener) {
+        addWeakListenerImpl(new PreferenceChangedListenerAdapter(listener));
+    }
+
+    protected void addWeakListenerImpl(PreferenceChangedListener adapter) {
+        getPreferences().addWeakKeyPreferenceChangeListener(getKey(), adapter);
+    }
+
+    /**
+     * Removes a listener that listens only for changes to this preference key.
+     * @param listener The listener to add.
+     */
+    public void removeListener(ValueChangeListener<? super T> listener) {
+        removeListenerImpl(new PreferenceChangedListenerAdapter(listener));
+    }
+
+    protected void removeListenerImpl(PreferenceChangedListener adapter) {
+        getPreferences().removeKeyPreferenceChangeListener(getKey(), adapter);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((key == null) ? 0 : key.hashCode());
+        result = prime * result + ((preferences == null) ? 0 : preferences.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        AbstractProperty other = (AbstractProperty) obj;
+        if (key == null) {
+            if (other.key != null)
+                return false;
+        } else if (!key.equals(other.key))
+            return false;
+        if (preferences == null) {
+            if (other.preferences != null)
+                return false;
+        } else if (!preferences.equals(other.preferences))
+            return false;
+        return true;
+    }
 }
diff --git a/src/org/openstreetmap/josm/data/preferences/AbstractToStringProperty.java b/src/org/openstreetmap/josm/data/preferences/AbstractToStringProperty.java
new file mode 100644
index 0000000..6a3a0b9
--- /dev/null
+++ b/src/org/openstreetmap/josm/data/preferences/AbstractToStringProperty.java
@@ -0,0 +1,165 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.preferences;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+import org.openstreetmap.josm.tools.bugreport.BugReport;
+
+/**
+ * This class represents a property that can be represented as String.
+ *
+ * @author Michael Zangl
+ *
+ * @param <T> The property content type.
+ * @since xxx
+ */
+public abstract class AbstractToStringProperty<T> extends AbstractProperty<T> {
+
+    /**
+     * This is a version of this property that attempts to get the property with a more specialized key and - if that fails - uses the property
+     * value as default.
+     *
+     * @author Michael Zangl
+     * @param <T> The content type
+     * @since xxx
+     */
+    public static class ChildProperty<T> extends AbstractToStringProperty<T> {
+        private AbstractToStringProperty<T> parent;
+
+        ChildProperty(AbstractToStringProperty<T> parent, String key) {
+            super(key, null);
+            CheckParameterUtil.ensureParameterNotNull(parent, "parent");
+            this.parent = parent;
+        }
+
+        @Override
+        protected void storeDefaultValue() {
+            // Default value hidden in preferences.
+        }
+
+        @Override
+        public T getDefaultValue() {
+            return parent.get();
+        }
+
+        @Override
+        protected T fromString(String string) {
+            return parent.fromString(string);
+        }
+
+        @Override
+        protected String toString(T t) {
+            return parent.toString(t);
+        }
+
+        @Override
+        protected void addListenerImpl(PreferenceChangedListener adapter) {
+            super.addListenerImpl(adapter);
+            parent.addListenerImpl(adapter);
+        }
+
+        @Override
+        protected void addWeakListenerImpl(PreferenceChangedListener adapter) {
+            super.addWeakListenerImpl(adapter);
+            parent.addWeakListenerImpl(adapter);
+        }
+
+        @Override
+        protected void removeListenerImpl(PreferenceChangedListener adapter) {
+            super.removeListenerImpl(adapter);
+            parent.removeListenerImpl(adapter);
+        }
+
+        @Override
+        public CachingProperty<T> cached() {
+            throw new UnsupportedOperationException("Not implemented yet.");
+        }
+
+    }
+
+    /**
+     * Create a new property and store the default value.
+     * @param key The key
+     * @param defaultValue The default value.
+     * @see AbstractProperty#AbstractProperty(String, Object)
+     */
+    public AbstractToStringProperty(String key, T defaultValue) {
+        super(key, defaultValue);
+        storeDefaultValue();
+    }
+
+    @Override
+    public T get() {
+        String string = getAsString();
+        if (!string.isEmpty()) {
+            try {
+                return fromString(string);
+            } catch (InvalidPreferenceValueException e) {
+                Main.warn(BugReport.intercept(e).put("key", key).put("value", string));
+            }
+        }
+        return getDefaultValue();
+    }
+
+    /**
+     * Converts the string to an object of the given type.
+     * @param string The string
+     * @return The object.
+     * @throws InvalidPreferenceValueException If the value could not be converted.
+     * @since xxx
+     */
+    protected abstract T fromString(String string);
+
+    @Override
+    public boolean put(T value) {
+        String string = value == null ? null : toString(value);
+        return getPreferences().put(getKey(), string);
+    }
+
+    /**
+     * Converts the string to an object of the given type.
+     * @param t The object.
+     * @return The string representing the object
+     * @throws InvalidPreferenceValueException If the value could not be converted.
+     * @since xxx
+     */
+    protected abstract String toString(T t);
+
+    /**
+     * Gets the preference value as String.
+     * @return The string preference value.
+     */
+    protected String getAsString() {
+        T def = getDefaultValue();
+        return getPreferences().get(key, def == null ? "" : toString(def));
+    }
+
+    /**
+     * Gets a specialized setting value that has the current value as default
+     * <p>
+     * The key will be getKey().spec
+     * @param spec The key specialization
+     * @return The property
+     */
+    public AbstractToStringProperty<T> getSpecialized(String spec) {
+        return getChildProperty(getKey() + "." + spec);
+    }
+
+    /**
+     * Gets a setting that defaults to this setting if the key is not set.
+     * @param key The more specialized key.
+     * @return The new setting.
+     */
+    protected AbstractToStringProperty<T> getChildProperty(String key) {
+        return new ChildProperty<>(this, key);
+    }
+
+    /**
+     * Creates a new {@link CachingProperty} instance for this property.
+     * @return The new caching property instance.
+     */
+    public CachingProperty<T> cached() {
+        return new CachingProperty<>(this);
+    }
+}
diff --git a/src/org/openstreetmap/josm/data/preferences/BooleanProperty.java b/src/org/openstreetmap/josm/data/preferences/BooleanProperty.java
index 578edca..93ab3b1 100644
--- a/src/org/openstreetmap/josm/data/preferences/BooleanProperty.java
+++ b/src/org/openstreetmap/josm/data/preferences/BooleanProperty.java
@@ -1,12 +1,10 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.data.preferences;
 
-import org.openstreetmap.josm.Main;
-
 /**
  * A property containing a {@code Boolean} value.
  */
-public class BooleanProperty extends AbstractProperty<Boolean> {
+public class BooleanProperty extends AbstractToStringProperty<Boolean> {
 
     /**
      * Constructs a new {@code BooleanProperty}.
@@ -15,18 +13,27 @@ public class BooleanProperty extends AbstractProperty<Boolean> {
      */
     public BooleanProperty(String key, boolean defaultValue) {
         super(key, defaultValue);
-        if (Main.pref != null) {
-            get();
-        }
     }
 
     @Override
     public Boolean get() {
-        return Main.pref.getBoolean(getKey(), defaultValue);
+        // Removing this implementation breaks binary compatibility
+        return super.get();
     }
 
     @Override
     public boolean put(Boolean value) {
-        return Main.pref.put(getKey(), value);
+        // Removing this implementation breaks binary compatibility
+        return super.put(value);
+    }
+
+    @Override
+    protected Boolean fromString(String string) {
+        return Boolean.valueOf(string);
+    }
+
+    @Override
+    protected String toString(Boolean t) {
+        return t.toString();
     }
 }
diff --git a/src/org/openstreetmap/josm/data/preferences/CachedProperty.java b/src/org/openstreetmap/josm/data/preferences/CachedProperty.java
index d8e36a4..c3459e7 100644
--- a/src/org/openstreetmap/josm/data/preferences/CachedProperty.java
+++ b/src/org/openstreetmap/josm/data/preferences/CachedProperty.java
@@ -13,7 +13,7 @@ public abstract class CachedProperty<T> extends AbstractProperty<T> implements P
 
     protected CachedProperty(String key, String defaultValueAsString) {
         super(key, null);
-        Main.pref.addPreferenceChangeListener(this);
+        Main.pref.addKeyPreferenceChangeListener(key, this);
         this.defaultValueAsString = defaultValueAsString;
         updateValue();
     }
@@ -60,7 +60,7 @@ public abstract class CachedProperty<T> extends AbstractProperty<T> implements P
     }
 
     public String getAsString() {
-        return Main.pref.get(getKey(), getDefaultValueAsString());
+        return getPreferences().get(getKey(), getDefaultValueAsString());
     }
 
     @Override
diff --git a/src/org/openstreetmap/josm/data/preferences/CachingProperty.java b/src/org/openstreetmap/josm/data/preferences/CachingProperty.java
new file mode 100644
index 0000000..d8644ee
--- /dev/null
+++ b/src/org/openstreetmap/josm/data/preferences/CachingProperty.java
@@ -0,0 +1,48 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.preferences;
+
+import org.openstreetmap.josm.data.preferences.AbstractProperty.ValueChangeListener;
+
+/**
+ * This is a special wrapper of {@link AbstractProperty}. The current preference value is cached. The value is invalidated if the preference was
+ * changed.
+ * @author Michael Zangl
+ *
+ * @param <T>
+ * @since xxx
+ */
+public class CachingProperty<T> extends AbstractProperty<T> implements ValueChangeListener<T> {
+
+    private T cache;
+    private boolean cacheActive;
+    private final AbstractProperty<T> toCache;
+
+    /**
+     * Create a new caching property.
+     * @param toCache The property to cache.
+     */
+    CachingProperty(AbstractProperty<T> toCache) {
+        super(toCache.getKey(), toCache.getDefaultValue());
+        this.toCache = toCache;
+        addWeakListener(this);
+    }
+
+    @Override
+    public synchronized T get() {
+        if (!cacheActive) {
+            cache = toCache.get();
+            cacheActive = true;
+        }
+        return cache;
+    }
+
+    @Override
+    public boolean put(T value) {
+        return toCache.put(cache);
+    }
+
+    @Override
+    public synchronized void valueChanged(org.openstreetmap.josm.data.preferences.AbstractProperty.ValueChangeEvent<? extends T> e) {
+        cacheActive = false;
+    }
+}
diff --git a/src/org/openstreetmap/josm/data/preferences/CollectionProperty.java b/src/org/openstreetmap/josm/data/preferences/CollectionProperty.java
index 0b5c854..44f6864 100644
--- a/src/org/openstreetmap/josm/data/preferences/CollectionProperty.java
+++ b/src/org/openstreetmap/josm/data/preferences/CollectionProperty.java
@@ -24,11 +24,11 @@ public class CollectionProperty extends AbstractProperty<Collection<String>> {
 
     @Override
     public Collection<String> get() {
-        return Main.pref.getCollection(getKey(), getDefaultValue());
+        return getPreferences().getCollection(getKey(), getDefaultValue());
     }
 
     @Override
     public boolean put(Collection<String> value) {
-        return Main.pref.putCollection(getKey(), value);
+        return getPreferences().putCollection(getKey(), value);
     }
 }
diff --git a/src/org/openstreetmap/josm/data/preferences/ColorProperty.java b/src/org/openstreetmap/josm/data/preferences/ColorProperty.java
index b597bb2..873dba8 100644
--- a/src/org/openstreetmap/josm/data/preferences/ColorProperty.java
+++ b/src/org/openstreetmap/josm/data/preferences/ColorProperty.java
@@ -4,38 +4,73 @@ package org.openstreetmap.josm.data.preferences;
 import java.awt.Color;
 import java.util.Locale;
 
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.Preferences.ColorKey;
+import org.openstreetmap.josm.tools.ColorHelper;
 
 /**
  * A property containing a {@link Color} value.
  * @since 5464
  */
-public class ColorProperty extends AbstractProperty<Color> implements ColorKey {
+public class ColorProperty extends AbstractToStringProperty<Color> {
 
     private final String name;
 
     /**
      * Constructs a new {@code ColorProperty}.
      * @param colName The color name
+     * @param defaultValue The default value as HTML string
+     */
+    public ColorProperty(String colName, String defaultValue) {
+        this(colName, ColorHelper.html2color(defaultValue));
+    }
+
+    /**
+     * Constructs a new {@code ColorProperty}.
+     * @param colName The color name
      * @param defaultValue The default value
      */
     public ColorProperty(String colName, Color defaultValue) {
         super(getColorKey(colName), defaultValue);
         this.name = colName;
-        if (Main.pref != null) {
-            get();
-        }
+        getPreferences().registerColor(getColorKey(colName), colName);
     }
 
     @Override
     public Color get() {
-        return Main.pref.getColor(this);
+        // Removing this implementation breaks binary compatibility due to the way generics work
+        return super.get();
     }
 
     @Override
     public boolean put(Color value) {
-        return Main.pref.putColor(getColorKey(name), value);
+        // Removing this implementation breaks binary compatibility due to the way generics work
+        return super.put(value);
+    }
+
+    @Override
+    protected Color fromString(String string) {
+        return ColorHelper.html2color(string);
+    }
+
+    @Override
+    protected String toString(Color t) {
+        return ColorHelper.color2html(t, true);
+    }
+
+    /**
+     * Gets a color of which the value can be set.
+     * @param colorName the name of the color.
+     * @return The child property that inherits this value if it is not set.
+     */
+    public AbstractToStringProperty<Color> getChildColor(String colorName) {
+        return getChildProperty(getColorKey(colorName));
+    }
+
+    /**
+     * Gets the name this color was registered with.
+     * @return The name.
+     */
+    public String getName() {
+        return name;
     }
 
     /**
@@ -44,16 +79,11 @@ public class ColorProperty extends AbstractProperty<Color> implements ColorKey {
      * @return The color key for this property
      */
     public static String getColorKey(String colName) {
-        return colName == null ? null : colName.toLowerCase(Locale.ENGLISH).replaceAll("[^a-z0-9]+", ".");
-    }
-
-    @Override
-    public String getColorName() {
-        return name;
+        return colName == null ? null : "color." + colName.toLowerCase(Locale.ENGLISH).replaceAll("[^a-z0-9]+", ".");
     }
 
     @Override
-    public String getSpecialName() {
-        return null;
+    public String toString() {
+        return "ColorProperty [name=" + name + ", defaultValue=" + getDefaultValue() + "]";
     }
 }
diff --git a/src/org/openstreetmap/josm/data/preferences/DoubleProperty.java b/src/org/openstreetmap/josm/data/preferences/DoubleProperty.java
index 44ca4c2..a44066d 100644
--- a/src/org/openstreetmap/josm/data/preferences/DoubleProperty.java
+++ b/src/org/openstreetmap/josm/data/preferences/DoubleProperty.java
@@ -1,13 +1,11 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.data.preferences;
 
-import org.openstreetmap.josm.Main;
-
 /**
  * A property containing an {@code Double} value.
  * @since 3246
  */
-public class DoubleProperty extends AbstractProperty<Double> {
+public class DoubleProperty extends AbstractToStringProperty<Double> {
 
     /**
      * Constructs a new {@code DoubleProperty}.
@@ -20,12 +18,28 @@ public class DoubleProperty extends AbstractProperty<Double> {
 
     @Override
     public Double get() {
-        return Main.pref.getDouble(getKey(), getDefaultValue());
+        // Removing this implementation breaks binary compatibility
+        return super.get();
     }
 
     @Override
     public boolean put(Double value) {
-        return Main.pref.putDouble(getKey(), value);
+        // Removing this implementation breaks binary compatibility
+        return super.put(value);
+    }
+
+    @Override
+    protected Double fromString(String string) {
+        try {
+            return Double.valueOf(string);
+        } catch (NumberFormatException e) {
+            throw new InvalidPreferenceValueException(e);
+        }
+    }
+
+    @Override
+    protected String toString(Double t) {
+        return t.toString();
     }
 
     /**
diff --git a/src/org/openstreetmap/josm/data/preferences/IntegerProperty.java b/src/org/openstreetmap/josm/data/preferences/IntegerProperty.java
index f15d082..f0cb061 100644
--- a/src/org/openstreetmap/josm/data/preferences/IntegerProperty.java
+++ b/src/org/openstreetmap/josm/data/preferences/IntegerProperty.java
@@ -7,7 +7,7 @@ import org.openstreetmap.josm.Main;
  * A property containing an {@code Integer} value.
  * @since 3246
  */
-public class IntegerProperty extends AbstractProperty<Integer> {
+public class IntegerProperty extends AbstractToStringProperty<Integer> {
 
     /**
      * Constructs a new {@code IntegerProperty}.
@@ -23,14 +23,31 @@ public class IntegerProperty extends AbstractProperty<Integer> {
 
     @Override
     public Integer get() {
-        return Main.pref.getInteger(getKey(), getDefaultValue());
+        // Removing this implementation breaks binary compatibility
+        return super.get();
     }
 
     @Override
     public boolean put(Integer value) {
-        return Main.pref.putInteger(getKey(), value);
+        // Removing this implementation breaks binary compatibility
+        return super.put(value);
     }
 
+    @Override
+    protected Integer fromString(String string) {
+        try {
+            return Integer.valueOf(string);
+        } catch (NumberFormatException e) {
+            throw new InvalidPreferenceValueException(e);
+        }
+    }
+
+    @Override
+    protected String toString(Integer t) {
+        return t.toString();
+    }
+
+
     /**
      * parses and saves an integer value
      * @param value the value to be parsed
diff --git a/src/org/openstreetmap/josm/data/preferences/LongProperty.java b/src/org/openstreetmap/josm/data/preferences/LongProperty.java
index d480e9e..689f354 100644
--- a/src/org/openstreetmap/josm/data/preferences/LongProperty.java
+++ b/src/org/openstreetmap/josm/data/preferences/LongProperty.java
@@ -1,14 +1,12 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.data.preferences;
 
-import org.openstreetmap.josm.Main;
-
 /**
  * A property containing an {@code Long} value.
  * @since 10087
  *
  */
-public class LongProperty extends AbstractProperty<Long> {
+public class LongProperty extends AbstractToStringProperty<Long> {
 
     /**
      * Constructs a new {@code LongProperty}
@@ -17,19 +15,31 @@ public class LongProperty extends AbstractProperty<Long> {
      */
     public LongProperty(String key, long defaultValue) {
         super(key, defaultValue);
-        if (Main.pref != null) {
-            get();
-        }
     }
 
     @Override
     public Long get() {
-        return Main.pref.getLong(getKey(), getDefaultValue());
+        // Removing this implementation breaks binary compatibility
+        return super.get();
     }
 
     @Override
     public boolean put(Long value) {
-        return Main.pref.putLong(getKey(), value);
+        // Removing this implementation breaks binary compatibility
+        return super.put(value);
+    }
+
+    @Override
+    protected Long fromString(String string) {
+        try {
+            return Long.valueOf(string);
+        } catch (NumberFormatException e) {
+            throw new InvalidPreferenceValueException(e);
+        }
     }
 
+    @Override
+    protected String toString(Long t) {
+        return t.toString();
+    }
 }
diff --git a/src/org/openstreetmap/josm/data/preferences/PreferencesWriter.java b/src/org/openstreetmap/josm/data/preferences/PreferencesWriter.java
index f247cde..80fd8d9 100644
--- a/src/org/openstreetmap/josm/data/preferences/PreferencesWriter.java
+++ b/src/org/openstreetmap/josm/data/preferences/PreferencesWriter.java
@@ -5,6 +5,7 @@ import java.io.PrintWriter;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Stream;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.Version;
@@ -37,6 +38,15 @@ public class PreferencesWriter extends XmlWriter implements SettingVisitor {
      * @param settings preferences settings to write
      */
     public void write(Collection<Map.Entry<String, Setting<?>>> settings) {
+        write(settings.stream());
+    }
+
+    /**
+     * Write preferences.
+     *
+     * @param settings preferences settings to write as stream.
+     */
+    public void write(Stream<Map.Entry<String, Setting<?>>> settings) {
         out.write(String.format("<?xml version=\"1.0\" encoding=\"UTF-8\"?>%n"));
         String rootElement = defaults ? "preferences-defaults" : "preferences";
         out.write(String.format("<%s xmlns='%s/preferences-1.0'", rootElement, Main.getXMLBase()));
@@ -44,10 +54,10 @@ public class PreferencesWriter extends XmlWriter implements SettingVisitor {
             out.write(" xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'");
         }
         out.write(String.format(" version='%d'>%n", Version.getInstance().getVersion()));
-        for (Map.Entry<String, Setting<?>> e : settings) {
+        settings.forEachOrdered(e -> {
             setKey(e.getKey());
             e.getValue().visit(this);
-        }
+        });
         out.write(String.format("</%s>%n", rootElement));
     }
 
diff --git a/src/org/openstreetmap/josm/data/preferences/StringProperty.java b/src/org/openstreetmap/josm/data/preferences/StringProperty.java
index 25e42a1..cab4539 100644
--- a/src/org/openstreetmap/josm/data/preferences/StringProperty.java
+++ b/src/org/openstreetmap/josm/data/preferences/StringProperty.java
@@ -1,12 +1,10 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.data.preferences;
 
-import org.openstreetmap.josm.Main;
-
 /**
  * A property containing an {@code String} value.
  */
-public class StringProperty extends AbstractProperty<String> {
+public class StringProperty extends AbstractToStringProperty<String> {
 
     /**
      * Constructs a new {@code StringProperty}.
@@ -15,18 +13,27 @@ public class StringProperty extends AbstractProperty<String> {
      */
     public StringProperty(String key, String defaultValue) {
         super(key, defaultValue);
-        if (Main.pref != null) {
-            get();
-        }
     }
 
     @Override
     public String get() {
-        return Main.pref.get(getKey(), getDefaultValue());
+        // Removing this implementation breaks binary compatibility
+        return super.get();
     }
 
     @Override
     public boolean put(String value) {
-        return Main.pref.put(getKey(), value);
+        // Removing this implementation breaks binary compatibility
+        return super.put(value);
+    }
+
+    @Override
+    protected String fromString(String string) {
+        return string;
+    }
+
+    @Override
+    protected String toString(String string) {
+        return string;
     }
 }
diff --git a/src/org/openstreetmap/josm/gui/MainApplication.java b/src/org/openstreetmap/josm/gui/MainApplication.java
index b65e16c..ea120a1 100644
--- a/src/org/openstreetmap/josm/gui/MainApplication.java
+++ b/src/org/openstreetmap/josm/gui/MainApplication.java
@@ -313,6 +313,18 @@ public class MainApplication extends Main {
             I18n.set(args.get(Option.LANGUAGE).iterator().next());
         }
 
+        if (args.containsKey(Option.TRACE)) {
+            // Enable JOSM debug level
+            logLevel = 5;
+            // Enable debug in OAuth signpost via system preference, but only at trace level
+            Utils.updateSystemProperty("debug", "true");
+            Main.info(tr("Enabled detailed debug level (trace)"));
+        } else if (args.containsKey(Option.DEBUG)) {
+            // Enable JOSM debug level
+            logLevel = 4;
+            Main.info(tr("Printing debugging messages to console"));
+        }
+
         initApplicationPreferences();
 
         Policy.setPolicy(new Policy() {
@@ -344,26 +356,12 @@ public class MainApplication extends Main {
             System.exit(0);
         }
 
-        if (args.containsKey(Option.DEBUG) || args.containsKey(Option.TRACE)) {
-            // Enable JOSM debug level
-            logLevel = 4;
-            Main.info(tr("Printing debugging messages to console"));
-        }
-
         boolean skipLoadingPlugins = false;
         if (args.containsKey(Option.SKIP_PLUGINS)) {
             skipLoadingPlugins = true;
             Main.info(tr("Plugin loading skipped"));
         }
 
-        if (args.containsKey(Option.TRACE)) {
-            // Enable JOSM debug level
-            logLevel = 5;
-            // Enable debug in OAuth signpost via system preference, but only at trace level
-            Utils.updateSystemProperty("debug", "true");
-            Main.info(tr("Enabled detailed debug level (trace)"));
-        }
-
         Main.pref.init(args.containsKey(Option.RESET_PREFERENCES));
 
         if (args.containsKey(Option.SET)) {
diff --git a/src/org/openstreetmap/josm/gui/MapFrame.java b/src/org/openstreetmap/josm/gui/MapFrame.java
index 54d8c2d..7613e0d 100644
--- a/src/org/openstreetmap/josm/gui/MapFrame.java
+++ b/src/org/openstreetmap/josm/gui/MapFrame.java
@@ -759,6 +759,7 @@ public class MapFrame extends JPanel implements Destroyable, ActiveLayerChangeLi
                 selectMapMode(newMapMode, newLayer);
             } else if (mapMode != null) {
                 mapMode.exitMode(); // if new mode is null - simply exit from previous mode
+                mapMode = null;
             }
         }
         // if this is really a change (and not the first active layer)
diff --git a/src/org/openstreetmap/josm/gui/MapStatus.java b/src/org/openstreetmap/josm/gui/MapStatus.java
index 50ef27c..d59d8fe 100644
--- a/src/org/openstreetmap/josm/gui/MapStatus.java
+++ b/src/org/openstreetmap/josm/gui/MapStatus.java
@@ -64,7 +64,10 @@ import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.preferences.AbstractProperty;
+import org.openstreetmap.josm.data.preferences.BooleanProperty;
 import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.DoubleProperty;
 import org.openstreetmap.josm.gui.help.Helpful;
 import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
 import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
@@ -92,21 +95,23 @@ import org.openstreetmap.josm.tools.ImageProvider;
 public final class MapStatus extends JPanel implements Helpful, Destroyable, PreferenceChangedListener, SoMChangeListener {
 
     private final DecimalFormat DECIMAL_FORMAT = new DecimalFormat(Main.pref.get("statusbar.decimal-format", "0.0"));
-    private final double DISTANCE_THRESHOLD = Main.pref.getDouble("statusbar.distance-threshold", 0.01);
+    private static final AbstractProperty<Double> DISTANCE_THRESHOLD = new DoubleProperty("statusbar.distance-threshold", 0.01).cached();
+
+    private static final AbstractProperty<Boolean> SHOW_ID = new BooleanProperty("osm-primitives.showid", false);
 
     /**
      * Property for map status background color.
      * @since 6789
      */
     public static final ColorProperty PROP_BACKGROUND_COLOR = new ColorProperty(
-            marktr("Status bar background"), Color.decode("#b8cfe5"));
+            marktr("Status bar background"), "#b8cfe5");
 
     /**
      * Property for map status background color (active state).
      * @since 6789
      */
     public static final ColorProperty PROP_ACTIVE_BACKGROUND_COLOR = new ColorProperty(
-            marktr("Status bar background: active"), Color.decode("#aaff5e"));
+            marktr("Status bar background: active"), "#aaff5e");
 
     /**
      * Property for map status foreground color.
@@ -567,7 +572,7 @@ public final class MapStatus extends JPanel implements Helpful, Destroyable, Pre
             }
             text.append(name);
 
-            boolean idShown = Main.pref.getBoolean("osm-primitives.showid");
+            boolean idShown = SHOW_ID.get();
             // fix #7557 - do not show ID twice
 
             if (!osm.isNew() && !idShown) {
@@ -1020,7 +1025,7 @@ public final class MapStatus extends JPanel implements Helpful, Destroyable, Pre
      */
     public void setDist(double dist) {
         distValue = dist;
-        distText.setText(dist < 0 ? "--" : NavigatableComponent.getDistText(dist, DECIMAL_FORMAT, DISTANCE_THRESHOLD));
+        distText.setText(dist < 0 ? "--" : NavigatableComponent.getDistText(dist, DECIMAL_FORMAT, DISTANCE_THRESHOLD.get()));
     }
 
     /**
diff --git a/src/org/openstreetmap/josm/gui/conflict/ConflictColors.java b/src/org/openstreetmap/josm/gui/conflict/ConflictColors.java
index 2d5e7e3..96e5a29 100644
--- a/src/org/openstreetmap/josm/gui/conflict/ConflictColors.java
+++ b/src/org/openstreetmap/josm/gui/conflict/ConflictColors.java
@@ -5,14 +5,13 @@ import static org.openstreetmap.josm.tools.I18n.marktr;
 
 import java.awt.Color;
 
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.Preferences.ColorKey;
+import org.openstreetmap.josm.data.preferences.ColorProperty;
 
 /**
  * Conflict color constants.
  * @since 4162
  */
-public enum ConflictColors implements ColorKey {
+public enum ConflictColors {
 
     /** Conflict background: no conflict */
     BGCOLOR_NO_CONFLICT(marktr("Conflict background: no conflict"), new Color(234, 234, 234)),
@@ -82,27 +81,10 @@ public enum ConflictColors implements ColorKey {
     /** Conflict foreground: remove member */
     FGCOLOR_MEMBER_REMOVE(marktr("Conflict foreground: remove member"), Color.black);
 
-    private final String name;
-    private final Color defaultColor;
+    private final ColorProperty property;
 
     ConflictColors(String name, Color defaultColor) {
-        this.name = name;
-        this.defaultColor = defaultColor;
-    }
-
-    @Override
-    public String getColorName() {
-        return name;
-    }
-
-    @Override
-    public Color getDefaultValue() {
-        return defaultColor;
-    }
-
-    @Override
-    public String getSpecialName() {
-        return null;
+        property = new ColorProperty(name, defaultColor);
     }
 
     /**
@@ -110,7 +92,7 @@ public enum ConflictColors implements ColorKey {
      * @return the color
      */
     public Color get() {
-        return Main.pref.getColor(this);
+        return property.get();
     }
 
     /**
diff --git a/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java b/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java
index 2b7798b..9dbc439 100644
--- a/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java
+++ b/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 import javax.swing.AbstractAction;
@@ -44,6 +45,7 @@ import javax.swing.table.TableModel;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.MergeLayerAction;
+import org.openstreetmap.josm.data.preferences.AbstractProperty;
 import org.openstreetmap.josm.gui.MapFrame;
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.gui.SideButton;
@@ -545,28 +547,18 @@ public class LayerListDialog extends ToggleDialog {
                 label.setFont(label.getFont().deriveFont(Font.BOLD));
             }
             if (Main.pref.getBoolean("dialog.layer.colorname", true)) {
-                Color c = layer.getColor(false);
-                if (c != null) {
-                    Color oc = null;
-                    for (Layer l : model.getLayers()) {
-                        oc = l.getColor(false);
-                        if (oc != null) {
-                            if (oc.equals(c)) {
-                                oc = null;
-                            } else {
-                                break;
-                            }
-                        }
-                    }
+                AbstractProperty<Color> prop = layer.getColorProperty();
+                Color c = prop == null ? null : prop.get();
+                if (c == null || !model.getLayers().stream()
+                        .map(Layer::getColorProperty)
+                        .filter(Objects::nonNull)
+                        .map(AbstractProperty::get)
+                        .anyMatch(oc -> oc != null && !oc.equals(c))) {
                     /* not more than one color, don't use coloring */
-                    if (oc == null) {
-                        c = null;
-                    }
-                }
-                if (c == null) {
-                    c = UIManager.getColor(isSelected ? "Table.selectionForeground" : "Table.foreground");
+                    label.setForeground(UIManager.getColor(isSelected ? "Table.selectionForeground" : "Table.foreground"));
+                } else {
+                    label.setForeground(c);
                 }
-                label.setForeground(c);
             }
             label.setIcon(layer.getIcon());
             label.setToolTipText(layer.getToolTipText());
diff --git a/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java b/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java
index f738e7a..0dceb5e 100644
--- a/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java
+++ b/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java
@@ -63,7 +63,7 @@ import org.openstreetmap.josm.actions.search.SearchCompiler;
 import org.openstreetmap.josm.command.ChangeCommand;
 import org.openstreetmap.josm.command.ChangePropertyCommand;
 import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
+import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
 import org.openstreetmap.josm.data.SelectionChangedListener;
 import org.openstreetmap.josm.data.osm.IRelation;
 import org.openstreetmap.josm.data.osm.Node;
@@ -225,6 +225,12 @@ implements SelectionChangedListener, ActiveLayerChangeListener, DataSetListenerA
     private final JLabel selectSth = new JLabel("<html><p>"
             + tr("Select objects for which to change tags.") + "</p></html>");
 
+    private final PreferenceChangedListener preferenceListener = e -> {
+                if (Main.getLayerManager().getEditDataSet() != null) {
+                    // Re-load data when display preference change
+                    updateSelection();
+                }};
+
     private final transient TaggingPresetHandler presetHandler = new TaggingPresetHandler() {
         @Override
         public void updateTags(List<Tag> tags) {
@@ -297,7 +303,7 @@ implements SelectionChangedListener, ActiveLayerChangeListener, DataSetListenerA
 
         editHelper.loadTagsIfNeeded();
 
-        Main.pref.addPreferenceChangeListener(this);
+        Main.pref.addKeyPreferenceChangeListener("display.discardable-keys", preferenceListener);
     }
 
     private void buildTagsTable() {
@@ -601,7 +607,7 @@ implements SelectionChangedListener, ActiveLayerChangeListener, DataSetListenerA
     @Override
     public void destroy() {
         super.destroy();
-        Main.pref.removePreferenceChangeListener(this);
+        Main.pref.removeKeyPreferenceChangeListener("display.discardable-keys", preferenceListener);
         Container parent = pluginHook.getParent();
         if (parent != null) {
             parent.remove(pluginHook);
@@ -1389,15 +1395,6 @@ implements SelectionChangedListener, ActiveLayerChangeListener, DataSetListenerA
         return ss;
     }
 
-    @Override
-    public void preferenceChanged(PreferenceChangeEvent e) {
-        super.preferenceChanged(e);
-        if ("display.discardable-keys".equals(e.getKey()) && Main.getLayerManager().getEditDataSet() != null) {
-            // Re-load data when display preference change
-            updateSelection();
-        }
-    }
-
     /**
      * Clears the row selection when it is filtered away by the row sorter.
      */
diff --git a/src/org/openstreetmap/josm/gui/layer/CustomizeColor.java b/src/org/openstreetmap/josm/gui/layer/CustomizeColor.java
index e39f575..1d53e28 100644
--- a/src/org/openstreetmap/josm/gui/layer/CustomizeColor.java
+++ b/src/org/openstreetmap/josm/gui/layer/CustomizeColor.java
@@ -7,8 +7,10 @@ import static org.openstreetmap.josm.tools.I18n.tr;
 import java.awt.Color;
 import java.awt.Component;
 import java.awt.event.ActionEvent;
-import java.util.LinkedList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 import javax.swing.AbstractAction;
 import javax.swing.Action;
@@ -17,12 +19,15 @@ import javax.swing.JMenuItem;
 import javax.swing.JOptionPane;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.preferences.AbstractProperty;
+import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
 import org.openstreetmap.josm.gui.layer.Layer.LayerAction;
 import org.openstreetmap.josm.gui.layer.Layer.MultiLayerAction;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.openstreetmap.josm.tools.ImageProvider;
 
 public class CustomizeColor extends AbstractAction implements LayerAction, MultiLayerAction {
-    private final transient List<Layer> layers;
+    private final transient List<AbstractProperty<Color>> colors;
 
     /**
      * Constructs a new {@code CustomizeColor} for a given list of layers.
@@ -30,8 +35,9 @@ public class CustomizeColor extends AbstractAction implements LayerAction, Multi
      */
     public CustomizeColor(List<Layer> l) {
         super(tr("Customize Color"), ImageProvider.get("colorchooser"));
+        colors = l.stream().map(Layer::getColorProperty).collect(Collectors.toList());
+        CheckParameterUtil.ensureThat(colors.stream().allMatch(Objects::nonNull), "All layers must have colors.");
         putValue("help", ht("/Action/LayerCustomizeColor"));
-        layers = l;
     }
 
     /**
@@ -39,17 +45,12 @@ public class CustomizeColor extends AbstractAction implements LayerAction, Multi
      * @param l layer
      */
     public CustomizeColor(Layer l) {
-        this(new LinkedList<Layer>());
-        layers.add(l);
+        this(Collections.singletonList(l));
     }
 
     @Override
     public boolean supportLayers(List<Layer> layers) {
-        for (Layer layer: layers) {
-            if (layer.getColor(false) == null)
-                return false;
-        }
-        return true;
+        return layers.stream().allMatch(l -> l.getColorProperty() != null);
     }
 
     @Override
@@ -64,9 +65,7 @@ public class CustomizeColor extends AbstractAction implements LayerAction, Multi
 
     @Override
     public void actionPerformed(ActionEvent e) {
-        Color cl = layers.get(0).getColor(false);
-        if (cl == null)
-            cl = Color.gray;
+        Color cl = colors.stream().map(c -> c.get()).filter(Objects::nonNull).findAny().orElse(Color.GRAY);
         JColorChooser c = new JColorChooser(cl);
         Object[] options = new Object[]{tr("OK"), tr("Cancel"), tr("Default")};
         int answer = JOptionPane.showOptionDialog(
@@ -81,18 +80,15 @@ public class CustomizeColor extends AbstractAction implements LayerAction, Multi
         );
         switch (answer) {
         case 0:
-            for (Layer layer : layers) {
-                Main.pref.putColor("layer "+layer.getName(), c.getColor());
-            }
+            colors.stream().forEach(prop -> prop.put(c.getColor()));
             break;
         case 1:
             return;
         case 2:
-            for (Layer layer : layers) {
-                Main.pref.putColor("layer "+layer.getName(), null);
-            }
+            colors.stream().forEach(prop -> prop.put(null));
             break;
         }
-        Main.map.repaint();
+        // TODO: Make the layer dialog listen to property change events so that this is not needed any more.
+        LayerListDialog.getInstance().repaint();
     }
 }
diff --git a/src/org/openstreetmap/josm/gui/layer/GpxLayer.java b/src/org/openstreetmap/josm/gui/layer/GpxLayer.java
index 482d7f7..a10d1b8 100644
--- a/src/org/openstreetmap/josm/gui/layer/GpxLayer.java
+++ b/src/org/openstreetmap/josm/gui/layer/GpxLayer.java
@@ -4,7 +4,6 @@ package org.openstreetmap.josm.gui.layer;
 import static org.openstreetmap.josm.tools.I18n.tr;
 import static org.openstreetmap.josm.tools.I18n.trn;
 
-import java.awt.Color;
 import java.awt.Dimension;
 import java.awt.Graphics2D;
 import java.io.File;
@@ -31,6 +30,7 @@ import org.openstreetmap.josm.data.gpx.GpxData;
 import org.openstreetmap.josm.data.gpx.GpxTrack;
 import org.openstreetmap.josm.data.gpx.WayPoint;
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.data.preferences.ColorProperty;
 import org.openstreetmap.josm.data.projection.Projection;
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
@@ -88,7 +88,7 @@ public class GpxLayer extends Layer {
     public GpxLayer(GpxData d, String name, boolean isLocal) {
         super(d.getString(GpxConstants.META_NAME));
         data = d;
-        drawHelper = new GpxDrawHelper(data);
+        drawHelper = new GpxDrawHelper(data, getColorProperty());
         SystemOfMeasurement.addSoMChangeListener(drawHelper);
         ensureTrackVisibilityLength();
         setName(name);
@@ -96,8 +96,8 @@ public class GpxLayer extends Layer {
     }
 
     @Override
-    public Color getColor(boolean ignoreCustom) {
-        return drawHelper.getColor(getName(), ignoreCustom);
+    protected ColorProperty getBaseColorProperty() {
+        return GpxDrawHelper.DEFAULT_COLOR;
     }
 
     /**
diff --git a/src/org/openstreetmap/josm/gui/layer/Layer.java b/src/org/openstreetmap/josm/gui/layer/Layer.java
index f70f0d8..331f84d4 100644
--- a/src/org/openstreetmap/josm/gui/layer/Layer.java
+++ b/src/org/openstreetmap/josm/gui/layer/Layer.java
@@ -25,6 +25,9 @@ import org.openstreetmap.josm.actions.SaveActionBase;
 import org.openstreetmap.josm.actions.SaveAsAction;
 import org.openstreetmap.josm.data.ProjectionBounds;
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.data.preferences.AbstractProperty;
+import org.openstreetmap.josm.data.preferences.AbstractProperty.ValueChangeListener;
+import org.openstreetmap.josm.data.preferences.ColorProperty;
 import org.openstreetmap.josm.data.projection.Projection;
 import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
 import org.openstreetmap.josm.tools.Destroyable;
@@ -145,6 +148,8 @@ public abstract class Layer extends AbstractMapViewPaintable implements Destroya
      */
     private File associatedFile;
 
+    private final ValueChangeListener<Object> invalidateListener = change -> invalidate();
+
     /**
      * Create the layer and fill in the necessary components.
      * @param name Layer name
@@ -181,12 +186,50 @@ public abstract class Layer extends AbstractMapViewPaintable implements Destroya
      *      is used. When this is true, then even for custom coloring the base
      *      color is returned - mainly for layer internal use.
      * @return layer color
+     * @deprecated Use the new {@link #getColorProperty()}. To be removed end of 2016.
      */
+    @Deprecated
     public Color getColor(boolean ignoreCustom) {
         return null;
     }
 
     /**
+     * Gets the color property to use for this layer.
+     * @return The color property.
+     */
+    public AbstractProperty<Color> getColorProperty() {
+        ColorProperty base = getBaseColorProperty();
+        if (base != null) {
+            // cannot cache this - name may change.
+            return base.getChildColor("layer " + getName());
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Gets the color property that stores the default color for this layer.
+     * @return The property or <code>null</code> if this layer is not colored.
+     */
+    protected ColorProperty getBaseColorProperty() {
+        return null;
+    }
+
+    private void addColorPropertyListener() {
+        AbstractProperty<Color> colorProperty = getColorProperty();
+        if (colorProperty != null) {
+            colorProperty.addWeakListener(invalidateListener);
+        }
+    }
+
+    private void removeColorPropertyListener() {
+        AbstractProperty<Color> colorProperty = getColorProperty();
+        if (colorProperty != null) {
+            colorProperty.removeListener(invalidateListener);
+        }
+    }
+
+    /**
      * @return A small tooltip hint about some statistics for this layer.
      */
     public abstract String getToolTipText();
@@ -264,14 +307,22 @@ public abstract class Layer extends AbstractMapViewPaintable implements Destroya
      * @param name the name. If null, the name is set to the empty string.
      */
     public final void setName(String name) {
+        if (this.name != null) {
+            removeColorPropertyListener();
+        }
         if (name == null) {
             name = "";
         }
+
         String oldValue = this.name;
         this.name = name;
         if (!this.name.equals(oldValue)) {
             propertyChangeSupport.firePropertyChange(NAME_PROP, oldValue, this.name);
         }
+
+        // re-add listener
+        addColorPropertyListener();
+        invalidate();
     }
 
     /**
diff --git a/src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java b/src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java
index 8e138db..03b537a 100644
--- a/src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java
+++ b/src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java
@@ -24,6 +24,8 @@ import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.gpx.GpxConstants;
 import org.openstreetmap.josm.data.gpx.GpxData;
 import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.data.preferences.AbstractProperty;
+import org.openstreetmap.josm.data.preferences.ColorProperty;
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.tools.ColorScale;
 
@@ -32,6 +34,12 @@ import org.openstreetmap.josm.tools.ColorScale;
  * @since 7319
  */
 public class GpxDrawHelper implements SoMChangeListener {
+
+    /**
+     * The color that is used for drawing GPX points.
+     */
+    public static final ColorProperty DEFAULT_COLOR = new ColorProperty(marktr("gps point"), Color.magenta);
+
     private final GpxData data;
 
     // draw lines between points belonging to different segments
@@ -83,8 +91,6 @@ public class GpxDrawHelper implements SoMChangeListener {
     /** Opacity for hdop points **/
     private int hdopAlpha;
 
-    private static final Color DEFAULT_COLOR = Color.magenta;
-
     // lookup array to draw arrows without doing any math
     private static final int ll0 = 9;
     private static final int sl4 = 5;
@@ -133,8 +139,9 @@ public class GpxDrawHelper implements SoMChangeListener {
     /**
      * Constructs a new {@code GpxDrawHelper}.
      * @param gpxData GPX data
+     * @param abstractProperty The color to draw with
      */
-    public GpxDrawHelper(GpxData gpxData) {
+    public GpxDrawHelper(GpxData gpxData, AbstractProperty<Color> abstractProperty) {
         data = gpxData;
         setupColors();
     }
@@ -150,8 +157,11 @@ public class GpxDrawHelper implements SoMChangeListener {
      * @return the color or null if the color is not constant
      */
     public Color getColor(String layerName, boolean ignoreCustom) {
-        Color c = Main.pref.getColor(marktr("gps point"), specName(layerName), DEFAULT_COLOR);
-        return ignoreCustom || getColorMode(layerName) == ColorMode.NONE ? c : null;
+        if (ignoreCustom || getColorMode(layerName) == ColorMode.NONE) {
+            return DEFAULT_COLOR.getChildColor(specName(layerName)).get();
+        } else {
+            return null;
+        }
     }
 
     /**
@@ -173,7 +183,7 @@ public class GpxDrawHelper implements SoMChangeListener {
      * @return the color
      **/
     public static Color getGenericColor() {
-        return Main.pref.getColor(marktr("gps point"), DEFAULT_COLOR);
+        return DEFAULT_COLOR.get();
     }
 
     /**
diff --git a/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java b/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java
index 15abf70..43c265b 100644
--- a/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java
+++ b/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java
@@ -37,6 +37,7 @@ import org.openstreetmap.josm.data.gpx.GpxData;
 import org.openstreetmap.josm.data.gpx.GpxLink;
 import org.openstreetmap.josm.data.gpx.WayPoint;
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.data.preferences.ColorProperty;
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
 import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
@@ -73,6 +74,7 @@ public class MarkerLayer extends Layer implements JumpToMarkerLayer {
     public AudioMarker syncAudioMarker;
 
     private static final Color DEFAULT_COLOR = Color.magenta;
+    private static final ColorProperty COLOR_PROPERTY = new ColorProperty(marktr("gps marker"), DEFAULT_COLOR);
 
     /**
      * Constructs a new {@code MarkerLayer}.
@@ -194,19 +196,19 @@ public class MarkerLayer extends Layer implements JumpToMarkerLayer {
     }
 
     @Override
-    public Color getColor(boolean ignoreCustom) {
-        return Main.pref.getColor(marktr("gps marker"), "layer "+getName(), DEFAULT_COLOR);
+    protected ColorProperty getBaseColorProperty() {
+        return COLOR_PROPERTY;
     }
 
     /* for preferences */
     public static Color getGenericColor() {
-        return Main.pref.getColor(marktr("gps marker"), DEFAULT_COLOR);
+        return COLOR_PROPERTY.get();
     }
 
     @Override
     public void paint(Graphics2D g, MapView mv, Bounds box) {
         boolean showTextOrIcon = isTextOrIconShown();
-        g.setColor(getColor(true));
+        g.setColor(getColorProperty().get());
 
         if (mousePressed) {
             boolean mousePressedTmp = mousePressed;
diff --git a/src/org/openstreetmap/josm/gui/preferences/display/ColorPreference.java b/src/org/openstreetmap/josm/gui/preferences/display/ColorPreference.java
index ca7c4aa..f7ca354 100644
--- a/src/org/openstreetmap/josm/gui/preferences/display/ColorPreference.java
+++ b/src/org/openstreetmap/josm/gui/preferences/display/ColorPreference.java
@@ -256,7 +256,7 @@ public class ColorPreference implements SubPreferenceSetting {
      * Add all missing color entries.
      */
     private static void fixColorPrefixes() {
-        PaintColors.getColors();
+        PaintColors.values();
         ConflictColors.getColors();
         Severity.getColors();
         MarkerLayer.getGenericColor();
diff --git a/src/org/openstreetmap/josm/tools/ListenerList.java b/src/org/openstreetmap/josm/tools/ListenerList.java
new file mode 100644
index 0000000..761e675
--- /dev/null
+++ b/src/org/openstreetmap/josm/tools/ListenerList.java
@@ -0,0 +1,230 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.tools;
+
+import java.lang.ref.WeakReference;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.stream.Stream;
+
+import org.openstreetmap.josm.Main;
+
+/**
+ * This is a list of listeners. It does error checking and allows you to fire all listeners.
+ *
+ * @author Michael Zangl
+ * @param <T> The type of listener contained in this list.
+ */
+public class ListenerList<T> {
+    /**
+     * This is a function that can be invoked for every listener.
+     * @param <T> the listener type.
+     */
+    @FunctionalInterface
+    public interface EventFirerer<T> {
+        /**
+         * Should fire the event for the given listener.
+         * @param listener The listener to fire the event for.
+         */
+        void fire(T listener);
+    }
+
+    private static final class WeakListener<T> {
+
+        private WeakReference<T> listener;
+
+        WeakListener(T listener) {
+            this.listener = new WeakReference<>(listener);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj != null && obj.getClass() == WeakListener.class) {
+                return Objects.equals(listener.get(), ((WeakListener<?>) obj).listener.get());
+            } else {
+                return false;
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            T l = listener.get();
+            if (l == null) {
+                return 0;
+            } else {
+                return l.hashCode();
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "WeakListener [listener=" + listener + "]";
+        }
+    }
+
+    private final CopyOnWriteArrayList<T> listeners = new CopyOnWriteArrayList<>();
+    private final CopyOnWriteArrayList<WeakListener<T>> weakListeners = new CopyOnWriteArrayList<>();
+
+    protected ListenerList() {
+        // hide
+    }
+
+    /**
+     * Adds a listener. The listener will not prevent the object from being garbage collected.
+     *
+     * This should be used with care. It is better to add good cleanup code.
+     * @param listener The listener.
+     */
+    public synchronized void addWeakListener(T listener) {
+        ensureNotInList(listener);
+        // clean the weak listeners, just to be sure...
+        while (weakListeners.remove(new WeakListener<T>(null))) {
+            // continue
+        }
+        weakListeners.add(new WeakListener<>(listener));
+    }
+
+    /**
+     * Adds a listener.
+     * @param listener The listener to add.
+     */
+    public synchronized void addListener(T listener) {
+        ensureNotInList(listener);
+        listeners.add(listener);
+    }
+
+    private void ensureNotInList(T listener) {
+        CheckParameterUtil.ensureParameterNotNull(listener, "listener");
+        if (containsListener(listener)) {
+            failAdd(listener);
+        }
+    }
+
+    protected void failAdd(T listener) {
+        throw new IllegalArgumentException(
+                MessageFormat.format("Listener {0} (instance of {1}) was already registered.", listener.toString(),
+                        listener.getClass().getName()));
+    }
+
+    private boolean containsListener(T listener) {
+        return listeners.contains(listener) || weakListeners.contains(new WeakListener<>(listener));
+    }
+
+    /**
+     * Removes a listener.
+     * @param listener The listener to remove.
+     * @throws IllegalArgumentException if the listener was not registered before
+     */
+    public synchronized void removeListener(T listener) {
+        if (!listeners.remove(listener) && !weakListeners.remove(new WeakListener<>(listener))) {
+            failRemove(listener);
+        }
+    }
+
+    protected void failRemove(T listener) {
+        throw new IllegalArgumentException(
+                MessageFormat.format("Listener {0} (instance of {1}) was not registered before or already removed.",
+                        listener.toString(), listener.getClass().getName()));
+    }
+
+    /**
+     * Check if any listeners are registered.
+     * @return <code>true</code> if any are registered.
+     */
+    public boolean hasListeners() {
+        return !listeners.isEmpty();
+    }
+
+    /**
+     * Fires an event to every listener.
+     * @param eventFirerer The firerer to invoke the event method of the listener.
+     */
+    public void fireEvent(EventFirerer<T> eventFirerer) {
+        for (T l : listeners) {
+            eventFirerer.fire(l);
+        }
+        for (Iterator<WeakListener<T>> iterator = weakListeners.iterator(); iterator.hasNext();) {
+            WeakListener<T> weakLink = iterator.next();
+            T l = weakLink.listener.get();
+            if (l == null) {
+                iterator.remove();
+            } else {
+                eventFirerer.fire(l);
+            }
+        }
+    }
+
+    /**
+     * This is a special {@link ListenerList} that traces calls to the add/remove methods. This may cause memory leaks.
+     * @author Michael Zangl
+     *
+     * @param <T>
+     */
+    public static class TracingListenerList<T> extends ListenerList<T> {
+        private HashMap<T, StackTraceElement[]> listenersAdded = new HashMap<>();
+        private HashMap<T, StackTraceElement[]> listenersRemoved = new HashMap<>();
+
+        protected TracingListenerList() {
+            // hidden
+        }
+
+        @Override
+        public synchronized void addListener(T listener) {
+            super.addListener(listener);
+            listenersRemoved.remove(listener);
+            listenersAdded.put(listener, Thread.currentThread().getStackTrace());
+        }
+
+        @Override
+        public synchronized void addWeakListener(T listener) {
+            super.addWeakListener(listener);
+            listenersRemoved.remove(listener);
+            listenersAdded.put(listener, Thread.currentThread().getStackTrace());
+        }
+
+        @Override
+        public synchronized void removeListener(T listener) {
+            super.removeListener(listener);
+            listenersAdded.remove(listener);
+            listenersRemoved.put(listener, Thread.currentThread().getStackTrace());
+        }
+
+        @Override
+        protected void failAdd(T listener) {
+            Main.trace("Previous addition of the listener");
+            dumpStack(listenersAdded.get(listener));
+            super.failAdd(listener);
+        }
+
+        @Override
+        protected void failRemove(T listener) {
+            Main.trace("Previous removal of the listener");
+            dumpStack(listenersRemoved.get(listener));
+            super.failRemove(listener);
+        }
+
+        private static void dumpStack(StackTraceElement[] stackTraceElements) {
+            if (stackTraceElements == null) {
+                Main.trace("  - (no trace recorded)");
+            } else {
+                Stream.of(stackTraceElements).limit(20).forEach(
+                        e -> Main.trace(e.getClassName() + "." + e.getMethodName() + " line " + e.getLineNumber()));
+            }
+        }
+    }
+
+    /**
+     * Create a new listener list
+     * @param <T> The listener type the list should hold.
+     * @return A new list. A tracing list is created if trace is enabled.
+     */
+    public static <T> ListenerList<T> create() {
+        if (Main.isTraceEnabled()) {
+            return new TracingListenerList<>();
+        } else {
+            return new ListenerList<>();
+        }
+    }
+}
diff --git a/test/unit/org/openstreetmap/josm/data/preferences/ColorPropertyTest.java b/test/unit/org/openstreetmap/josm/data/preferences/ColorPropertyTest.java
new file mode 100644
index 0000000..d76ad8f
--- /dev/null
+++ b/test/unit/org/openstreetmap/josm/data/preferences/ColorPropertyTest.java
@@ -0,0 +1,83 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.preferences;
+
+import static org.junit.Assert.assertEquals;
+
+import java.awt.Color;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Test {@link ColorProperty}
+ * @author Michael Zangl
+ * @since xxx
+ */
+public class ColorPropertyTest {
+    /**
+     * This is a preference test.
+     */
+    @Rule
+    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+    public JOSMTestRules test = new JOSMTestRules().preferences();
+    private ColorProperty base;
+
+    /**
+     * Set up test case
+     */
+    @Before
+    public void createTestProperty() {
+        base = new ColorProperty("test", Color.RED);
+    }
+
+    /**
+     * Test {@link ColorProperty#get()}
+     */
+    @Test
+    public void testGet() {
+        assertEquals(Color.RED, base.get());
+
+        Main.pref.put("color.test", "#00ff00");
+        assertEquals(new Color(0xff00ff00), base.get());
+    }
+
+    /**
+     * Test {@link ColorProperty#put()}
+     */
+    @Test
+    public void testPut() {
+        assertEquals(Color.RED, base.get());
+
+        base.put(new Color(0xff00ff00));
+        assertEquals(new Color(0xff00ff00), base.get());
+        assertEquals("#00ff00", Main.pref.get("color.test").toLowerCase());
+
+        base.put(null);
+        assertEquals(Color.RED, base.get());
+    }
+
+    /**
+     * Test {@link ColorProperty#getChildColor(String)}
+     */
+    @Test
+    public void testGetChildColor() {
+        AbstractToStringProperty<Color> child = base.getChildColor("test2");
+
+        assertEquals(Color.RED, child.get());
+
+        base.put(Color.GREEN);
+        assertEquals(Color.GREEN, child.get());
+
+        child.put(Color.YELLOW);
+        assertEquals(Color.YELLOW, child.get());
+        assertEquals(Color.GREEN, base.get());
+
+        child.put(null);
+        assertEquals(Color.GREEN, child.get());
+    }
+}
diff --git a/test/unit/org/openstreetmap/josm/gui/layer/GpxLayerTest.java b/test/unit/org/openstreetmap/josm/gui/layer/GpxLayerTest.java
index 1d8acb0..b151773 100644
--- a/test/unit/org/openstreetmap/josm/gui/layer/GpxLayerTest.java
+++ b/test/unit/org/openstreetmap/josm/gui/layer/GpxLayerTest.java
@@ -73,13 +73,13 @@ public class GpxLayerTest {
         GpxLayer layer = new GpxLayer(new GpxData(), "foo", false);
         assertEquals("foo", layer.getName());
         assertFalse(layer.isLocalFile());
-        assertEquals(Color.MAGENTA, layer.getColor(false));
+        assertEquals(Color.MAGENTA, layer.getColorProperty().get());
         assertEquals("<html>0 tracks, 0 routes, 0 waypoints<br>Length: < 0.01 m<br></html>", layer.getToolTipText());
 
         GpxLayer layer2 = new GpxLayer(new GpxData(), "bar", true);
         assertEquals("bar", layer2.getName());
         assertTrue(layer2.isLocalFile());
-        assertEquals(Color.MAGENTA, layer2.getColor(true));
+        assertEquals(Color.MAGENTA, layer2.getColorProperty().get());
         assertEquals("<html>0 tracks, 0 routes, 0 waypoints<br>Length: < 0.01 m<br></html>", layer2.getToolTipText());
 
         assertFalse(layer.isChanged());
diff --git a/test/unit/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelperTest.java b/test/unit/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelperTest.java
index ed17db6..9e83236 100644
--- a/test/unit/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelperTest.java
+++ b/test/unit/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelperTest.java
@@ -3,6 +3,7 @@ package org.openstreetmap.josm.gui.layer.gpx;
 
 import static org.junit.Assert.assertEquals;
 
+import java.awt.Color;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -16,6 +17,7 @@ import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.TestUtils;
 import org.openstreetmap.josm.data.gpx.GpxData;
 import org.openstreetmap.josm.data.gpx.WayPoint;
+import org.openstreetmap.josm.data.preferences.ColorProperty;
 import org.openstreetmap.josm.io.GpxReaderTest;
 import org.openstreetmap.josm.tools.ColorHelper;
 import org.xml.sax.SAXException;
@@ -124,7 +126,7 @@ public class GpxDrawHelperTest {
      */
     static List<String> calculateColors(String fileName, String layerName, int n) throws IOException, SAXException {
         final GpxData data = GpxReaderTest.parseGpxData(fileName);
-        final GpxDrawHelper gdh = new GpxDrawHelper(data);
+        final GpxDrawHelper gdh = new GpxDrawHelper(data, new ColorProperty("x", Color.MAGENTA));
         gdh.readPreferences(layerName);
         gdh.calculateColors();
         final Iterator<WayPoint> wayPointIterator = data.tracks.iterator().next().getSegments().iterator().next().getWayPoints().iterator();
diff --git a/test/unit/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayerTest.java b/test/unit/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayerTest.java
index 19dfbb0..a5596eb 100644
--- a/test/unit/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayerTest.java
+++ b/test/unit/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayerTest.java
@@ -41,7 +41,7 @@ public class MarkerLayerTest {
         MarkerLayer layer = new MarkerLayer(new GpxData(), "foo", null, null);
 
         assertEquals("foo", layer.getName());
-        assertEquals(Color.magenta, layer.getColor(false));
+        assertEquals(Color.magenta, layer.getColorProperty().get());
         assertNotNull(layer.getIcon());
         assertEquals("0 markers", layer.getToolTipText());
         assertEquals("<html>foo consists of 0 markers</html>", layer.getInfoComponent());
@@ -58,7 +58,7 @@ public class MarkerLayerTest {
         layer = new MarkerLayer(gpx, "bar", null, null);
 
         assertEquals("bar", layer.getName());
-        assertEquals(Color.magenta, layer.getColor(false));
+        assertEquals(Color.magenta, layer.getColorProperty().get());
         assertNotNull(layer.getIcon());
         assertEquals("3 markers", layer.getToolTipText());
         assertEquals("<html>bar consists of 3 markers</html>", layer.getInfoComponent());
diff --git a/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java b/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java
index 634b625..246bbff 100644
--- a/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java
+++ b/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java
@@ -159,7 +159,7 @@ public class JOSMTestRules implements TestRule {
         Statement statement = base;
         if (timeout > 0) {
             // TODO: new DisableOnDebug(timeout)
-            statement = new FailOnTimeoutStatement(statement, timeout);
+           //  statement = new FailOnTimeoutStatement(statement, timeout);
         }
         statement = new CreateJosmEnvironment(statement);
         if (josmHome != null) {
