diff --git a/Makefile.am b/Makefile.am
index e836c7343..ebe358d6d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -389,6 +389,7 @@ endif
 # Generic utility library
 
 libutil_a_SOURCES = \
+	src/util/RuntimeError.hxx \
 	src/util/Macros.hxx \
 	src/util/Cast.hxx \
 	src/util/Clamp.hxx \
diff --git a/src/Main.cxx b/src/Main.cxx
index e76806256..de950c05a 100644
--- a/src/Main.cxx
+++ b/src/Main.cxx
@@ -444,11 +444,11 @@ int mpd_main(int argc, char *argv[])
 	io_thread_init();
 	config_global_init();
 
+	try {
 #ifdef ANDROID
-	(void)argc;
-	(void)argv;
+		(void)argc;
+		(void)argv;
 
-	{
 		const auto sdcard = Environment::getExternalStorageDirectory();
 		if (!sdcard.IsNull()) {
 			const auto config_path =
@@ -459,13 +459,16 @@ int mpd_main(int argc, char *argv[])
 				return EXIT_FAILURE;
 			}
 		}
-	}
 #else
-	if (!parse_cmdline(argc, argv, &options, error)) {
-		LogError(error);
+		if (!parse_cmdline(argc, argv, &options, error)) {
+			LogError(error);
+			return EXIT_FAILURE;
+		}
+#endif
+	} catch (const std::exception &e) {
+		LogError(e);
 		return EXIT_FAILURE;
 	}
-#endif
 
 #ifdef ENABLE_DAEMON
 	if (!glue_daemonize_init(&options, error)) {
diff --git a/src/config/ConfigFile.cxx b/src/config/ConfigFile.cxx
index a9bf694c3..4a33efee8 100644
--- a/src/config/ConfigFile.cxx
+++ b/src/config/ConfigFile.cxx
@@ -27,6 +27,7 @@
 #include "util/StringUtil.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "util/RuntimeError.hxx"
 #include "fs/Path.hxx"
 #include "fs/io/FileReader.hxx"
 #include "fs/io/BufferedReader.hxx"
@@ -54,27 +55,20 @@ config_read_name_value(ConfigBlock &block, char *input, unsigned line,
 
 	const char *value = tokenizer.NextString(error);
 	if (value == nullptr) {
-		if (tokenizer.IsEnd()) {
-			error.Set(config_file_domain, "Value missing");
-		} else {
-			assert(error.IsDefined());
-		}
+		if (tokenizer.IsEnd())
+			throw std::runtime_error("Value missing");
 
+		assert(error.IsDefined());
 		return false;
 	}
 
-	if (!tokenizer.IsEnd() && tokenizer.CurrentChar() != CONF_COMMENT) {
-		error.Set(config_file_domain, "Unknown tokens after value");
-		return false;
-	}
+	if (!tokenizer.IsEnd() && tokenizer.CurrentChar() != CONF_COMMENT)
+		throw std::runtime_error("Unknown tokens after value");
 
 	const BlockParam *bp = block.GetBlockParam(name);
-	if (bp != nullptr) {
-		error.Format(config_file_domain,
-			     "\"%s\" is duplicate, first defined on line %i",
-			     name, bp->line);
-		return false;
-	}
+	if (bp != nullptr)
+		throw FormatRuntimeError("\"%s\" is duplicate, first defined on line %i",
+					 name, bp->line);
 
 	block.AddBlockParam(name, value, line);
 	return true;
@@ -82,15 +76,14 @@ config_read_name_value(ConfigBlock &block, char *input, unsigned line,
 
 static ConfigBlock *
 config_read_block(BufferedReader &reader, Error &error)
-{
+try {
 	std::unique_ptr<ConfigBlock> block(new ConfigBlock(reader.GetLineNumber()));
 
 	while (true) {
 		char *line = reader.ReadLine();
 		if (line == nullptr) {
 			if (reader.Check(error))
-				error.Set(config_file_domain,
-					  "Expected '}' before end-of-file");
+				throw std::runtime_error("Expected '}' before end-of-file");
 			return nullptr;
 		}
 
@@ -103,12 +96,8 @@ config_read_block(BufferedReader &reader, Error &error)
 			   (and from this "while" loop) */
 
 			line = StripLeft(line + 1);
-			if (*line != 0 && *line != CONF_COMMENT) {
-				error.Format(config_file_domain,
-					     "line %u: Unknown tokens after '}'",
-					     reader.GetLineNumber());
-				return nullptr;
-			}
+			if (*line != 0 && *line != CONF_COMMENT)
+				throw std::runtime_error("Unknown tokens after '}'");
 
 			return block.release();
 		}
@@ -123,6 +112,8 @@ config_read_block(BufferedReader &reader, Error &error)
 			return nullptr;
 		}
 	}
+} catch (...) {
+	std::throw_with_nested(FormatRuntimeError("Error in line %u", reader.GetLineNumber()));
 }
 
 gcc_nonnull_all
@@ -150,30 +141,22 @@ ReadConfigBlock(ConfigData &config_data, BufferedReader &reader,
 
 	if (head != nullptr && !option.repeatable) {
 		ConfigBlock *block = head;
-		error.Format(config_file_domain,
-			     "config parameter \"%s\" is first defined "
-			     "on line %d and redefined on line %u\n",
-			     name, block->line,
-			     reader.GetLineNumber());
-		return false;
+		throw FormatRuntimeError("config parameter \"%s\" is first defined "
+					 "on line %d and redefined on line %u\n",
+					 name, block->line,
+					 reader.GetLineNumber());
 	}
 
 	/* now parse the block or the value */
 
-	if (tokenizer.CurrentChar() != '{') {
-		error.Format(config_file_domain,
-			     "line %u: '{' expected",
-			     reader.GetLineNumber());
-		return false;
-	}
+	if (tokenizer.CurrentChar() != '{')
+		throw FormatRuntimeError("line %u: '{' expected",
+					 reader.GetLineNumber());
 
 	char *line = StripLeft(tokenizer.Rest() + 1);
-	if (*line != 0 && *line != CONF_COMMENT) {
-		error.Format(config_file_domain,
-			     "line %u: Unknown tokens after '{'",
-			     reader.GetLineNumber());
-		return false;
-	}
+	if (*line != 0 && *line != CONF_COMMENT)
+		throw FormatRuntimeError("line %u: Unknown tokens after '{'",
+					 reader.GetLineNumber());
 
 	auto *param = config_read_block(reader, error);
 	if (param == nullptr)
@@ -208,12 +191,10 @@ ReadConfigParam(ConfigData &config_data, BufferedReader &reader,
 
 	if (head != nullptr && !option.repeatable) {
 		struct config_param *param = head;
-		error.Format(config_file_domain,
-			     "config parameter \"%s\" is first defined "
-			     "on line %d and redefined on line %u\n",
-			     name, param->line,
-			     reader.GetLineNumber());
-		return false;
+		throw FormatRuntimeError("config parameter \"%s\" is first defined "
+					 "on line %d and redefined on line %u\n",
+					 name, param->line,
+					 reader.GetLineNumber());
 	}
 
 	/* now parse the block or the value */
@@ -221,23 +202,16 @@ ReadConfigParam(ConfigData &config_data, BufferedReader &reader,
 	const char *value = tokenizer.NextString(error);
 	if (value == nullptr) {
 		if (tokenizer.IsEnd())
-			error.Format(config_file_domain,
-				     "line %u: Value missing",
-				     reader.GetLineNumber());
-		else
-			error.FormatPrefix("line %u: ",
-					   reader.GetLineNumber());
+			throw FormatRuntimeError("line %u: Value missing",
+						 reader.GetLineNumber());
 
+		error.FormatPrefix("line %u: ", reader.GetLineNumber());
 		return false;
 	}
 
-	if (!tokenizer.IsEnd() &&
-	    tokenizer.CurrentChar() != CONF_COMMENT) {
-		error.Format(config_file_domain,
-			     "line %u: Unknown tokens after value",
-			     reader.GetLineNumber());
-		return false;
-	}
+	if (!tokenizer.IsEnd() && tokenizer.CurrentChar() != CONF_COMMENT)
+		throw FormatRuntimeError("line %u: Unknown tokens after value",
+					 reader.GetLineNumber());
 
 	auto *param = new config_param(value, reader.GetLineNumber());
 	Append(head, param);
@@ -281,11 +255,9 @@ ReadConfigFile(ConfigData &config_data, BufferedReader &reader, Error &error)
 					     tokenizer, error))
 				return false;
 		} else {
-			error.Format(config_file_domain,
-				     "unrecognized parameter in config file at "
-				     "line %u: %s\n",
-				     reader.GetLineNumber(), name);
-			return false;
+			throw FormatRuntimeError("unrecognized parameter in config file at "
+						 "line %u: %s\n",
+						 reader.GetLineNumber(), name);
 		}
 	}
 }
diff --git a/src/util/RuntimeError.hxx b/src/util/RuntimeError.hxx
new file mode 100644
index 000000000..a25b71420
--- /dev/null
+++ b/src/util/RuntimeError.hxx
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013-2015 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef RUNTIME_ERROR_HXX
+#define RUNTIME_ERROR_HXX
+
+#include <stdexcept>
+#include <utility>
+
+template<typename... Args>
+static inline std::runtime_error
+FormatRuntimeError(const char *fmt, Args&&... args) noexcept
+{
+	char buffer[1024];
+	snprintf(buffer, sizeof(buffer), fmt, std::forward<Args>(args)...);
+	return std::runtime_error(buffer);
+}
+
+#endif
diff --git a/test/DumpDatabase.cxx b/test/DumpDatabase.cxx
index 4f00006c7..034428d88 100644
--- a/test/DumpDatabase.cxx
+++ b/test/DumpDatabase.cxx
@@ -32,6 +32,7 @@
 #include "tag/TagConfig.hxx"
 #include "fs/Path.hxx"
 #include "event/Loop.hxx"
+#include "Log.hxx"
 #include "util/Error.hxx"
 
 #include <iostream>
@@ -89,7 +90,7 @@ DumpPlaylist(const PlaylistInfo &playlist,
 
 int
 main(int argc, char **argv)
-{
+try {
 	if (argc != 3) {
 		cerr << "Usage: DumpDatabase CONFIG PLUGIN" << endl;
 		return 1;
@@ -158,4 +159,7 @@ main(int argc, char **argv)
 	config_global_finish();
 
 	return EXIT_SUCCESS;
-}
+ } catch (const std::exception &e) {
+	LogError(e);
+	return EXIT_FAILURE;
+ }
diff --git a/test/dump_playlist.cxx b/test/dump_playlist.cxx
index 4a21e2df5..65e980b21 100644
--- a/test/dump_playlist.cxx
+++ b/test/dump_playlist.cxx
@@ -48,7 +48,7 @@ tag_save(FILE *file, const Tag &tag)
 }
 
 int main(int argc, char **argv)
-{
+try {
 	const char *uri;
 	InputStream *is = NULL;
 
@@ -144,5 +144,8 @@ int main(int argc, char **argv)
 	input_stream_global_finish();
 	config_global_finish();
 
-	return 0;
-}
+	return EXIT_SUCCESS;
+ } catch (const std::exception &e) {
+	LogError(e);
+	return EXIT_FAILURE;
+ }
diff --git a/test/read_conf.cxx b/test/read_conf.cxx
index fdf93a40d..4d0fadcc6 100644
--- a/test/read_conf.cxx
+++ b/test/read_conf.cxx
@@ -28,7 +28,7 @@
 #include <stdlib.h>
 
 int main(int argc, char **argv)
-{
+try {
 	if (argc != 3) {
 		fprintf(stderr, "Usage: read_conf FILE SETTING\n");
 		return EXIT_FAILURE;
@@ -60,4 +60,7 @@ int main(int argc, char **argv)
 
 	config_global_finish();
 	return ret;
-}
+ } catch (const std::exception &e) {
+	LogError(e);
+	return EXIT_FAILURE;
+ }
diff --git a/test/run_filter.cxx b/test/run_filter.cxx
index 6499b9631..912bc6470 100644
--- a/test/run_filter.cxx
+++ b/test/run_filter.cxx
@@ -67,7 +67,7 @@ load_filter(const char *name)
 }
 
 int main(int argc, char **argv)
-{
+try {
 	struct audio_format_string af_string;
 	Error error2;
 	char buffer[4096];
@@ -151,5 +151,8 @@ int main(int argc, char **argv)
 
 	config_global_finish();
 
-	return 0;
-}
+	return EXIT_SUCCESS;
+ } catch (const std::exception &e) {
+	LogError(e);
+	return EXIT_FAILURE;
+ }
diff --git a/test/run_neighbor_explorer.cxx b/test/run_neighbor_explorer.cxx
index 29582fc55..3f48ee06f 100644
--- a/test/run_neighbor_explorer.cxx
+++ b/test/run_neighbor_explorer.cxx
@@ -46,7 +46,7 @@ class MyNeighborListener final : public NeighborListener {
 
 int
 main(int argc, char **argv)
-{
+try {
 	if (argc != 2) {
 		fprintf(stderr, "Usage: run_neighbor_explorer CONFIG\n");
 		return EXIT_FAILURE;
@@ -82,4 +82,7 @@ main(int argc, char **argv)
 	loop.Run();
 	neighbor.Close();
 	return EXIT_SUCCESS;
-}
+ } catch (const std::exception &e) {
+	LogError(e);
+	return EXIT_FAILURE;
+ }
diff --git a/test/run_output.cxx b/test/run_output.cxx
index 5100e8b44..f8f959a07 100644
--- a/test/run_output.cxx
+++ b/test/run_output.cxx
@@ -146,7 +146,7 @@ run_output(AudioOutput *ao, AudioFormat audio_format)
 }
 
 int main(int argc, char **argv)
-{
+try {
 	Error error;
 
 	if (argc < 3 || argc > 4) {
@@ -196,4 +196,7 @@ int main(int argc, char **argv)
 	config_global_finish();
 
 	return success ? EXIT_SUCCESS : EXIT_FAILURE;
-}
+ } catch (const std::exception &e) {
+	LogError(e);
+	return EXIT_FAILURE;
+ }