diff --git a/src/main/java/de/thm/arsnova/controller/AbstractControllerExceptionHandler.java b/src/main/java/de/thm/arsnova/controller/AbstractControllerExceptionHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..a393dc2483fac635661f05abb036c610b4fd1edf
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/controller/AbstractControllerExceptionHandler.java
@@ -0,0 +1,21 @@
+package de.thm.arsnova.controller;
+
+import org.springframework.beans.factory.annotation.Value;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class AbstractControllerExceptionHandler {
+	/* Since exception messages might contain sensitive data, they are not exposed by default. */
+	@Value("${api.expose-error-messages:false}") private boolean exposeMessages;
+
+	protected Map<String, Object> handleException(Throwable e) {
+		final Map<String, Object> result = new HashMap<>();
+		result.put("errorType", e.getClass().getSimpleName());
+		if (exposeMessages) {
+			result.put("errorMessage", e.getMessage());
+		}
+
+		return result;
+	}
+}
diff --git a/src/main/java/de/thm/arsnova/controller/SecurityExceptionControllerAdvice.java b/src/main/java/de/thm/arsnova/controller/ControllerExceptionHandler.java
similarity index 60%
rename from src/main/java/de/thm/arsnova/controller/SecurityExceptionControllerAdvice.java
rename to src/main/java/de/thm/arsnova/controller/ControllerExceptionHandler.java
index 48111c5360011167dedacd79ff9afb99374bf54d..433f259aadef3df37d60a0276e6da733d99d272d 100644
--- a/src/main/java/de/thm/arsnova/controller/SecurityExceptionControllerAdvice.java
+++ b/src/main/java/de/thm/arsnova/controller/ControllerExceptionHandler.java
@@ -26,6 +26,7 @@ import de.thm.arsnova.exceptions.PayloadTooLargeException;
 import de.thm.arsnova.exceptions.PreconditionFailedException;
 import de.thm.arsnova.exceptions.UnauthorizedException;
 import org.springframework.http.HttpStatus;
+import org.springframework.http.converter.HttpMessageNotReadableException;
 import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.authentication.AnonymousAuthenticationToken;
 import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
@@ -33,101 +34,104 @@ import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.web.bind.annotation.ControllerAdvice;
 import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.bind.annotation.ResponseStatus;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import java.util.HashMap;
 import java.util.Map;
 
 /**
- * Translates security/authentication related exceptions into HTTP status codes.
+ * Translates exceptions into HTTP status codes.
  */
 @ControllerAdvice
-public class SecurityExceptionControllerAdvice {
-
-	@ExceptionHandler
-	@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
-	public Map<String, String> defaultExceptionHandler(
-			final Exception e,
-			final HttpServletRequest req
-			) {
-		final Map<String, String> result = new HashMap<>();
-		result.put("code", "500");
-		result.put("status", "Internal server error");
-		result.put("message", e.getMessage());
-		return result;
-	}
-
-	@ResponseStatus(HttpStatus.NOT_FOUND)
+public class ControllerExceptionHandler extends AbstractControllerExceptionHandler {
 	@ExceptionHandler(NotFoundException.class)
-	public void handleNotFoundException(final Exception e, final HttpServletRequest request) {
-		/* No implementation - handled solely by annotations */
+	@ResponseBody
+	@ResponseStatus(HttpStatus.NOT_FOUND)
+	public Map<String, Object> handleNotFoundException(final Exception e, final HttpServletRequest request) {
+		return handleException(e);
 	}
 
-	@ResponseStatus(HttpStatus.UNAUTHORIZED)
 	@ExceptionHandler(UnauthorizedException.class)
-	public void handleUnauthorizedException(final Exception e, final HttpServletRequest request) {
-		/* No implementation - handled solely by annotations */
+	@ResponseBody
+	@ResponseStatus(HttpStatus.UNAUTHORIZED)
+	public Map<String, Object> handleUnauthorizedException(final Exception e, final HttpServletRequest request) {
+		return handleException(e);
 	}
 
-	@ResponseStatus(HttpStatus.UNAUTHORIZED)
 	@ExceptionHandler(AuthenticationCredentialsNotFoundException.class)
-	public void handleAuthenticationCredentialsNotFoundException(final Exception e, final HttpServletRequest request) {
-		/* No implementation - handled solely by annotations */
+	@ResponseStatus(HttpStatus.UNAUTHORIZED)
+	@ResponseBody
+	public Map<String, Object> handleAuthenticationCredentialsNotFoundException(final Exception e, final HttpServletRequest request) {
+		return handleException(e);
 	}
 
 	@ExceptionHandler(AccessDeniedException.class)
-	public void handleAccessDeniedException(
+	@ResponseBody
+	public Map<String, Object> handleAccessDeniedException(
 			final Exception e,
 			final HttpServletRequest request,
 			final HttpServletResponse response
 			) {
 		final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-		if (
-				authentication == null
+		if (authentication == null
 				|| authentication.getPrincipal() == null
-				|| authentication instanceof AnonymousAuthenticationToken
-				) {
+				|| authentication instanceof AnonymousAuthenticationToken) {
 			response.setStatus(HttpStatus.UNAUTHORIZED.value());
-			return;
+		} else {
+			response.setStatus(HttpStatus.FORBIDDEN.value());
 		}
-		response.setStatus(HttpStatus.FORBIDDEN.value());
+
+		return handleException(e);
 	}
 
-	@ResponseStatus(HttpStatus.FORBIDDEN)
 	@ExceptionHandler(ForbiddenException.class)
-	public void handleForbiddenException(final Exception e, final HttpServletRequest request) {
-		/* No implementation - handled solely by annotations */
+	@ResponseBody
+	@ResponseStatus(HttpStatus.FORBIDDEN)
+	public Map<String, Object> handleForbiddenException(final Exception e, final HttpServletRequest request) {
+		return handleException(e);
 	}
 
-	@ResponseStatus(HttpStatus.NO_CONTENT)
 	@ExceptionHandler(NoContentException.class)
-	public void handleNoContentException(final Exception e, final HttpServletRequest request) {
-		/* No implementation - handled solely by annotations */
+	@ResponseBody
+	@ResponseStatus(HttpStatus.NO_CONTENT)
+	public Map<String, Object> handleNoContentException(final Exception e, final HttpServletRequest request) {
+		return handleException(e);
 	}
 
-	@ResponseStatus(HttpStatus.BAD_REQUEST)
 	@ExceptionHandler(BadRequestException.class)
-	public void handleBadRequestException(final Exception e, final HttpServletRequest request) {
-		/* No implementation - handled solely by annotations */
+	@ResponseBody
+	@ResponseStatus(HttpStatus.BAD_REQUEST)
+	public Map<String, Object> handleBadRequestException(final Exception e, final HttpServletRequest request) {
+		return handleException(e);
 	}
 
-	@ResponseStatus(HttpStatus.PRECONDITION_FAILED)
 	@ExceptionHandler(PreconditionFailedException.class)
-	public void handlePreconditionFailedException(final Exception e, final HttpServletRequest request) {
-		/* No implementation - handled solely by annotations */
+	@ResponseBody
+	@ResponseStatus(HttpStatus.PRECONDITION_FAILED)
+	public Map<String, Object> handlePreconditionFailedException(final Exception e, final HttpServletRequest request) {
+		return handleException(e);
 	}
 
-	@ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
 	@ExceptionHandler(NotImplementedException.class)
-	public void handleNotImplementedException(final Exception e, final HttpServletRequest request) {
-		/* No implementation - handled solely by annotations */
+	@ResponseBody
+	@ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
+	public Map<String, Object> handleNotImplementedException(final Exception e, final HttpServletRequest request) {
+		return handleException(e);
 	}
 
-	@ResponseStatus(HttpStatus.PAYLOAD_TOO_LARGE)
 	@ExceptionHandler(PayloadTooLargeException.class)
-	public void handlePayloadTooLargeException(final Exception e, final HttpServletRequest request) {
-		/* No implementation - handled solely by annotations */
+	@ResponseBody
+	@ResponseStatus(HttpStatus.PAYLOAD_TOO_LARGE)
+	public Map<String, Object> handlePayloadTooLargeException(final Exception e, final HttpServletRequest request) {
+		return handleException(e);
+	}
+
+	@ExceptionHandler(HttpMessageNotReadableException.class)
+	@ResponseBody
+	@ResponseStatus(HttpStatus.BAD_REQUEST)
+	public Map<String, Object> handleHttpMessageNotReadableException(final Exception e, final HttpServletRequest request) {
+		return handleException(e);
 	}
 }
diff --git a/src/main/java/de/thm/arsnova/controller/DefaultControllerExceptionHandler.java b/src/main/java/de/thm/arsnova/controller/DefaultControllerExceptionHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..fe8546c826bfe65e6c36cfc1a2b80d3f8d1eb25e
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/controller/DefaultControllerExceptionHandler.java
@@ -0,0 +1,31 @@
+package de.thm.arsnova.controller;
+
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Map;
+
+@ControllerAdvice
+public class DefaultControllerExceptionHandler extends AbstractControllerExceptionHandler {
+	@ExceptionHandler
+	@ResponseBody
+	@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+	public Map<String, Object> defaultExceptionHandler(
+			final Exception e,
+			final HttpServletRequest req
+	) throws Exception {
+		/* If the exception is annotated with @ResponseStatus rethrow it and let
+		 * the framework handle it.
+		 * See https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc. */
+		if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
+			throw e;
+		}
+
+		return handleException(e);
+	}
+}
diff --git a/src/site/markdown/development.md b/src/site/markdown/development.md
index 8aee947e0c48f297330c3330e11a448720fd7bff..e7bf9aaf527bff5691327a3746897d9b3ca4f325 100644
--- a/src/site/markdown/development.md
+++ b/src/site/markdown/development.md
@@ -37,6 +37,9 @@ Run the following command to download the dependencies and startup the backend w
 
 After a few seconds the ARSnova API will be accessible at <http://localhost:8080/>.
 
+You can adjust the amount of debug logging by changing the log levels in [log4j-dev.properties](src/main/resources/log4j-dev.properties).
+Additionally, you can enable exception messages in API responses by setting the boolean property `api.expose-error-messages` in `arsnova.properties`.
+
 
 ## Continuous Integration
 
diff --git a/src/test/java/de/thm/arsnova/controller/CourseControllerTest.java b/src/test/java/de/thm/arsnova/controller/CourseControllerTest.java
index 351a9ea5ddd7709b6e0dd8e79e09568715c27f9b..57b57c9ea3ab39230d86515de21e01a62c725deb 100644
--- a/src/test/java/de/thm/arsnova/controller/CourseControllerTest.java
+++ b/src/test/java/de/thm/arsnova/controller/CourseControllerTest.java
@@ -89,15 +89,15 @@ public class CourseControllerTest {
 	public void testShouldIndicateNotImplementedIfInactiveClient() throws Exception {
 		setAuthenticated(true, "ptsr00");
 
-		mockMvc.perform(get("/mycourses").accept(MediaType.TEXT_PLAIN))
-		.andExpect(status().isNotImplemented());
+		mockMvc.perform(get("/mycourses").accept(MediaType.APPLICATION_JSON))
+				.andExpect(status().isNotImplemented());
 	}
 
 	@Test
 	public void testShouldNotReturnCurrentUsersCoursesIfUnauthorized() throws Exception {
 		setAuthenticated(false, "nobody");
 
-		mockMvc.perform(get("/mycourses").accept(MediaType.TEXT_PLAIN))
-		.andExpect(status().isUnauthorized());
+		mockMvc.perform(get("/mycourses").accept(MediaType.APPLICATION_JSON))
+				.andExpect(status().isUnauthorized());
 	}
 }