diff --git a/src/main/java/org/cryptomator/common/settings/AbstractInvalidSetting.java b/src/main/java/org/cryptomator/common/settings/AbstractInvalidSetting.java
new file mode 100644
index 000000000..5e08d69cb
--- /dev/null
+++ b/src/main/java/org/cryptomator/common/settings/AbstractInvalidSetting.java
@@ -0,0 +1,23 @@
+package org.cryptomator.common.settings;
+
+import java.net.URI;
+
+public interface AbstractInvalidSetting {
+
+ /**
+ * Returns the corresponding issue URI of this setting.
+ * The issue URI usually resolves to a page on the
+ * Cryptomator Bugtracker.
+ *
+ * @return the issue URI or {@code null} if none is provided.
+ */
+ URI getIssueURI();
+
+ /**
+ * Returns a (preferably localized) message, that helps the user understand the
+ * issue in their configuration and how to fix it.
+ *
+ * @return the non-null description of the issue.
+ */
+ String getMessage();
+}
\ No newline at end of file
diff --git a/src/main/java/org/cryptomator/common/settings/InvalidSetting.java b/src/main/java/org/cryptomator/common/settings/InvalidSetting.java
new file mode 100644
index 000000000..380aeb093
--- /dev/null
+++ b/src/main/java/org/cryptomator/common/settings/InvalidSetting.java
@@ -0,0 +1,43 @@
+package org.cryptomator.common.settings;
+
+import java.net.URI;
+import java.util.Objects;
+import java.util.ResourceBundle;
+
+public enum InvalidSetting implements AbstractInvalidSetting {
+
+ ;
+
+ private final static String DEFAULT_TRACKER = "https://github.com/cryptomator/cryptomator/issues/";
+ private final static ResourceBundle BUNDLE = ResourceBundle.getBundle("i18n.strings");
+ private final static String UNKNOWN_KEY_FORMAT = "";
+
+ private final URI issue;
+ private final String resourceKey;
+
+ InvalidSetting(URI issue, String resourceKey) {
+ this.issue = issue;
+ this.resourceKey = Objects.requireNonNull(resourceKey);
+ }
+
+ InvalidSetting(int issueId, String resourceKey) {
+ this(onDefaultTracker(issueId), Objects.requireNonNull(resourceKey));
+ }
+
+ @Override
+ public URI getIssueURI() {
+ return this.issue;
+ }
+
+ @Override
+ public String getMessage() {
+ if (!BUNDLE.containsKey(this.resourceKey)) {
+ return UNKNOWN_KEY_FORMAT.formatted(this.resourceKey);
+ }
+ return BUNDLE.getString(this.resourceKey);
+ }
+
+ private static URI onDefaultTracker(int id) {
+ return URI.create(DEFAULT_TRACKER + id);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/cryptomator/common/settings/InvalidSettingsException.java b/src/main/java/org/cryptomator/common/settings/InvalidSettingsException.java
new file mode 100644
index 000000000..228fc74a2
--- /dev/null
+++ b/src/main/java/org/cryptomator/common/settings/InvalidSettingsException.java
@@ -0,0 +1,42 @@
+package org.cryptomator.common.settings;
+
+
+public class InvalidSettingsException extends RuntimeException {
+
+ private final AbstractInvalidSetting reason;
+ private final String additionalMessage;
+
+ public InvalidSettingsException(AbstractInvalidSetting reason) {
+ this(reason, null, null);
+ }
+
+ public InvalidSettingsException(AbstractInvalidSetting reason, String additionalMessage) {
+ this(reason, additionalMessage, null);
+ }
+
+ public InvalidSettingsException(AbstractInvalidSetting reason, Throwable cause) {
+ this(reason, null, cause);
+ }
+
+ public InvalidSettingsException(AbstractInvalidSetting reason, String additionalMessage, Throwable cause) {
+ super(composeMessage(reason, additionalMessage), cause);
+ this.reason = reason;
+ this.additionalMessage = additionalMessage;
+ }
+
+ public AbstractInvalidSetting getReason() {
+ return this.reason;
+ }
+
+ public String getAdditionalMessage() {
+ return this.additionalMessage;
+ }
+
+ private static String composeMessage(AbstractInvalidSetting reason, String additionalMessage) {
+ if (additionalMessage == null) {
+ return reason.getMessage();
+ }
+ return reason.getMessage() + "\n" //
+ + "Additionally: " + additionalMessage;
+ }
+}
\ No newline at end of file