From 7194bb00fd42d5abb5aa8ca96bd99f84a9eaea1b Mon Sep 17 00:00:00 2001
From: Colin DAMON <cdamon@ippon.fr>
Date: Sun, 23 Aug 2020 11:48:47 +0200
Subject: [PATCH] Custom errors management

---
 borestop/pom.xml                              |   9 +-
 .../borestop/common/domain/error/Assert.java  |  20 ++
 .../domain/error/BorestopException.java       | 175 ++++++++++
 .../common/domain/error/BorestopMessage.java  |   8 +
 .../common/domain/error/ErrorStatus.java      |  10 +
 .../error/MissingMandatoryValueException.java |  32 ++
 .../common/domain/error/StandardMessage.java  |  19 ++
 .../primary/ArgumentsReplacer.java            |  24 ++
 .../primary/AuthenticationMessage.java        |  17 +
 .../infrastructure/primary/BorestopError.java |  48 +++
 .../primary/BorestopErrorHandler.java         | 298 ++++++++++++++++++
 .../primary/BorestopFieldError.java           |  74 +++++
 .../primary/ValidationMessage.java            |  12 +
 .../config/AuthenticationErrorsHandler.java   |  38 +++
 .../com/ippon/borestop/config/Constants.java  |   2 +-
 .../borestop/config/JacksonConfiguration.java |  19 +-
 .../config/SecurityConfiguration.java         |  13 +-
 .../service/EmailAlreadyUsedException.java    |  11 +-
 .../service/InvalidPasswordException.java     |   9 +-
 .../service/LoginAlreadyUsedException.java    |  19 ++
 .../ippon/borestop/service/UserMessage.java   |  19 ++
 .../ippon/borestop/service/UserService.java   |   2 +-
 .../service/UsernameAlreadyUsedException.java |  12 -
 .../borestop/web/rest/AccountResource.java    |  14 +-
 .../ippon/borestop/web/rest/UserResource.java |  91 ++++--
 .../rest/errors/BadRequestAlertException.java |  42 ---
 .../errors/EmailAlreadyUsedException.java     |  12 -
 .../web/rest/errors/ErrorConstants.java       |  18 --
 .../web/rest/errors/ExceptionTranslator.java  | 136 --------
 .../web/rest/errors/FieldErrorVM.java         |  33 --
 .../rest/errors/InvalidPasswordException.java |  14 -
 .../errors/LoginAlreadyUsedException.java     |  12 -
 .../web/rest/errors/package-info.java         |   6 -
 .../main/resources/i18n/messages.properties   |  49 ++-
 .../resources/i18n/messages_en.properties     |  23 +-
 .../borestop/common/BorestopIntTest.java      |  20 ++
 .../com/ippon/borestop/common/LogSpy.java     |  62 ++++
 .../common/domain/error/AssertUnitTest.java   |  45 +++
 .../error/BorestopExceptionUnitTest.java      |  58 ++++
 .../primary/ArgumentsReplacerUnitTest.java    |  25 ++
 .../primary}/AuthenticationSteps.java         |   2 +-
 .../primary/BorestopErrorHandlerIntTest.java  | 210 ++++++++++++
 .../primary/BorestopErrorHandlerUnitTest.java | 218 +++++++++++++
 .../primary/BorestopErrorUnitTest.java        |  39 +++
 .../primary/BorestopFieldErrorUnitTest.java   |  30 ++
 .../primary/ComplicatedRequest.java           |  17 +
 .../primary}/CucumberTestContext.java         |   2 +-
 .../primary/ErrorMessagesUnitTest.java        |  83 +++++
 .../infrastructure/primary/ErrorResource.java |  77 +++++
 .../primary/NotDeserializable.java            |  13 +
 .../primary/QueryParameter.java               |  18 ++
 .../infrastructure/primary}/TestJson.java     |   2 +-
 .../cucumber/CucumberConfiguration.java       |   2 +-
 .../ippon/borestop/cucumber/HttpSteps.java    |   2 +-
 .../ippon/borestop/service/MailServiceIT.java |  79 +----
 .../borestop/web/rest/AccountResourceIT.java  | 105 +++---
 .../borestop/web/rest/AccountsSteps.java      |   2 +-
 .../rest/errors/ExceptionTranslatorIT.java    | 117 -------
 .../ExceptionTranslatorTestController.java    |  65 ----
 .../resources/i18n/messages_en.properties     |   4 -
 60 files changed, 1950 insertions(+), 687 deletions(-)
 create mode 100644 borestop/src/main/java/com/ippon/borestop/common/domain/error/Assert.java
 create mode 100644 borestop/src/main/java/com/ippon/borestop/common/domain/error/BorestopException.java
 create mode 100644 borestop/src/main/java/com/ippon/borestop/common/domain/error/BorestopMessage.java
 create mode 100644 borestop/src/main/java/com/ippon/borestop/common/domain/error/ErrorStatus.java
 create mode 100644 borestop/src/main/java/com/ippon/borestop/common/domain/error/MissingMandatoryValueException.java
 create mode 100644 borestop/src/main/java/com/ippon/borestop/common/domain/error/StandardMessage.java
 create mode 100644 borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/ArgumentsReplacer.java
 create mode 100644 borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/AuthenticationMessage.java
 create mode 100644 borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopError.java
 create mode 100644 borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandler.java
 create mode 100644 borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopFieldError.java
 create mode 100644 borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/ValidationMessage.java
 create mode 100644 borestop/src/main/java/com/ippon/borestop/config/AuthenticationErrorsHandler.java
 create mode 100644 borestop/src/main/java/com/ippon/borestop/service/LoginAlreadyUsedException.java
 create mode 100644 borestop/src/main/java/com/ippon/borestop/service/UserMessage.java
 delete mode 100644 borestop/src/main/java/com/ippon/borestop/service/UsernameAlreadyUsedException.java
 delete mode 100644 borestop/src/main/java/com/ippon/borestop/web/rest/errors/BadRequestAlertException.java
 delete mode 100644 borestop/src/main/java/com/ippon/borestop/web/rest/errors/EmailAlreadyUsedException.java
 delete mode 100644 borestop/src/main/java/com/ippon/borestop/web/rest/errors/ErrorConstants.java
 delete mode 100644 borestop/src/main/java/com/ippon/borestop/web/rest/errors/ExceptionTranslator.java
 delete mode 100644 borestop/src/main/java/com/ippon/borestop/web/rest/errors/FieldErrorVM.java
 delete mode 100644 borestop/src/main/java/com/ippon/borestop/web/rest/errors/InvalidPasswordException.java
 delete mode 100644 borestop/src/main/java/com/ippon/borestop/web/rest/errors/LoginAlreadyUsedException.java
 delete mode 100644 borestop/src/main/java/com/ippon/borestop/web/rest/errors/package-info.java
 create mode 100644 borestop/src/test/java/com/ippon/borestop/common/BorestopIntTest.java
 create mode 100644 borestop/src/test/java/com/ippon/borestop/common/LogSpy.java
 create mode 100644 borestop/src/test/java/com/ippon/borestop/common/domain/error/AssertUnitTest.java
 create mode 100644 borestop/src/test/java/com/ippon/borestop/common/domain/error/BorestopExceptionUnitTest.java
 create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ArgumentsReplacerUnitTest.java
 rename borestop/src/test/java/com/ippon/borestop/{infrastructure/primay => common/infrastructure/primary}/AuthenticationSteps.java (96%)
 create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandlerIntTest.java
 create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandlerUnitTest.java
 create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorUnitTest.java
 create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopFieldErrorUnitTest.java
 create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ComplicatedRequest.java
 rename borestop/src/test/java/com/ippon/borestop/{infrastructure/primay => common/infrastructure/primary}/CucumberTestContext.java (98%)
 create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ErrorMessagesUnitTest.java
 create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ErrorResource.java
 create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/NotDeserializable.java
 create mode 100644 borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/QueryParameter.java
 rename borestop/src/test/java/com/ippon/borestop/{infrastructure/primay => common/infrastructure/primary}/TestJson.java (96%)
 delete mode 100644 borestop/src/test/java/com/ippon/borestop/web/rest/errors/ExceptionTranslatorIT.java
 delete mode 100644 borestop/src/test/java/com/ippon/borestop/web/rest/errors/ExceptionTranslatorTestController.java
 delete mode 100644 borestop/src/test/resources/i18n/messages_en.properties

diff --git a/borestop/pom.xml b/borestop/pom.xml
index e984eed4..82e234ff 100644
--- a/borestop/pom.xml
+++ b/borestop/pom.xml
@@ -245,10 +245,6 @@
       <artifactId>spring-security-test</artifactId>
       <scope>test</scope>
     </dependency>
-    <dependency>
-      <groupId>org.zalando</groupId>
-      <artifactId>problem-spring-web</artifactId>
-    </dependency>
     <dependency>
       <groupId>io.jsonwebtoken</groupId>
       <artifactId>jjwt-api</artifactId>
@@ -305,6 +301,11 @@
       <artifactId>junit-vintage-engine</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.reflections</groupId>
+      <artifactId>reflections</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/borestop/src/main/java/com/ippon/borestop/common/domain/error/Assert.java b/borestop/src/main/java/com/ippon/borestop/common/domain/error/Assert.java
new file mode 100644
index 00000000..4d9b2556
--- /dev/null
+++ b/borestop/src/main/java/com/ippon/borestop/common/domain/error/Assert.java
@@ -0,0 +1,20 @@
+package com.ippon.borestop.common.domain.error;
+
+public final class Assert {
+
+  private Assert() {}
+
+  public static void notNull(String field, Object input) {
+    if (input == null) {
+      throw MissingMandatoryValueException.forNullValue(field);
+    }
+  }
+
+  public static void notBlank(String field, String input) {
+    notNull(field, input);
+
+    if (input.isBlank()) {
+      throw MissingMandatoryValueException.forBlankValue(field);
+    }
+  }
+}
diff --git a/borestop/src/main/java/com/ippon/borestop/common/domain/error/BorestopException.java b/borestop/src/main/java/com/ippon/borestop/common/domain/error/BorestopException.java
new file mode 100644
index 00000000..26efd7fe
--- /dev/null
+++ b/borestop/src/main/java/com/ippon/borestop/common/domain/error/BorestopException.java
@@ -0,0 +1,175 @@
+package com.ippon.borestop.common.domain.error;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Parent exception used in Borestop application. Those exceptions will be resolved as human readable errors.
+ *
+ * <p>
+ * You can use this implementation directly:
+ * </p>
+ *
+ * <p>
+ * <code>
+ *     <pre>
+ *       BorestopException.builder(StandardMessages.USER_MANDATORY)
+ *          .argument("key", "value")
+ *          .argument("other", "test")
+ *          .status(ErrorsHttpStatus.BAD_REQUEST)
+ *          .message("Error message")
+ *          .cause(new RuntimeException())
+ *          .build();
+ *     </pre>
+ *   </code>
+ * </p>
+ *
+ * <p>
+ * Or make extension exceptions:
+ * </p>
+ *
+ * <p>
+ * <code>
+ *     <pre>
+ *       public class MissingMandatoryValueException extends BorestopException {
+ *
+ *         public MissingMandatoryValueException(BorestopMessage borestopMessage, String fieldName) {
+ *           this(builder(borestopMessage, fieldName, defaultMessage(fieldName)));
+ *         }
+ *
+ *         protected MissingMandatoryValueException(BorestopExceptionBuilder builder) {
+ *           super(builder);
+ *         }
+ *
+ *         private static BorestopExceptionBuilder builder(BorestopMessage borestopMessage, String fieldName, String message) {
+ *           return BorestopException.builder(borestopMessage)
+ *               .status(ErrorsHttpStatus.INTERNAL_SERVER_ERROR)
+ *               .argument("field", fieldName)
+ *               .message(message);
+ *         }
+ *
+ *         private static String defaultMessage(String fieldName) {
+ *           return "The field \"" + fieldName + "\" is mandatory and wasn't set";
+ *         }
+ *       }
+ *     </pre>
+ *   </code>
+ * </p>
+ */
+public class BorestopException extends RuntimeException {
+  private final Map<String, String> arguments;
+  private final ErrorStatus status;
+  private final BorestopMessage borestopMessage;
+
+  protected BorestopException(BorestopExceptionBuilder builder) {
+    super(getMessage(builder), getCause(builder));
+    arguments = getArguments(builder);
+    status = getStatus(builder);
+    borestopMessage = getBorestopMessage(builder);
+  }
+
+  private static String getMessage(BorestopExceptionBuilder builder) {
+    if (builder == null) {
+      return null;
+    }
+
+    return builder.message;
+  }
+
+  private static Throwable getCause(BorestopExceptionBuilder builder) {
+    if (builder == null) {
+      return null;
+    }
+
+    return builder.cause;
+  }
+
+  private static Map<String, String> getArguments(BorestopExceptionBuilder builder) {
+    if (builder == null) {
+      return null;
+    }
+
+    return Collections.unmodifiableMap(builder.arguments);
+  }
+
+  private static ErrorStatus getStatus(BorestopExceptionBuilder builder) {
+    if (builder == null) {
+      return null;
+    }
+
+    return builder.status;
+  }
+
+  private static BorestopMessage getBorestopMessage(BorestopExceptionBuilder builder) {
+    if (builder == null) {
+      return null;
+    }
+
+    return builder.borestopMessage;
+  }
+
+  public static BorestopExceptionBuilder builder(BorestopMessage message) {
+    return new BorestopExceptionBuilder(message);
+  }
+
+  public Map<String, String> getArguments() {
+    return arguments;
+  }
+
+  public ErrorStatus getStatus() {
+    return status;
+  }
+
+  public BorestopMessage getBorestopMessage() {
+    return borestopMessage;
+  }
+
+  public static class BorestopExceptionBuilder {
+    private final Map<String, String> arguments = new HashMap<>();
+    private String message;
+    private ErrorStatus status;
+    private BorestopMessage borestopMessage;
+    private Throwable cause;
+
+    public BorestopExceptionBuilder(BorestopMessage borestopMessage) {
+      this.borestopMessage = borestopMessage;
+    }
+
+    public BorestopExceptionBuilder argument(String key, Object value) {
+      arguments.put(key, getStringValue(value));
+
+      return this;
+    }
+
+    private String getStringValue(Object value) {
+      if (value == null) {
+        return "null";
+      }
+
+      return value.toString();
+    }
+
+    public BorestopExceptionBuilder status(ErrorStatus status) {
+      this.status = status;
+
+      return this;
+    }
+
+    public BorestopExceptionBuilder message(String message) {
+      this.message = message;
+
+      return this;
+    }
+
+    public BorestopExceptionBuilder cause(Throwable cause) {
+      this.cause = cause;
+
+      return this;
+    }
+
+    public BorestopException build() {
+      return new BorestopException(this);
+    }
+  }
+}
diff --git a/borestop/src/main/java/com/ippon/borestop/common/domain/error/BorestopMessage.java b/borestop/src/main/java/com/ippon/borestop/common/domain/error/BorestopMessage.java
new file mode 100644
index 00000000..6a24d4c2
--- /dev/null
+++ b/borestop/src/main/java/com/ippon/borestop/common/domain/error/BorestopMessage.java
@@ -0,0 +1,8 @@
+package com.ippon.borestop.common.domain.error;
+
+import java.io.Serializable;
+
+@FunctionalInterface
+public interface BorestopMessage extends Serializable {
+  String getMessageKey();
+}
diff --git a/borestop/src/main/java/com/ippon/borestop/common/domain/error/ErrorStatus.java b/borestop/src/main/java/com/ippon/borestop/common/domain/error/ErrorStatus.java
new file mode 100644
index 00000000..c8aefe82
--- /dev/null
+++ b/borestop/src/main/java/com/ippon/borestop/common/domain/error/ErrorStatus.java
@@ -0,0 +1,10 @@
+package com.ippon.borestop.common.domain.error;
+
+public enum ErrorStatus {
+  BAD_REQUEST,
+  UNAUTHORIZED,
+  FORBIDDEN,
+  NOT_FOUND,
+  CONFLICT,
+  INTERNAL_SERVER_ERROR
+}
diff --git a/borestop/src/main/java/com/ippon/borestop/common/domain/error/MissingMandatoryValueException.java b/borestop/src/main/java/com/ippon/borestop/common/domain/error/MissingMandatoryValueException.java
new file mode 100644
index 00000000..51284aaa
--- /dev/null
+++ b/borestop/src/main/java/com/ippon/borestop/common/domain/error/MissingMandatoryValueException.java
@@ -0,0 +1,32 @@
+package com.ippon.borestop.common.domain.error;
+
+public class MissingMandatoryValueException extends BorestopException {
+
+  protected MissingMandatoryValueException(BorestopExceptionBuilder builder) {
+    super(builder);
+  }
+
+  private static BorestopExceptionBuilder builder(BorestopMessage borestopMessage, String fieldName, String message) {
+    return BorestopException
+      .builder(borestopMessage)
+      .status(ErrorStatus.INTERNAL_SERVER_ERROR)
+      .argument("field", fieldName)
+      .message(message);
+  }
+
+  private static String defaultMessage(String fieldName) {
+    return "The field \"" + fieldName + "\" is mandatory and wasn't set";
+  }
+
+  public static MissingMandatoryValueException forNullValue(String fieldName) {
+    return new MissingMandatoryValueException(
+      builder(StandardMessage.SERVER_MANDATORY_NULL, fieldName, defaultMessage(fieldName) + " (null)")
+    );
+  }
+
+  public static MissingMandatoryValueException forBlankValue(String fieldName) {
+    return new MissingMandatoryValueException(
+      builder(StandardMessage.SERVER_MANDATORY_BLANK, fieldName, defaultMessage(fieldName) + " (blank)")
+    );
+  }
+}
diff --git a/borestop/src/main/java/com/ippon/borestop/common/domain/error/StandardMessage.java b/borestop/src/main/java/com/ippon/borestop/common/domain/error/StandardMessage.java
new file mode 100644
index 00000000..91093d58
--- /dev/null
+++ b/borestop/src/main/java/com/ippon/borestop/common/domain/error/StandardMessage.java
@@ -0,0 +1,19 @@
+package com.ippon.borestop.common.domain.error;
+
+public enum StandardMessage implements BorestopMessage {
+  USER_MANDATORY("user.mandatory"),
+  BAD_REQUEST("user.bad-request"),
+  INTERNAL_SERVER_ERROR("server.internal-server-error"),
+  SERVER_MANDATORY_NULL("server.mandatory-null"),
+  SERVER_MANDATORY_BLANK("server.mandatory-blank");
+  private final String messageKey;
+
+  private StandardMessage(String code) {
+    this.messageKey = code;
+  }
+
+  @Override
+  public String getMessageKey() {
+    return messageKey;
+  }
+}
diff --git a/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/ArgumentsReplacer.java b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/ArgumentsReplacer.java
new file mode 100644
index 00000000..ca80b959
--- /dev/null
+++ b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/ArgumentsReplacer.java
@@ -0,0 +1,24 @@
+package com.ippon.borestop.common.infrastructure.primary;
+
+import java.util.Map;
+
+final class ArgumentsReplacer {
+  private static final String OPEN_MUSTACHE = "\\{\\{\\s*";
+  private static final String CLOSE_MUSTACHE = "\\s*\\}\\}";
+
+  private ArgumentsReplacer() {}
+
+  public static String replaceArguments(String message, Map<String, String> arguments) {
+    if (message == null || arguments == null) {
+      return message;
+    }
+
+    String result = message;
+    for (Map.Entry<String, String> argument : arguments.entrySet()) {
+      String argumentRegex = OPEN_MUSTACHE + argument.getKey() + CLOSE_MUSTACHE;
+      result = result.replaceAll(argumentRegex, argument.getValue());
+    }
+
+    return result;
+  }
+}
diff --git a/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/AuthenticationMessage.java b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/AuthenticationMessage.java
new file mode 100644
index 00000000..7bb80fa4
--- /dev/null
+++ b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/AuthenticationMessage.java
@@ -0,0 +1,17 @@
+package com.ippon.borestop.common.infrastructure.primary;
+
+import com.ippon.borestop.common.domain.error.BorestopMessage;
+
+enum AuthenticationMessage implements BorestopMessage {
+  NOT_AUTHENTICATED("user.authentication-not-authenticated");
+  private final String messageKey;
+
+  AuthenticationMessage(String messageKey) {
+    this.messageKey = messageKey;
+  }
+
+  @Override
+  public String getMessageKey() {
+    return messageKey;
+  }
+}
diff --git a/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopError.java b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopError.java
new file mode 100644
index 00000000..9e4cd83f
--- /dev/null
+++ b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopError.java
@@ -0,0 +1,48 @@
+/**
+ *
+ */
+package com.ippon.borestop.common.infrastructure.primary;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import java.util.Collection;
+import java.util.List;
+
+@ApiModel(description = "Error result for a WebService call")
+public class BorestopError {
+  @ApiModelProperty(value = "Technical type of this error", example = "user.mandatory", required = true)
+  private final String errorType;
+
+  @ApiModelProperty(
+    value = "Human readable error message",
+    example = "Une erreur technique est survenue lors du traitement de votre demande",
+    required = true
+  )
+  private final String message;
+
+  @ApiModelProperty(value = "Invalid fields", required = false)
+  private final List<BorestopFieldError> fieldsErrors;
+
+  public BorestopError(
+    @JsonProperty("errorType") String errorType,
+    @JsonProperty("message") String message,
+    @JsonProperty("fieldsErrors") List<BorestopFieldError> fieldsErrors
+  ) {
+    this.errorType = errorType;
+    this.message = message;
+    this.fieldsErrors = fieldsErrors;
+  }
+
+  public String getErrorType() {
+    return errorType;
+  }
+
+  public String getMessage() {
+    return message;
+  }
+
+  public Collection<BorestopFieldError> getFieldsErrors() {
+    return fieldsErrors;
+  }
+}
diff --git a/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandler.java b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandler.java
new file mode 100644
index 00000000..80578265
--- /dev/null
+++ b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandler.java
@@ -0,0 +1,298 @@
+/**
+ *
+ */
+package com.ippon.borestop.common.infrastructure.primary;
+
+import com.ippon.borestop.common.domain.error.BorestopException;
+import com.ippon.borestop.common.domain.error.ErrorStatus;
+import com.ippon.borestop.common.domain.error.StandardMessage;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.MessageSource;
+import org.springframework.context.NoSuchMessageException;
+import org.springframework.context.i18n.LocaleContextHolder;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.validation.BindException;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.client.RestClientResponseException;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
+import org.springframework.web.multipart.MaxUploadSizeExceededException;
+import org.springframework.web.server.ResponseStatusException;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+@ControllerAdvice
+public class BorestopErrorHandler extends ResponseEntityExceptionHandler {
+  private static final String MESSAGE_PREFIX = "borestop.error.";
+  private static final String DEFAULT_KEY = StandardMessage.INTERNAL_SERVER_ERROR.getMessageKey();
+  private static final String BAD_REQUEST_KEY = StandardMessage.BAD_REQUEST.getMessageKey();
+  private static final String STATUS_EXCEPTION_KEY = "status-exception";
+
+  private static final Logger logger = LoggerFactory.getLogger(BorestopErrorHandler.class);
+
+  private final MessageSource messages;
+
+  public BorestopErrorHandler(MessageSource messages) {
+    Locale.setDefault(Locale.FRANCE);
+    this.messages = messages;
+  }
+
+  @ExceptionHandler
+  public ResponseEntity<BorestopError> handleBorestopException(BorestopException exception) {
+    HttpStatus status = getStatus(exception);
+
+    logError(exception, status);
+
+    String messageKey = getMessageKey(status, exception);
+    BorestopError error = new BorestopError(messageKey, getMessage(messageKey, exception.getArguments()), null);
+    return new ResponseEntity<>(error, status);
+  }
+
+  @ExceptionHandler
+  public ResponseEntity<BorestopError> handleResponseStatusException(ResponseStatusException exception) {
+    HttpStatus status = exception.getStatus();
+
+    logError(exception, status);
+
+    BorestopError error = new BorestopError(STATUS_EXCEPTION_KEY, buildErrorStatusMessage(exception), null);
+    return new ResponseEntity<>(error, status);
+  }
+
+  private String buildErrorStatusMessage(ResponseStatusException exception) {
+    String reason = exception.getReason();
+
+    if (StringUtils.isBlank(reason)) {
+      Map<String, String> statusArgument = Map.of("status", String.valueOf(exception.getStatus().value()));
+
+      return getMessage(STATUS_EXCEPTION_KEY, statusArgument);
+    }
+
+    return reason;
+  }
+
+  @ExceptionHandler(MaxUploadSizeExceededException.class)
+  public ResponseEntity<BorestopError> handleFileSizeException(MaxUploadSizeExceededException maxUploadSizeExceededException) {
+    logger.warn("File size limit exceeded: {}", maxUploadSizeExceededException.getMessage(), maxUploadSizeExceededException);
+
+    BorestopError error = new BorestopError("server.upload-too-big", getMessage("server.upload-too-big", null), null);
+    return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
+  }
+
+  @ExceptionHandler(AccessDeniedException.class)
+  public ResponseEntity<BorestopError> handleAccessDeniedException(AccessDeniedException accessDeniedException) {
+    BorestopError error = new BorestopError("user.access-denied", getMessage("user.access-denied", null), null);
+    return new ResponseEntity<>(error, HttpStatus.FORBIDDEN);
+  }
+
+  @ExceptionHandler(MethodArgumentTypeMismatchException.class)
+  public ResponseEntity<BorestopError> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException exception) {
+    Throwable rootCause = ExceptionUtils.getRootCause(exception);
+    if (rootCause instanceof BorestopException) {
+      return handleBorestopException((BorestopException) rootCause);
+    }
+
+    BorestopError error = new BorestopError(BAD_REQUEST_KEY, getMessage(BAD_REQUEST_KEY, null), null);
+    return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
+  }
+
+  private HttpStatus getStatus(BorestopException exception) {
+    ErrorStatus status = exception.getStatus();
+    if (status == null) {
+      return HttpStatus.INTERNAL_SERVER_ERROR;
+    }
+
+    switch (status) {
+      case BAD_REQUEST:
+        return HttpStatus.BAD_REQUEST;
+      case UNAUTHORIZED:
+        return HttpStatus.UNAUTHORIZED;
+      case FORBIDDEN:
+        return HttpStatus.FORBIDDEN;
+      case NOT_FOUND:
+        return HttpStatus.NOT_FOUND;
+      case CONFLICT:
+        return HttpStatus.CONFLICT;
+      default:
+        return HttpStatus.INTERNAL_SERVER_ERROR;
+    }
+  }
+
+  private void logError(Exception exception, HttpStatus status) {
+    if (status.is5xxServerError()) {
+      logger.error("A server error was sent to a user: {}", exception.getMessage(), exception);
+
+      logErrorBody(exception);
+    } else {
+      logger.warn("An error was sent to a user: {}", exception.getMessage(), exception);
+    }
+  }
+
+  private void logErrorBody(Exception exception) {
+    Throwable cause = exception.getCause();
+    if (cause instanceof RestClientResponseException) {
+      RestClientResponseException restCause = (RestClientResponseException) cause;
+
+      logger.error("Cause body: {}", restCause.getResponseBodyAsString());
+    }
+  }
+
+  private String getMessageKey(HttpStatus status, BorestopException exception) {
+    if (exception.getBorestopMessage() == null) {
+      return getDefaultMessage(status);
+    }
+
+    return exception.getBorestopMessage().getMessageKey();
+  }
+
+  private String getDefaultMessage(HttpStatus status) {
+    if (status.is5xxServerError()) {
+      return DEFAULT_KEY;
+    }
+
+    return BAD_REQUEST_KEY;
+  }
+
+  @Override
+  protected ResponseEntity<Object> handleMethodArgumentNotValid(
+    MethodArgumentNotValidException exception,
+    HttpHeaders headers,
+    HttpStatus status,
+    WebRequest request
+  ) {
+    logger.debug("Bean validation error {}", exception.getMessage(), exception);
+
+    BorestopError error = new BorestopError(BAD_REQUEST_KEY, getMessage(BAD_REQUEST_KEY, null), getFieldsError(exception));
+    return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
+  }
+
+  @ExceptionHandler
+  public ResponseEntity<BorestopError> handleBeanValidationError(ConstraintViolationException exception) {
+    logger.debug("Bean validation error {}", exception.getMessage(), exception);
+
+    BorestopError error = new BorestopError(BAD_REQUEST_KEY, getMessage(BAD_REQUEST_KEY, null), getFieldsErrors(exception));
+    return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
+  }
+
+  @Override
+  protected ResponseEntity<Object> handleBindException(
+    BindException exception,
+    HttpHeaders headers,
+    HttpStatus status,
+    WebRequest request
+  ) {
+    List<BorestopFieldError> fieldErrors = exception.getFieldErrors().stream().map(toBorestopFieldError()).collect(Collectors.toList());
+
+    BorestopError error = new BorestopError(BAD_REQUEST_KEY, getMessage(BAD_REQUEST_KEY, null), fieldErrors);
+
+    return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
+  }
+
+  private ConstraintViolation<?> extractSource(FieldError error) {
+    return error.unwrap(ConstraintViolation.class);
+  }
+
+  private String getMessage(String messageKey, Map<String, String> arguments) {
+    String text = getMessageFromSource(messageKey);
+
+    return ArgumentsReplacer.replaceArguments(text, arguments);
+  }
+
+  private String getMessageFromSource(String messageKey) {
+    Locale locale = LocaleContextHolder.getLocale();
+
+    try {
+      return messages.getMessage(MESSAGE_PREFIX + messageKey, null, locale);
+    } catch (NoSuchMessageException e) {
+      return messages.getMessage(MESSAGE_PREFIX + DEFAULT_KEY, null, locale);
+    }
+  }
+
+  private List<BorestopFieldError> getFieldsErrors(ConstraintViolationException exception) {
+    return exception.getConstraintViolations().stream().map(toFieldError()).collect(Collectors.toList());
+  }
+
+  private List<BorestopFieldError> getFieldsError(MethodArgumentNotValidException exception) {
+    return exception.getBindingResult().getFieldErrors().stream().map(toBorestopFieldError()).collect(Collectors.toList());
+  }
+
+  private Function<FieldError, BorestopFieldError> toBorestopFieldError() {
+    return error ->
+      BorestopFieldError
+        .builder()
+        .fieldPath(error.getField())
+        .reason(error.getDefaultMessage())
+        .message(getMessage(error.getDefaultMessage(), buildArguments(extractSource(error))))
+        .build();
+  }
+
+  private Map<String, String> buildArguments(ConstraintViolation<?> violation) {
+    return violation
+      .getConstraintDescriptor()
+      .getAttributes()
+      .entrySet()
+      .stream()
+      .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().toString()));
+  }
+
+  private Function<ConstraintViolation<?>, BorestopFieldError> toFieldError() {
+    return violation -> {
+      Map<String, String> arguments = buildArguments(violation);
+
+      String message = violation.getMessage();
+      return BorestopFieldError
+        .builder()
+        .fieldPath(violation.getPropertyPath().toString())
+        .reason(message)
+        .message(getMessage(message, arguments))
+        .build();
+    };
+  }
+
+  @Override
+  protected ResponseEntity<Object> handleHttpMessageNotReadable(
+    HttpMessageNotReadableException exception,
+    HttpHeaders headers,
+    HttpStatus status,
+    WebRequest request
+  ) {
+    logger.error("Error reading query information: {}", exception.getMessage(), exception);
+
+    BorestopError error = new BorestopError(DEFAULT_KEY, getMessage(DEFAULT_KEY, null), null);
+    return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
+  }
+
+  @ExceptionHandler
+  public ResponseEntity<BorestopError> handleAuthenticationException(AuthenticationException exception) {
+    logger.debug("A user tried to do an unauthorized operation: {}", exception.getMessage(), exception);
+
+    String message = AuthenticationMessage.NOT_AUTHENTICATED.getMessageKey();
+    BorestopError error = new BorestopError(message, getMessage(message, null), null);
+
+    return new ResponseEntity<>(error, HttpStatus.UNAUTHORIZED);
+  }
+
+  @ExceptionHandler
+  public ResponseEntity<BorestopError> handleRuntimeException(Throwable throwable) {
+    logger.error("An unhandled error occurs: {}", throwable.getMessage(), throwable);
+
+    BorestopError error = new BorestopError(DEFAULT_KEY, getMessage(DEFAULT_KEY, null), null);
+    return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
+  }
+}
diff --git a/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopFieldError.java b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopFieldError.java
new file mode 100644
index 00000000..7a771603
--- /dev/null
+++ b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/BorestopFieldError.java
@@ -0,0 +1,74 @@
+/**
+ *
+ */
+package com.ippon.borestop.common.infrastructure.primary;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import com.ippon.borestop.common.infrastructure.primary.BorestopFieldError.BorestopFieldErrorBuilder;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+@JsonDeserialize(builder = BorestopFieldErrorBuilder.class)
+@ApiModel(description = "Error for a field validation")
+public class BorestopFieldError {
+  @ApiModelProperty(value = "Path to the field in error", example = "address.country", required = true)
+  private final String fieldPath;
+
+  @ApiModelProperty(value = "Technical reason for the invalidation", example = "user.mandatory", required = true)
+  private final String reason;
+
+  @ApiModelProperty(value = "Human readable message for the invalidation", example = "Le champ doit être renseigné", required = true)
+  private final String message;
+
+  private BorestopFieldError(BorestopFieldErrorBuilder builder) {
+    fieldPath = builder.fieldPath;
+    reason = builder.reason;
+    message = builder.message;
+  }
+
+  public static BorestopFieldErrorBuilder builder() {
+    return new BorestopFieldErrorBuilder();
+  }
+
+  public String getFieldPath() {
+    return fieldPath;
+  }
+
+  public String getReason() {
+    return reason;
+  }
+
+  public String getMessage() {
+    return message;
+  }
+
+  @JsonPOJOBuilder(withPrefix = "")
+  public static class BorestopFieldErrorBuilder {
+    private String fieldPath;
+    private String reason;
+    private String message;
+
+    public BorestopFieldErrorBuilder fieldPath(String fieldPath) {
+      this.fieldPath = fieldPath;
+
+      return this;
+    }
+
+    public BorestopFieldErrorBuilder reason(String reason) {
+      this.reason = reason;
+
+      return this;
+    }
+
+    public BorestopFieldErrorBuilder message(String message) {
+      this.message = message;
+
+      return this;
+    }
+
+    public BorestopFieldError build() {
+      return new BorestopFieldError(this);
+    }
+  }
+}
diff --git a/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/ValidationMessage.java b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/ValidationMessage.java
new file mode 100644
index 00000000..bd7eb0a4
--- /dev/null
+++ b/borestop/src/main/java/com/ippon/borestop/common/infrastructure/primary/ValidationMessage.java
@@ -0,0 +1,12 @@
+package com.ippon.borestop.common.infrastructure.primary;
+
+public final class ValidationMessage {
+  public static final String MANDATORY = "user.mandatory";
+  public static final String WRONG_FORMAT = "user.wrong-format";
+  public static final String RPPS_FORMAT = "user.wrong-national-id-format";
+  public static final String MAIL_FORMAT = "user.wrong-mail-format";
+  public static final String VALUE_TOO_LOW = "user.too-low";
+  public static final String VALUE_TOO_HIGH = "user.too-high";
+
+  private ValidationMessage() {}
+}
diff --git a/borestop/src/main/java/com/ippon/borestop/config/AuthenticationErrorsHandler.java b/borestop/src/main/java/com/ippon/borestop/config/AuthenticationErrorsHandler.java
new file mode 100644
index 00000000..cc292bd9
--- /dev/null
+++ b/borestop/src/main/java/com/ippon/borestop/config/AuthenticationErrorsHandler.java
@@ -0,0 +1,38 @@
+package com.ippon.borestop.config;
+
+import com.ippon.borestop.common.infrastructure.Generated;
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerExceptionResolver;
+
+@Generated
+@Component
+class AuthenticationErrorsHandler implements AuthenticationEntryPoint, AccessDeniedHandler {
+  private final HandlerExceptionResolver resolver;
+
+  @Autowired
+  public AuthenticationErrorsHandler(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
+    this.resolver = resolver;
+  }
+
+  @Override
+  public void commence(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException exception)
+    throws IOException, ServletException {
+    resolver.resolveException(request, response, null, exception);
+  }
+
+  @Override
+  public void handle(final HttpServletRequest request, final HttpServletResponse response, final AccessDeniedException exception)
+    throws IOException, ServletException {
+    resolver.resolveException(request, response, null, exception);
+  }
+}
diff --git a/borestop/src/main/java/com/ippon/borestop/config/Constants.java b/borestop/src/main/java/com/ippon/borestop/config/Constants.java
index 63741033..a650d3b2 100644
--- a/borestop/src/main/java/com/ippon/borestop/config/Constants.java
+++ b/borestop/src/main/java/com/ippon/borestop/config/Constants.java
@@ -11,7 +11,7 @@ public final class Constants {
   public static final String LOGIN_REGEX = "^(?>[a-zA-Z0-9!$&*+=?^_`{|}~.-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)|(?>[_.@A-Za-z0-9-]+)$";
 
   public static final String SYSTEM_ACCOUNT = "system";
-  public static final String DEFAULT_LANGUAGE = "en";
+  public static final String DEFAULT_LANGUAGE = "fr";
   public static final String ANONYMOUS_USER = "anonymoususer";
 
   private Constants() {}
diff --git a/borestop/src/main/java/com/ippon/borestop/config/JacksonConfiguration.java b/borestop/src/main/java/com/ippon/borestop/config/JacksonConfiguration.java
index d000b8c1..2ce01440 100644
--- a/borestop/src/main/java/com/ippon/borestop/config/JacksonConfiguration.java
+++ b/borestop/src/main/java/com/ippon/borestop/config/JacksonConfiguration.java
@@ -6,8 +6,6 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import com.ippon.borestop.common.infrastructure.Generated;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.zalando.problem.ProblemModule;
-import org.zalando.problem.violations.ConstraintViolationProblemModule;
 
 @Generated
 @Configuration
@@ -15,6 +13,7 @@ public class JacksonConfiguration {
 
   /**
    * Support for Java date and time API.
+   *
    * @return the corresponding Jackson module.
    */
   @Bean
@@ -34,20 +33,4 @@ public class JacksonConfiguration {
   public Hibernate5Module hibernate5Module() {
     return new Hibernate5Module();
   }
-
-  /*
-   * Module for serialization/deserialization of RFC7807 Problem.
-   */
-  @Bean
-  public ProblemModule problemModule() {
-    return new ProblemModule();
-  }
-
-  /*
-   * Module for serialization/deserialization of ConstraintViolationProblem.
-   */
-  @Bean
-  public ConstraintViolationProblemModule constraintViolationProblemModule() {
-    return new ConstraintViolationProblemModule();
-  }
 }
diff --git a/borestop/src/main/java/com/ippon/borestop/config/SecurityConfiguration.java b/borestop/src/main/java/com/ippon/borestop/config/SecurityConfiguration.java
index 156106b6..364d50ce 100644
--- a/borestop/src/main/java/com/ippon/borestop/config/SecurityConfiguration.java
+++ b/borestop/src/main/java/com/ippon/borestop/config/SecurityConfiguration.java
@@ -5,7 +5,6 @@ import com.ippon.borestop.security.AuthoritiesConstants;
 import com.ippon.borestop.security.jwt.JWTConfigurer;
 import com.ippon.borestop.security.jwt.TokenProvider;
 import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Import;
 import org.springframework.http.HttpMethod;
 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -22,22 +21,20 @@ import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
 import org.springframework.security.web.csrf.CsrfTokenRepository;
 import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;
 import org.springframework.web.filter.CorsFilter;
-import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;
 
 @Generated
 @EnableWebSecurity
 @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
-@Import(SecurityProblemSupport.class)
 public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
   private final TokenProvider tokenProvider;
 
   private final CorsFilter corsFilter;
-  private final SecurityProblemSupport problemSupport;
+  private final AuthenticationErrorsHandler errorsHandler;
 
-  public SecurityConfiguration(TokenProvider tokenProvider, CorsFilter corsFilter, SecurityProblemSupport problemSupport) {
+  public SecurityConfiguration(TokenProvider tokenProvider, CorsFilter corsFilter, AuthenticationErrorsHandler errorsHandler) {
     this.tokenProvider = tokenProvider;
     this.corsFilter = corsFilter;
-    this.problemSupport = problemSupport;
+    this.errorsHandler = errorsHandler;
   }
 
   @Bean
@@ -67,8 +64,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
             .disable()
             .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
             .exceptionHandling()
-                .authenticationEntryPoint(problemSupport)
-                .accessDeniedHandler(problemSupport)
+                .authenticationEntryPoint(errorsHandler)
+                .accessDeniedHandler(errorsHandler)
         .and()
             .headers()
             .contentSecurityPolicy("default-src 'self'; frame-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://storage.googleapis.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:")
diff --git a/borestop/src/main/java/com/ippon/borestop/service/EmailAlreadyUsedException.java b/borestop/src/main/java/com/ippon/borestop/service/EmailAlreadyUsedException.java
index f2bccc96..3593d0ba 100644
--- a/borestop/src/main/java/com/ippon/borestop/service/EmailAlreadyUsedException.java
+++ b/borestop/src/main/java/com/ippon/borestop/service/EmailAlreadyUsedException.java
@@ -1,12 +1,19 @@
 package com.ippon.borestop.service;
 
+import com.ippon.borestop.common.domain.error.BorestopException;
+import com.ippon.borestop.common.domain.error.ErrorStatus;
 import com.ippon.borestop.common.infrastructure.Generated;
 
 @Generated
-public class EmailAlreadyUsedException extends RuntimeException {
+public class EmailAlreadyUsedException extends BorestopException {
   private static final long serialVersionUID = 1L;
 
   public EmailAlreadyUsedException() {
-    super("Email is already in use!");
+    super(
+      BorestopException
+        .builder(UserMessage.EMAIL_ALREADY_USED)
+        .message("A user tried to create an account with an used email")
+        .status(ErrorStatus.BAD_REQUEST)
+    );
   }
 }
diff --git a/borestop/src/main/java/com/ippon/borestop/service/InvalidPasswordException.java b/borestop/src/main/java/com/ippon/borestop/service/InvalidPasswordException.java
index 3c044746..c296cf52 100644
--- a/borestop/src/main/java/com/ippon/borestop/service/InvalidPasswordException.java
+++ b/borestop/src/main/java/com/ippon/borestop/service/InvalidPasswordException.java
@@ -1,12 +1,15 @@
 package com.ippon.borestop.service;
 
+import com.ippon.borestop.common.domain.error.BorestopException;
+import com.ippon.borestop.common.domain.error.ErrorStatus;
 import com.ippon.borestop.common.infrastructure.Generated;
 
 @Generated
-public class InvalidPasswordException extends RuntimeException {
-  private static final long serialVersionUID = 1L;
+public class InvalidPasswordException extends BorestopException {
 
   public InvalidPasswordException() {
-    super("Incorrect password");
+    super(
+      BorestopException.builder(UserMessage.INVALID_PASSWORD).status(ErrorStatus.BAD_REQUEST).message("A user entered an invalid password")
+    );
   }
 }
diff --git a/borestop/src/main/java/com/ippon/borestop/service/LoginAlreadyUsedException.java b/borestop/src/main/java/com/ippon/borestop/service/LoginAlreadyUsedException.java
new file mode 100644
index 00000000..69cc20d3
--- /dev/null
+++ b/borestop/src/main/java/com/ippon/borestop/service/LoginAlreadyUsedException.java
@@ -0,0 +1,19 @@
+package com.ippon.borestop.service;
+
+import com.ippon.borestop.common.domain.error.BorestopException;
+import com.ippon.borestop.common.domain.error.ErrorStatus;
+import com.ippon.borestop.common.infrastructure.Generated;
+
+@Generated
+public class LoginAlreadyUsedException extends BorestopException {
+  private static final long serialVersionUID = 1L;
+
+  public LoginAlreadyUsedException() {
+    super(
+      BorestopException
+        .builder(UserMessage.LOGIN_ALREADY_USED)
+        .status(ErrorStatus.BAD_REQUEST)
+        .message("A user tried to create an account with an used login")
+    );
+  }
+}
diff --git a/borestop/src/main/java/com/ippon/borestop/service/UserMessage.java b/borestop/src/main/java/com/ippon/borestop/service/UserMessage.java
new file mode 100644
index 00000000..beadd404
--- /dev/null
+++ b/borestop/src/main/java/com/ippon/borestop/service/UserMessage.java
@@ -0,0 +1,19 @@
+package com.ippon.borestop.service;
+
+import com.ippon.borestop.common.domain.error.BorestopMessage;
+
+enum UserMessage implements BorestopMessage {
+  EMAIL_ALREADY_USED("user.e-mail-already-used"),
+  LOGIN_ALREADY_USED("user.login-already-used"),
+  INVALID_PASSWORD("user.invalid-password");
+  private final String messageKey;
+
+  private UserMessage(String messageKey) {
+    this.messageKey = messageKey;
+  }
+
+  @Override
+  public String getMessageKey() {
+    return messageKey;
+  }
+}
diff --git a/borestop/src/main/java/com/ippon/borestop/service/UserService.java b/borestop/src/main/java/com/ippon/borestop/service/UserService.java
index e8f52e98..25d86ddd 100644
--- a/borestop/src/main/java/com/ippon/borestop/service/UserService.java
+++ b/borestop/src/main/java/com/ippon/borestop/service/UserService.java
@@ -94,7 +94,7 @@ public class UserService {
         existingUser -> {
           boolean removed = removeNonActivatedUser(existingUser);
           if (!removed) {
-            throw new UsernameAlreadyUsedException();
+            throw new LoginAlreadyUsedException();
           }
         }
       );
diff --git a/borestop/src/main/java/com/ippon/borestop/service/UsernameAlreadyUsedException.java b/borestop/src/main/java/com/ippon/borestop/service/UsernameAlreadyUsedException.java
deleted file mode 100644
index cdcbeb94..00000000
--- a/borestop/src/main/java/com/ippon/borestop/service/UsernameAlreadyUsedException.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.ippon.borestop.service;
-
-import com.ippon.borestop.common.infrastructure.Generated;
-
-@Generated
-public class UsernameAlreadyUsedException extends RuntimeException {
-  private static final long serialVersionUID = 1L;
-
-  public UsernameAlreadyUsedException() {
-    super("Login name already used!");
-  }
-}
diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/AccountResource.java b/borestop/src/main/java/com/ippon/borestop/web/rest/AccountResource.java
index e3323fe1..56cf229c 100644
--- a/borestop/src/main/java/com/ippon/borestop/web/rest/AccountResource.java
+++ b/borestop/src/main/java/com/ippon/borestop/web/rest/AccountResource.java
@@ -4,21 +4,29 @@ import com.ippon.borestop.common.infrastructure.Generated;
 import com.ippon.borestop.domain.User;
 import com.ippon.borestop.repository.UserRepository;
 import com.ippon.borestop.security.SecurityUtils;
+import com.ippon.borestop.service.EmailAlreadyUsedException;
+import com.ippon.borestop.service.InvalidPasswordException;
+import com.ippon.borestop.service.LoginAlreadyUsedException;
 import com.ippon.borestop.service.MailService;
 import com.ippon.borestop.service.UserService;
 import com.ippon.borestop.service.dto.PasswordChangeDTO;
 import com.ippon.borestop.service.dto.UserDTO;
-import com.ippon.borestop.web.rest.errors.*;
 import com.ippon.borestop.web.rest.vm.KeyAndPasswordVM;
 import com.ippon.borestop.web.rest.vm.ManagedUserVM;
-import java.util.*;
+import java.util.Optional;
 import javax.servlet.http.HttpServletRequest;
 import javax.validation.Valid;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
 
 /**
  * REST controller for managing the current user's account.
diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/UserResource.java b/borestop/src/main/java/com/ippon/borestop/web/rest/UserResource.java
index 6a608dfe..5f053f1b 100644
--- a/borestop/src/main/java/com/ippon/borestop/web/rest/UserResource.java
+++ b/borestop/src/main/java/com/ippon/borestop/web/rest/UserResource.java
@@ -1,22 +1,25 @@
 package com.ippon.borestop.web.rest;
 
+import com.ippon.borestop.common.domain.error.BorestopException;
+import com.ippon.borestop.common.domain.error.ErrorStatus;
+import com.ippon.borestop.common.domain.error.StandardMessage;
 import com.ippon.borestop.common.infrastructure.Generated;
 import com.ippon.borestop.config.Constants;
 import com.ippon.borestop.domain.User;
 import com.ippon.borestop.repository.UserRepository;
 import com.ippon.borestop.security.AuthoritiesConstants;
+import com.ippon.borestop.service.EmailAlreadyUsedException;
+import com.ippon.borestop.service.LoginAlreadyUsedException;
 import com.ippon.borestop.service.MailService;
 import com.ippon.borestop.service.UserService;
 import com.ippon.borestop.service.dto.UserDTO;
-import com.ippon.borestop.web.rest.errors.BadRequestAlertException;
-import com.ippon.borestop.web.rest.errors.EmailAlreadyUsedException;
-import com.ippon.borestop.web.rest.errors.LoginAlreadyUsedException;
 import io.github.jhipster.web.util.HeaderUtil;
 import io.github.jhipster.web.util.PaginationUtil;
 import io.github.jhipster.web.util.ResponseUtil;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.util.*;
+import java.util.List;
+import java.util.Optional;
 import javax.validation.Valid;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -27,7 +30,14 @@ import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
 
 /**
@@ -35,21 +45,20 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
  * <p>
  * This class accesses the {@link User} entity, and needs to fetch its collection of authorities.
  * <p>
- * For a normal use-case, it would be better to have an eager relationship between User and Authority,
- * and send everything to the client side: there would be no View Model and DTO, a lot less code, and an outer-join
- * which would be good for performance.
+ * For a normal use-case, it would be better to have an eager relationship between User and Authority, and send
+ * everything to the client side: there would be no View Model and DTO, a lot less code, and an outer-join which would
+ * be good for performance.
  * <p>
  * We use a View Model and a DTO for 3 reasons:
  * <ul>
- * <li>We want to keep a lazy association between the user and the authorities, because people will
- * quite often do relationships with the user, and we don't want them to get the authorities all
- * the time for nothing (for performance reasons). This is the #1 goal: we should not impact our users'
- * application because of this use-case.</li>
- * <li> Not having an outer join causes n+1 requests to the database. This is not a real issue as
- * we have by default a second-level cache. This means on the first HTTP call we do the n+1 requests,
- * but then all authorities come from the cache, so in fact it's much better than doing an outer join
- * (which will get lots of data from the database, for each HTTP call).</li>
- * <li> As this manages users, for security reasons, we'd rather have a DTO layer.</li>
+ * <li>We want to keep a lazy association between the user and the authorities, because people will quite often do
+ * relationships with the user, and we don't want them to get the authorities all the time for nothing (for performance
+ * reasons). This is the #1 goal: we should not impact our users' application because of this use-case.</li>
+ * <li>Not having an outer join causes n+1 requests to the database. This is not a real issue as we have by default a
+ * second-level cache. This means on the first HTTP call we do the n+1 requests, but then all authorities come from the
+ * cache, so in fact it's much better than doing an outer join (which will get lots of data from the database, for each
+ * HTTP call).</li>
+ * <li>As this manages users, for security reasons, we'd rather have a DTO layer.</li>
  * </ul>
  * <p>
  * Another option would be to have a specific JPA entity graph to handle this case.
@@ -76,16 +85,19 @@ public class UserResource {
   }
 
   /**
-   * {@code POST  /users}  : Creates a new user.
+   * {@code POST  /users} : Creates a new user.
    * <p>
-   * Creates a new user if the login and email are not already used, and sends an
-   * mail with an activation link.
-   * The user needs to be activated on creation.
+   * Creates a new user if the login and email are not already used, and sends an mail with an activation link. The user
+   * needs to be activated on creation.
    *
-   * @param userDTO the user to create.
-   * @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new user, or with status {@code 400 (Bad Request)} if the login or email is already in use.
-   * @throws URISyntaxException if the Location URI syntax is incorrect.
-   * @throws BadRequestAlertException {@code 400 (Bad Request)} if the login or email is already in use.
+   * @param userDTO
+   *          the user to create.
+   * @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new user, or with status
+   *         {@code 400 (Bad Request)} if the login or email is already in use.
+   * @throws URISyntaxException
+   *           if the Location URI syntax is incorrect.
+   * @throws BadRequestAlertException
+   *           {@code 400 (Bad Request)} if the login or email is already in use.
    */
   @PostMapping("/users")
   @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")")
@@ -93,8 +105,11 @@ public class UserResource {
     log.debug("REST request to save User : {}", userDTO);
 
     if (userDTO.getId() != null) {
-      throw new BadRequestAlertException("A new user cannot already have an ID", "userManagement", "idexists");
-    // Lowercase the user login before comparing with database
+      throw BorestopException
+        .builder(StandardMessage.INTERNAL_SERVER_ERROR)
+        .message("Can't create a user with a setted login")
+        .status(ErrorStatus.BAD_REQUEST)
+        .build(); // Lowercase the user login before comparing with database
     } else if (userRepository.findOneByLogin(userDTO.getLogin().toLowerCase()).isPresent()) {
       throw new LoginAlreadyUsedException();
     } else if (userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()).isPresent()) {
@@ -112,10 +127,13 @@ public class UserResource {
   /**
    * {@code PUT /users} : Updates an existing User.
    *
-   * @param userDTO the user to update.
+   * @param userDTO
+   *          the user to update.
    * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated user.
-   * @throws EmailAlreadyUsedException {@code 400 (Bad Request)} if the email is already in use.
-   * @throws LoginAlreadyUsedException {@code 400 (Bad Request)} if the login is already in use.
+   * @throws EmailAlreadyUsedException
+   *           {@code 400 (Bad Request)} if the email is already in use.
+   * @throws LoginAlreadyUsedException
+   *           {@code 400 (Bad Request)} if the login is already in use.
    */
   @PutMapping("/users")
   @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")")
@@ -137,7 +155,8 @@ public class UserResource {
   /**
    * {@code GET /users} : get all users.
    *
-   * @param pageable the pagination information.
+   * @param pageable
+   *          the pagination information.
    * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body all users.
    */
   @GetMapping("/users")
@@ -149,6 +168,7 @@ public class UserResource {
 
   /**
    * Gets a list of all roles.
+   *
    * @return a string list of all roles.
    */
   @GetMapping("/users/authorities")
@@ -160,8 +180,10 @@ public class UserResource {
   /**
    * {@code GET /users/:login} : get the "login" user.
    *
-   * @param login the login of the user to find.
-   * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the "login" user, or with status {@code 404 (Not Found)}.
+   * @param login
+   *          the login of the user to find.
+   * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the "login" user, or with status
+   *         {@code 404 (Not Found)}.
    */
   @GetMapping("/users/{login:" + Constants.LOGIN_REGEX + "}")
   public ResponseEntity<UserDTO> getUser(@PathVariable String login) {
@@ -172,7 +194,8 @@ public class UserResource {
   /**
    * {@code DELETE /users/:login} : delete the "login" User.
    *
-   * @param login the login of the user to delete.
+   * @param login
+   *          the login of the user to delete.
    * @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}.
    */
   @DeleteMapping("/users/{login:" + Constants.LOGIN_REGEX + "}")
diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/BadRequestAlertException.java b/borestop/src/main/java/com/ippon/borestop/web/rest/errors/BadRequestAlertException.java
deleted file mode 100644
index 4c83f779..00000000
--- a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/BadRequestAlertException.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.ippon.borestop.web.rest.errors;
-
-import com.ippon.borestop.common.infrastructure.Generated;
-import java.net.URI;
-import java.util.HashMap;
-import java.util.Map;
-import org.zalando.problem.AbstractThrowableProblem;
-import org.zalando.problem.Status;
-
-@Generated
-public class BadRequestAlertException extends AbstractThrowableProblem {
-  private static final long serialVersionUID = 1L;
-
-  private final String entityName;
-
-  private final String errorKey;
-
-  public BadRequestAlertException(String defaultMessage, String entityName, String errorKey) {
-    this(ErrorConstants.DEFAULT_TYPE, defaultMessage, entityName, errorKey);
-  }
-
-  public BadRequestAlertException(URI type, String defaultMessage, String entityName, String errorKey) {
-    super(type, defaultMessage, Status.BAD_REQUEST, null, null, null, getAlertParameters(entityName, errorKey));
-    this.entityName = entityName;
-    this.errorKey = errorKey;
-  }
-
-  public String getEntityName() {
-    return entityName;
-  }
-
-  public String getErrorKey() {
-    return errorKey;
-  }
-
-  private static Map<String, Object> getAlertParameters(String entityName, String errorKey) {
-    Map<String, Object> parameters = new HashMap<>();
-    parameters.put("message", "error." + errorKey);
-    parameters.put("params", entityName);
-    return parameters;
-  }
-}
diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/EmailAlreadyUsedException.java b/borestop/src/main/java/com/ippon/borestop/web/rest/errors/EmailAlreadyUsedException.java
deleted file mode 100644
index 56c8495b..00000000
--- a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/EmailAlreadyUsedException.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.ippon.borestop.web.rest.errors;
-
-import com.ippon.borestop.common.infrastructure.Generated;
-
-@Generated
-public class EmailAlreadyUsedException extends BadRequestAlertException {
-  private static final long serialVersionUID = 1L;
-
-  public EmailAlreadyUsedException() {
-    super(ErrorConstants.EMAIL_ALREADY_USED_TYPE, "Email is already in use!", "userManagement", "emailexists");
-  }
-}
diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/ErrorConstants.java b/borestop/src/main/java/com/ippon/borestop/web/rest/errors/ErrorConstants.java
deleted file mode 100644
index 581e9c8a..00000000
--- a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/ErrorConstants.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.ippon.borestop.web.rest.errors;
-
-import com.ippon.borestop.common.infrastructure.Generated;
-import java.net.URI;
-
-@Generated
-public final class ErrorConstants {
-  public static final String ERR_CONCURRENCY_FAILURE = "error.concurrencyFailure";
-  public static final String ERR_VALIDATION = "error.validation";
-  public static final String PROBLEM_BASE_URL = "https://www.jhipster.tech/problem";
-  public static final URI DEFAULT_TYPE = URI.create(PROBLEM_BASE_URL + "/problem-with-message");
-  public static final URI CONSTRAINT_VIOLATION_TYPE = URI.create(PROBLEM_BASE_URL + "/constraint-violation");
-  public static final URI INVALID_PASSWORD_TYPE = URI.create(PROBLEM_BASE_URL + "/invalid-password");
-  public static final URI EMAIL_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/email-already-used");
-  public static final URI LOGIN_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/login-already-used");
-
-  private ErrorConstants() {}
-}
diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/ExceptionTranslator.java b/borestop/src/main/java/com/ippon/borestop/web/rest/errors/ExceptionTranslator.java
deleted file mode 100644
index f2b9a129..00000000
--- a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/ExceptionTranslator.java
+++ /dev/null
@@ -1,136 +0,0 @@
-package com.ippon.borestop.web.rest.errors;
-
-import com.ippon.borestop.common.infrastructure.Generated;
-import io.github.jhipster.web.util.HeaderUtil;
-import java.util.List;
-import java.util.stream.Collectors;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import javax.servlet.http.HttpServletRequest;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.dao.ConcurrencyFailureException;
-import org.springframework.http.ResponseEntity;
-import org.springframework.validation.BindingResult;
-import org.springframework.web.bind.MethodArgumentNotValidException;
-import org.springframework.web.bind.annotation.ControllerAdvice;
-import org.springframework.web.bind.annotation.ExceptionHandler;
-import org.springframework.web.context.request.NativeWebRequest;
-import org.zalando.problem.DefaultProblem;
-import org.zalando.problem.Problem;
-import org.zalando.problem.ProblemBuilder;
-import org.zalando.problem.Status;
-import org.zalando.problem.spring.web.advice.ProblemHandling;
-import org.zalando.problem.spring.web.advice.security.SecurityAdviceTrait;
-import org.zalando.problem.violations.ConstraintViolationProblem;
-
-/**
- * Controller advice to translate the server side exceptions to client-friendly json structures.
- * The error response follows RFC7807 - Problem Details for HTTP APIs (https://tools.ietf.org/html/rfc7807).
- */
-@Generated
-@ControllerAdvice
-public class ExceptionTranslator implements ProblemHandling, SecurityAdviceTrait {
-  private static final String FIELD_ERRORS_KEY = "fieldErrors";
-  private static final String MESSAGE_KEY = "message";
-  private static final String PATH_KEY = "path";
-  private static final String VIOLATIONS_KEY = "violations";
-
-  @Value("${jhipster.clientApp.name}")
-  private String applicationName;
-
-  /**
-   * Post-process the Problem payload to add the message key for the front-end if needed.
-   */
-  @Override
-  public ResponseEntity<Problem> process(@Nullable ResponseEntity<Problem> entity, NativeWebRequest request) {
-    if (entity == null) {
-      return entity;
-    }
-    Problem problem = entity.getBody();
-    if (!(problem instanceof ConstraintViolationProblem || problem instanceof DefaultProblem)) {
-      return entity;
-    }
-    ProblemBuilder builder = Problem
-      .builder()
-      .withType(Problem.DEFAULT_TYPE.equals(problem.getType()) ? ErrorConstants.DEFAULT_TYPE : problem.getType())
-      .withStatus(problem.getStatus())
-      .withTitle(problem.getTitle())
-      .with(PATH_KEY, request.getNativeRequest(HttpServletRequest.class).getRequestURI());
-
-    if (problem instanceof ConstraintViolationProblem) {
-      builder.with(VIOLATIONS_KEY, ((ConstraintViolationProblem) problem).getViolations()).with(MESSAGE_KEY, ErrorConstants.ERR_VALIDATION);
-    } else {
-      builder.withCause(((DefaultProblem) problem).getCause()).withDetail(problem.getDetail()).withInstance(problem.getInstance());
-      problem.getParameters().forEach(builder::with);
-      if (!problem.getParameters().containsKey(MESSAGE_KEY) && problem.getStatus() != null) {
-        builder.with(MESSAGE_KEY, "error.http." + problem.getStatus().getStatusCode());
-      }
-    }
-    return new ResponseEntity<>(builder.build(), entity.getHeaders(), entity.getStatusCode());
-  }
-
-  @Override
-  public ResponseEntity<Problem> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, @Nonnull NativeWebRequest request) {
-    BindingResult result = ex.getBindingResult();
-    List<FieldErrorVM> fieldErrors = result
-      .getFieldErrors()
-      .stream()
-      .map(f -> new FieldErrorVM(f.getObjectName().replaceFirst("DTO$", ""), f.getField(), f.getCode()))
-      .collect(Collectors.toList());
-
-    Problem problem = Problem
-      .builder()
-      .withType(ErrorConstants.CONSTRAINT_VIOLATION_TYPE)
-      .withTitle("Method argument not valid")
-      .withStatus(defaultConstraintViolationStatus())
-      .with(MESSAGE_KEY, ErrorConstants.ERR_VALIDATION)
-      .with(FIELD_ERRORS_KEY, fieldErrors)
-      .build();
-    return create(ex, problem, request);
-  }
-
-  @ExceptionHandler
-  public ResponseEntity<Problem> handleEmailAlreadyUsedException(
-    com.ippon.borestop.service.EmailAlreadyUsedException ex,
-    NativeWebRequest request
-  ) {
-    EmailAlreadyUsedException problem = new EmailAlreadyUsedException();
-    return create(
-      problem,
-      request,
-      HeaderUtil.createFailureAlert(applicationName, true, problem.getEntityName(), problem.getErrorKey(), problem.getMessage())
-    );
-  }
-
-  @ExceptionHandler
-  public ResponseEntity<Problem> handleUsernameAlreadyUsedException(
-    com.ippon.borestop.service.UsernameAlreadyUsedException ex,
-    NativeWebRequest request
-  ) {
-    LoginAlreadyUsedException problem = new LoginAlreadyUsedException();
-    return create(
-      problem,
-      request,
-      HeaderUtil.createFailureAlert(applicationName, true, problem.getEntityName(), problem.getErrorKey(), problem.getMessage())
-    );
-  }
-
-  @ExceptionHandler
-  public ResponseEntity<Problem> handleInvalidPasswordException(
-    com.ippon.borestop.service.InvalidPasswordException ex,
-    NativeWebRequest request
-  ) {
-    return create(new InvalidPasswordException(), request);
-  }
-
-  @ExceptionHandler
-  public ResponseEntity<Problem> handleBadRequestAlertException(BadRequestAlertException ex, NativeWebRequest request) {
-    return create(ex, request, HeaderUtil.createFailureAlert(applicationName, true, ex.getEntityName(), ex.getErrorKey(), ex.getMessage()));
-  }
-
-  @ExceptionHandler
-  public ResponseEntity<Problem> handleConcurrencyFailure(ConcurrencyFailureException ex, NativeWebRequest request) {
-    Problem problem = Problem.builder().withStatus(Status.CONFLICT).with(MESSAGE_KEY, ErrorConstants.ERR_CONCURRENCY_FAILURE).build();
-    return create(ex, problem, request);
-  }
-}
diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/FieldErrorVM.java b/borestop/src/main/java/com/ippon/borestop/web/rest/errors/FieldErrorVM.java
deleted file mode 100644
index 5628a99c..00000000
--- a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/FieldErrorVM.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.ippon.borestop.web.rest.errors;
-
-import com.ippon.borestop.common.infrastructure.Generated;
-import java.io.Serializable;
-
-@Generated
-public class FieldErrorVM implements Serializable {
-  private static final long serialVersionUID = 1L;
-
-  private final String objectName;
-
-  private final String field;
-
-  private final String message;
-
-  public FieldErrorVM(String dto, String field, String message) {
-    this.objectName = dto;
-    this.field = field;
-    this.message = message;
-  }
-
-  public String getObjectName() {
-    return objectName;
-  }
-
-  public String getField() {
-    return field;
-  }
-
-  public String getMessage() {
-    return message;
-  }
-}
diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/InvalidPasswordException.java b/borestop/src/main/java/com/ippon/borestop/web/rest/errors/InvalidPasswordException.java
deleted file mode 100644
index 17955eef..00000000
--- a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/InvalidPasswordException.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.ippon.borestop.web.rest.errors;
-
-import com.ippon.borestop.common.infrastructure.Generated;
-import org.zalando.problem.AbstractThrowableProblem;
-import org.zalando.problem.Status;
-
-@Generated
-public class InvalidPasswordException extends AbstractThrowableProblem {
-  private static final long serialVersionUID = 1L;
-
-  public InvalidPasswordException() {
-    super(ErrorConstants.INVALID_PASSWORD_TYPE, "Incorrect password", Status.BAD_REQUEST);
-  }
-}
diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/LoginAlreadyUsedException.java b/borestop/src/main/java/com/ippon/borestop/web/rest/errors/LoginAlreadyUsedException.java
deleted file mode 100644
index 72896e27..00000000
--- a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/LoginAlreadyUsedException.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.ippon.borestop.web.rest.errors;
-
-import com.ippon.borestop.common.infrastructure.Generated;
-
-@Generated
-public class LoginAlreadyUsedException extends BadRequestAlertException {
-  private static final long serialVersionUID = 1L;
-
-  public LoginAlreadyUsedException() {
-    super(ErrorConstants.LOGIN_ALREADY_USED_TYPE, "Login name already used!", "userManagement", "userexists");
-  }
-}
diff --git a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/package-info.java b/borestop/src/main/java/com/ippon/borestop/web/rest/errors/package-info.java
deleted file mode 100644
index a5553029..00000000
--- a/borestop/src/main/java/com/ippon/borestop/web/rest/errors/package-info.java
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- * Specific errors used with Zalando's "problem-spring-web" library.
- *
- * More information on https://github.com/zalando/problem-spring-web
- */
-package com.ippon.borestop.web.rest.errors;
diff --git a/borestop/src/main/resources/i18n/messages.properties b/borestop/src/main/resources/i18n/messages.properties
index ea138657..a1ffe275 100644
--- a/borestop/src/main/resources/i18n/messages.properties
+++ b/borestop/src/main/resources/i18n/messages.properties
@@ -1,21 +1,42 @@
 # Error page
-error.title=Your request cannot be processed
-error.subtitle=Sorry, an error has occurred.
-error.status=Status:
-error.message=Message:
+error.title=Votre demande ne peut être traitée
+error.subtitle=Désolé, une erreur s'est produite.
+error.status=Statut :
+error.message=Message :
 
 # Activation email
-email.activation.title=borestop account activation is required
-email.activation.greeting=Dear {0}
-email.activation.text1=Your borestop account has been created, please click on the URL below to activate it:
-email.activation.text2=Regards,
-email.signature=borestop Team.
+email.activation.title=Activation de votre compte borestop
+email.activation.greeting=Cher {0}
+email.activation.text1=Votre compte borestop a été créé, pour l'activer merci de cliquer sur le lien ci-dessous :
+email.activation.text2=Cordialement,
+email.signature=borestop.
 
 # Creation email
-email.creation.text1=Your borestop account has been created, please click on the URL below to access it:
+email.creation.text1=Votre compte borestop a été créé, merci de cliquer sur le lien ci-dessous pour y accéder :
 
 # Reset email
-email.reset.title=borestop password reset
-email.reset.greeting=Dear {0}
-email.reset.text1=For your borestop account a password reset was requested, please click on the URL below to reset it:
-email.reset.text2=Regards,
+email.reset.title=borestop Réinitialisation de mot de passe
+email.reset.greeting=Cher {0}
+email.reset.text1=Un nouveau mot de passe pour votre compte borestop a été demandé, veuillez cliquer sur le lien ci-dessous pour le réinitialiser :
+email.reset.text2=Cordialement,
+
+################################################################
+######################## Error messages ########################
+################################################################
+borestop.error.status-exception=Une erreur {{ status }} est survenue lors du traitement de votre requête.
+
+borestop.error.user.bad-request=Les données que vous avez saisies sont incorrectes.
+borestop.error.user.mandatory=Le champ est obligatoire.
+borestop.error.user.wrong-format=Le format n'est pas correct, il doit respecter "{{ regexp }}".
+borestop.error.user.too-low=La valeur que vous avez entrée est trop petite, le minimum autorisé est {{ value }}.
+borestop.error.user.too-high=La valeur que vous avez entrée est trop grande, le maximum autorisé est {{ value }}.
+borestop.error.user.access-denied=Vous n'avez pas les droits suffisants pour acceder à cette ressource.
+borestop.error.user.authentication-not-authenticated=Vous devez être authentifié pour acceder à cette ressource.
+borestop.error.user.e-mail-already-used=Cette adresse email est déjà utilisée dans borestop.
+borestop.error.user.login-already-used=Ce login est déjà utilsié dans borestop.
+borestop.error.user.invalid-password=Ce mot de passe n'est pas valide.
+
+borestop.error.server.internal-server-error=Une erreur est survenue, notre équipe travaille à sa résolution !
+borestop.error.server.mandatory-null=Une erreur est survenue, un champ obligatoire ({{ field }}) est null dans notre système. Notre équipe travaille à la résolution de ce problème !
+borestop.error.server.mandatory-empty=Une erreur est survenue, un champ obligatoire ({{ field }}) est vide dans notre système. Notre équipe travaille à la résolution de ce problème !
+borestop.error.server.mandatory-blank=Une erreur est survenue, un champ obligatoire ({{ field }}) est vide dans notre système. Notre équipe travaille à la résolution de ce problème !
diff --git a/borestop/src/main/resources/i18n/messages_en.properties b/borestop/src/main/resources/i18n/messages_en.properties
index 73f69f64..9061824f 100644
--- a/borestop/src/main/resources/i18n/messages_en.properties
+++ b/borestop/src/main/resources/i18n/messages_en.properties
@@ -5,7 +5,7 @@ error.status=Status:
 error.message=Message:
 
 # Activation email
-email.activation.title=borestop account activation
+email.activation.title=borestop account activation is required
 email.activation.greeting=Dear {0}
 email.activation.text1=Your borestop account has been created, please click on the URL below to activate it:
 email.activation.text2=Regards,
@@ -19,3 +19,24 @@ email.reset.title=borestop password reset
 email.reset.greeting=Dear {0}
 email.reset.text1=For your borestop account a password reset was requested, please click on the URL below to reset it:
 email.reset.text2=Regards,
+
+################################################################
+######################## Error messages ########################
+################################################################
+borestop.error.status-exception=An error {{ status }} occured while handling your request.
+
+borestop.error.user.bad-request=The values you entered are incorrects.
+borestop.error.user.mandatory=The field is mandatory.
+borestop.error.user.wrong-format=The format is incorrect, it has to match "{{ regexp }}".
+borestop.error.user.too-low=The value you entered is too low, minimum autorized is {{ value }}.
+borestop.error.user.too-high=The value you entered is too high, maximum authorized is {{ value }}.
+borestop.error.user.access-denied=You don't have sufficient rights to access this resource.
+borestop.error.user.authentication-not-authenticated=You must be authenticated to access this resource.
+borestop.error.user.e-mail-already-used=This email address is already used in the borestop.
+borestop.error.user.login-already-used=This login is already used in borestop.
+borestop.error.user.invalid-password=This password is not valid.
+
+borestop.error.server.internal-server-error=An error occurs, our team is working to fix it!
+borestop.error.server.mandatory-null=An error occurs, a mandatory field ({{ field }}) was null in our system. Our team is working to fix it!
+borestop.error.server.mandatory-empty=An error occurs, a mandatory field ({{ field }}) was empty in our system. Our team is working to fix it!
+borestop.error.server.mandatory-blank=An error occurs, a mandatory field ({{ field }}) was blank in our system. Our team is working to fix it!
diff --git a/borestop/src/test/java/com/ippon/borestop/common/BorestopIntTest.java b/borestop/src/test/java/com/ippon/borestop/common/BorestopIntTest.java
new file mode 100644
index 00000000..48faae98
--- /dev/null
+++ b/borestop/src/test/java/com/ippon/borestop/common/BorestopIntTest.java
@@ -0,0 +1,20 @@
+package com.ippon.borestop.common;
+
+import com.ippon.borestop.BorestopApp;
+import io.github.jhipster.config.JHipsterConstants;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import javax.transaction.Transactional;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.test.context.ActiveProfiles;
+
+@Transactional
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@ActiveProfiles(JHipsterConstants.SPRING_PROFILE_TEST)
+@SpringBootTest(classes = BorestopApp.class, webEnvironment = WebEnvironment.RANDOM_PORT)
+public @interface BorestopIntTest {
+}
diff --git a/borestop/src/test/java/com/ippon/borestop/common/LogSpy.java b/borestop/src/test/java/com/ippon/borestop/common/LogSpy.java
new file mode 100644
index 00000000..8b0036f0
--- /dev/null
+++ b/borestop/src/test/java/com/ippon/borestop/common/LogSpy.java
@@ -0,0 +1,62 @@
+package com.ippon.borestop.common;
+
+import static org.assertj.core.api.Assertions.*;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.read.ListAppender;
+import java.util.function.Predicate;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolutionException;
+import org.junit.jupiter.api.extension.ParameterResolver;
+import org.slf4j.LoggerFactory;
+
+public final class LogSpy implements BeforeEachCallback, AfterEachCallback, ParameterResolver {
+  private Logger logger;
+  private ListAppender<ILoggingEvent> appender;
+
+  @Override
+  public void beforeEach(ExtensionContext context) throws Exception {
+    appender = new ListAppender<>();
+    logger = (Logger) LoggerFactory.getLogger("com.ippon.borestop");
+    logger.addAppender(appender);
+    logger.setLevel(Level.TRACE);
+    appender.start();
+  }
+
+  @Override
+  public void afterEach(ExtensionContext context) throws Exception {
+    logger.detachAppender(appender);
+  }
+
+  public void assertLogged(Level level, String content) {
+    assertThat(appender.list.stream().anyMatch(withLog(level, content))).isTrue();
+  }
+
+  public void assertLogged(Level level, String content, int count) {
+    assertThat(appender.list.stream().filter(withLog(level, content)).count()).isEqualTo(count);
+  }
+
+  public void assertNotLogged(Level level, String content) {
+    assertThat(appender.list.stream().noneMatch(withLog(level, content))).isTrue();
+  }
+
+  private Predicate<ILoggingEvent> withLog(Level level, String content) {
+    return event -> level.equals(event.getLevel()) && event.toString().contains(content);
+  }
+
+  @Override
+  public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
+    throws ParameterResolutionException {
+    return parameterContext.getParameter().getType().equals(LogSpy.class);
+  }
+
+  @Override
+  public LogSpy resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
+    return this;
+  }
+}
diff --git a/borestop/src/test/java/com/ippon/borestop/common/domain/error/AssertUnitTest.java b/borestop/src/test/java/com/ippon/borestop/common/domain/error/AssertUnitTest.java
new file mode 100644
index 00000000..c7e8e97a
--- /dev/null
+++ b/borestop/src/test/java/com/ippon/borestop/common/domain/error/AssertUnitTest.java
@@ -0,0 +1,45 @@
+package com.ippon.borestop.common.domain.error;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.junit.jupiter.api.Test;
+
+class AssertUnitTest {
+
+  @Test
+  void shouldNotValidateNullInputs() {
+    assertThatThrownBy(() -> Assert.notNull("field", null))
+      .isExactlyInstanceOf(MissingMandatoryValueException.class)
+      .hasMessageContaining("\"field\"");
+  }
+
+  @Test
+  void shouldNotValidateNullString() {
+    assertThatThrownBy(() -> Assert.notBlank("field", null))
+      .isExactlyInstanceOf(MissingMandatoryValueException.class)
+      .hasMessageContaining("\"field\"")
+      .hasMessageContaining("(null)");
+  }
+
+  @Test
+  void shouldNotValidateEmptyString() {
+    assertNotBlankString("");
+  }
+
+  @Test
+  void shouldNotValidateSpaceString() {
+    assertNotBlankString(" ");
+  }
+
+  @Test
+  void shouldNotValidateTabString() {
+    assertNotBlankString("  ");
+  }
+
+  private void assertNotBlankString(String input) {
+    assertThatThrownBy(() -> Assert.notBlank("field", input))
+      .isExactlyInstanceOf(MissingMandatoryValueException.class)
+      .hasMessageContaining("\"field\"")
+      .hasMessageContaining("(blank)");
+  }
+}
diff --git a/borestop/src/test/java/com/ippon/borestop/common/domain/error/BorestopExceptionUnitTest.java b/borestop/src/test/java/com/ippon/borestop/common/domain/error/BorestopExceptionUnitTest.java
new file mode 100644
index 00000000..a031eeda
--- /dev/null
+++ b/borestop/src/test/java/com/ippon/borestop/common/domain/error/BorestopExceptionUnitTest.java
@@ -0,0 +1,58 @@
+package com.ippon.borestop.common.domain.error;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.entry;
+
+import com.ippon.borestop.common.domain.error.BorestopException.BorestopExceptionBuilder;
+import org.junit.jupiter.api.Test;
+
+class BorestopExceptionUnitTest {
+
+  @Test
+  public void shouldGetUnmodifiableArguments() {
+    assertThatThrownBy(() -> fullBuilder().build().getArguments().clear()).isExactlyInstanceOf(UnsupportedOperationException.class);
+  }
+
+  @Test
+  public void shouldBuildWithoutBuilder() {
+    BorestopException exception = new BorestopException(null);
+
+    assertThat(exception.getCause()).isNull();
+    assertThat(exception.getMessage()).isNull();
+    assertThat(exception.getStatus()).isNull();
+    assertThat(exception.getArguments()).isNull();
+    assertThat(exception.getBorestopMessage()).isNull();
+  }
+
+  @Test
+  public void shouldGetExceptionInformation() {
+    BorestopException exception = fullBuilder().build();
+
+    assertThat(exception.getArguments()).hasSize(2).contains(entry("key", "value"), entry("other", "test"));
+    assertThat(exception.getStatus()).isEqualTo(ErrorStatus.BAD_REQUEST);
+    assertThat(exception.getMessage()).isEqualTo("Error message");
+    assertThat(exception.getBorestopMessage()).isEqualTo(StandardMessage.USER_MANDATORY);
+    assertThat(exception.getCause()).isExactlyInstanceOf(RuntimeException.class);
+  }
+
+  @Test
+  void shouldMapNullArgumentAsNullString() {
+    assertThat(fullBuilder().argument("nullable", null).build().getArguments().get("nullable")).isEqualTo("null");
+  }
+
+  @Test
+  void shouldGetObjectsToString() {
+    assertThat(fullBuilder().argument("object", 4).build().getArguments().get("object")).isEqualTo("4");
+  }
+
+  private BorestopExceptionBuilder fullBuilder() {
+    return BorestopException
+      .builder(StandardMessage.USER_MANDATORY)
+      .argument("key", "value")
+      .argument("other", "test")
+      .status(ErrorStatus.BAD_REQUEST)
+      .message("Error message")
+      .cause(new RuntimeException());
+  }
+}
diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ArgumentsReplacerUnitTest.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ArgumentsReplacerUnitTest.java
new file mode 100644
index 00000000..a6b4a9ee
--- /dev/null
+++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ArgumentsReplacerUnitTest.java
@@ -0,0 +1,25 @@
+package com.ippon.borestop.common.infrastructure.primary;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+class ArgumentsReplacerUnitTest {
+
+  @Test
+  public void shouldNotReplaceArgumentsInNullMessage() {
+    assertThat(ArgumentsReplacer.replaceArguments(null, Map.of("key", "value"))).isNull();
+  }
+
+  @Test
+  public void shouldNotReplaceArgumentsWithoutArguments() {
+    assertThat(ArgumentsReplacer.replaceArguments("Hey {{ user }}", null)).isEqualTo("Hey {{ user }}");
+  }
+
+  @Test
+  public void shouldReplaceKnownArguments() {
+    assertThat(ArgumentsReplacer.replaceArguments("Hey {{ user }}, how's {{ friend }} doing? Say {{user}}", Map.of("user", "Joe")))
+      .isEqualTo("Hey Joe, how's {{ friend }} doing? Say Joe");
+  }
+}
diff --git a/borestop/src/test/java/com/ippon/borestop/infrastructure/primay/AuthenticationSteps.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/AuthenticationSteps.java
similarity index 96%
rename from borestop/src/test/java/com/ippon/borestop/infrastructure/primay/AuthenticationSteps.java
rename to borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/AuthenticationSteps.java
index cce5c375..6602959f 100644
--- a/borestop/src/test/java/com/ippon/borestop/infrastructure/primay/AuthenticationSteps.java
+++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/AuthenticationSteps.java
@@ -1,4 +1,4 @@
-package com.ippon.borestop.infrastructure.primay;
+package com.ippon.borestop.common.infrastructure.primary;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandlerIntTest.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandlerIntTest.java
new file mode 100644
index 00000000..4a3578e3
--- /dev/null
+++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandlerIntTest.java
@@ -0,0 +1,210 @@
+package com.ippon.borestop.common.infrastructure.primary;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import com.ippon.borestop.common.BorestopIntTest;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.Charset;
+import java.util.Locale;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+@BorestopIntTest
+class BorestopErrorHandlerIntTest {
+  private static final Charset UTF_8 = Charset.forName("UTF-8");
+
+  private static final String FRANCE_TAG = Locale.FRANCE.toLanguageTag();
+
+  @Autowired
+  private BorestopErrorHandler exceptionTranslator;
+
+  @Autowired
+  private ErrorResource resource;
+
+  private MockMvc mockMvc;
+
+  @BeforeEach
+  public void setup() {
+    mockMvc = MockMvcBuilders.standaloneSetup(resource).setControllerAdvice(exceptionTranslator).build();
+  }
+
+  @Test
+  public void shouldMapBorestopExceptions() throws Exception {
+    String response = mockMvc
+      .perform(post(errorEndpoint("borestop-exceptions")).header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG))
+      .andExpect(status().isInternalServerError())
+      .andReturn()
+      .getResponse()
+      .getContentAsString(UTF_8);
+
+    assertThat(response).contains("Une erreur est survenue, notre équipe travaille à sa résolution !");
+  }
+
+  @Test
+  void shouldMapResponseStatusWithMessageExceptions() throws UnsupportedEncodingException, Exception {
+    String response = mockMvc
+      .perform(post(errorEndpoint("responsestatus-with-message-exceptions")).header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG))
+      .andExpect(status().isNotFound())
+      .andReturn()
+      .getResponse()
+      .getContentAsString(UTF_8);
+
+    assertThat(response).contains("oops");
+  }
+
+  @Test
+  void shouldMapResponseStatusWithoutMessageExceptions() throws UnsupportedEncodingException, Exception {
+    String response = mockMvc
+      .perform(post(errorEndpoint("responsestatus-without-message-exceptions")).header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG))
+      .andExpect(status().isNotFound())
+      .andReturn()
+      .getResponse()
+      .getContentAsString(UTF_8);
+
+    assertThat(response).contains("est survenue").contains("404");
+  }
+
+  @Test
+  public void shouldGetMessagesInOtherLanguage() throws Exception {
+    String response = mockMvc
+      .perform(post(errorEndpoint("borestop-exceptions")).header(HttpHeaders.ACCEPT_LANGUAGE, Locale.UK.toLanguageTag()))
+      .andExpect(status().isInternalServerError())
+      .andReturn()
+      .getResponse()
+      .getContentAsString(UTF_8);
+
+    assertThat(response).contains("An error occurs, our team is working to fix it!");
+  }
+
+  @Test
+  public void shouldGetMessagesInDefaultLanguage() throws Exception {
+    String response = mockMvc
+      .perform(post(errorEndpoint("borestop-exceptions")).header(HttpHeaders.ACCEPT_LANGUAGE, Locale.CHINESE.toLanguageTag()))
+      .andExpect(status().isInternalServerError())
+      .andReturn()
+      .getResponse()
+      .getContentAsString(UTF_8);
+
+    assertThat(response).contains("Une erreur est survenue, notre équipe travaille à sa résolution !");
+  }
+
+  @Test
+  public void shouldMapParametersBeanValidationErrors() throws Exception {
+    String response = mockMvc
+      .perform(get(errorEndpoint("oops")).header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG))
+      .andExpect(status().isBadRequest())
+      .andReturn()
+      .getResponse()
+      .getContentAsString(UTF_8);
+
+    assertThat(response)
+      .contains("Les données que vous avez saisies sont incorrectes.")
+      .contains("Le format n'est pas correct, il doit respecter \\\"complicated\\\".");
+  }
+
+  @Test
+  public void shouldMapMinValueQueryStringBeanValidationErrors() throws Exception {
+    String response = mockMvc
+      .perform(get(errorEndpoint("?parameter=1")).header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG))
+      .andExpect(status().isBadRequest())
+      .andReturn()
+      .getResponse()
+      .getContentAsString(UTF_8);
+
+    assertThat(response)
+      .contains("Les données que vous avez saisies sont incorrectes.")
+      .contains("La valeur que vous avez entrée est trop petite, le minimum autorisé est 42.");
+  }
+
+  @Test
+  public void shouldMapMaxValueQueryStringBeanValidationErrors() throws Exception {
+    String response = mockMvc
+      .perform(get(errorEndpoint("?parameter=100")).header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG))
+      .andExpect(status().isBadRequest())
+      .andReturn()
+      .getResponse()
+      .getContentAsString(UTF_8);
+
+    assertThat(response)
+      .contains("Les données que vous avez saisies sont incorrectes.")
+      .contains("La valeur que vous avez entrée est trop grande, le maximum autorisé est 42.");
+  }
+
+  @Test
+  public void shouldMapBodyBeanValidationErrors() throws Exception {
+    String response = mockMvc
+      .perform(
+        post(errorEndpoint("oops"))
+          .header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG)
+          .contentType(MediaType.APPLICATION_JSON)
+          .content(TestJson.writeAsString(new ComplicatedRequest("value")))
+      )
+      .andExpect(status().isBadRequest())
+      .andReturn()
+      .getResponse()
+      .getContentAsString(UTF_8);
+
+    assertThat(response)
+      .contains("Les données que vous avez saisies sont incorrectes.")
+      .contains("Le format n'est pas correct, il doit respecter \\\"complicated\\\".");
+  }
+
+  @Test
+  public void shouldMapTechnicalError() throws Exception {
+    String response = mockMvc
+      .perform(
+        post(errorEndpoint("not-deserializables"))
+          .header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG)
+          .contentType(MediaType.APPLICATION_JSON)
+          .content(TestJson.writeAsString(new NotDeserializable("value")))
+      )
+      .andExpect(status().isInternalServerError())
+      .andReturn()
+      .getResponse()
+      .getContentAsString(UTF_8);
+
+    assertThat(response).contains("Une erreur est survenue, notre équipe travaille à sa résolution !");
+  }
+
+  @Test
+  public void shouldHandleAccessDeniedException() throws Exception {
+    String response = mockMvc
+      .perform(get(errorEndpoint("access-denied")).header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG))
+      .andExpect(status().isForbidden())
+      .andReturn()
+      .getResponse()
+      .getContentAsString(UTF_8);
+
+    assertThat(response).contains("Vous n'avez pas les droits suffisants");
+  }
+
+  @Test
+  public void shouldHandleRuntimeException() throws Exception {
+    String response = mockMvc
+      .perform(get(errorEndpoint("runtime-exceptions")).header(HttpHeaders.ACCEPT_LANGUAGE, FRANCE_TAG))
+      .andExpect(status().isInternalServerError())
+      .andReturn()
+      .getResponse()
+      .getContentAsString(UTF_8);
+
+    assertThat(response).contains("Une erreur est survenue, notre équipe travaille à sa résolution !");
+  }
+
+  private URI errorEndpoint(String path) {
+    try {
+      return new URI("/errors/" + path);
+    } catch (URISyntaxException e) {
+      throw new AssertionError();
+    }
+  }
+}
diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandlerUnitTest.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandlerUnitTest.java
new file mode 100644
index 00000000..f116c7b8
--- /dev/null
+++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorHandlerUnitTest.java
@@ -0,0 +1,218 @@
+package com.ippon.borestop.common.infrastructure.primary;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+import ch.qos.logback.classic.Level;
+import com.ippon.borestop.common.LogSpy;
+import com.ippon.borestop.common.domain.error.BorestopException;
+import com.ippon.borestop.common.domain.error.ErrorStatus;
+import com.ippon.borestop.common.domain.error.StandardMessage;
+import java.nio.charset.Charset;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+import org.junit.BeforeClass;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.context.MessageSource;
+import org.springframework.context.NoSuchMessageException;
+import org.springframework.context.i18n.LocaleContextHolder;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.InsufficientAuthenticationException;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.web.client.RestClientResponseException;
+import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
+import org.springframework.web.multipart.MaxUploadSizeExceededException;
+
+@ExtendWith({ SpringExtension.class, LogSpy.class })
+class BorestopErrorHandlerUnitTest {
+  @Mock
+  private MessageSource messages;
+
+  @InjectMocks
+  private BorestopErrorHandler handler;
+
+  private final LogSpy logs;
+
+  public BorestopErrorHandlerUnitTest(LogSpy logs) {
+    this.logs = logs;
+  }
+
+  @BeforeClass
+  public void loadUserLocale() {
+    LocaleContextHolder.setLocale(Locale.FRANCE);
+  }
+
+  @Test
+  public void shouldHandleAsServerErrorWithoutStatus() {
+    ResponseEntity<BorestopError> response = handler.handleBorestopException(
+      BorestopException.builder(StandardMessage.USER_MANDATORY).build()
+    );
+
+    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
+  }
+
+  @Test
+  void shouldHandleAllErrorsAsUniqueHttpCode() {
+    Set<HttpStatus> statuses = new HashSet<>();
+
+    for (ErrorStatus status : ErrorStatus.values()) {
+      ResponseEntity<BorestopError> response = handler.handleBorestopException(
+        BorestopException.builder(StandardMessage.USER_MANDATORY).status(status).build()
+      );
+
+      statuses.add(response.getStatusCode());
+    }
+
+    assertThat(statuses.size()).isEqualTo(ErrorStatus.values().length);
+  }
+
+  @Test
+  public void shouldGetDefaultBorestopMessageWithoutMessageForBadRequest() {
+    ResponseEntity<BorestopError> response = handler.handleBorestopException(
+      BorestopException.builder(null).status(ErrorStatus.BAD_REQUEST).build()
+    );
+
+    assertThat(response.getBody().getErrorType()).isEqualTo("user.bad-request");
+  }
+
+  @Test
+  public void shouldGetDefaultBorestopMessageWithoutMessageForServerError() {
+    ResponseEntity<BorestopError> response = handler.handleBorestopException(BorestopException.builder(null).build());
+
+    assertThat(response.getBody().getErrorType()).isEqualTo("server.internal-server-error");
+  }
+
+  @Test
+  public void shouldGetDefaultMessageForUnknownMessage() {
+    when(messages.getMessage("borestop.error.hey", null, Locale.FRANCE)).thenThrow(new NoSuchMessageException("hey"));
+    when(messages.getMessage("borestop.error.server.internal-server-error", null, Locale.FRANCE)).thenReturn("User message");
+    ResponseEntity<BorestopError> response = handler.handleBorestopException(BorestopException.builder(() -> "hey").build());
+
+    BorestopError body = response.getBody();
+    assertThat(body.getErrorType()).isEqualTo("hey");
+    assertThat(body.getMessage()).isEqualTo("User message");
+  }
+
+  @Test
+  public void shouldHandleUserBorestopExceptionWithMessageWithoutArguments() {
+    RuntimeException cause = new RuntimeException();
+    BorestopException exception = BorestopException
+      .builder(StandardMessage.USER_MANDATORY)
+      .cause(cause)
+      .status(ErrorStatus.BAD_REQUEST)
+      .message("Hum")
+      .build();
+
+    when(messages.getMessage("borestop.error.user.mandatory", null, Locale.FRANCE)).thenReturn("User message");
+    ResponseEntity<BorestopError> response = handler.handleBorestopException(exception);
+
+    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
+
+    BorestopError body = response.getBody();
+    assertThat(body.getErrorType()).isEqualTo("user.mandatory");
+    assertThat(body.getMessage()).isEqualTo("User message");
+    assertThat(body.getFieldsErrors()).isNull();
+  }
+
+  @Test
+  public void shouldReplaceArgumentsValueFromBorestopException() {
+    when(messages.getMessage("borestop.error.user.mandatory", null, Locale.FRANCE))
+      .thenReturn("User {{ firstName }} {{ lastName}} message");
+
+    ResponseEntity<BorestopError> response = handler.handleBorestopException(
+      BorestopException.builder(StandardMessage.USER_MANDATORY).argument("firstName", "Joe").argument("lastName", "Dalton").build()
+    );
+
+    assertThat(response.getBody().getMessage()).isEqualTo("User Joe Dalton message");
+  }
+
+  @Test
+  public void shouldHandleAsBadRequestForExceededSizeFileUpload() {
+    when(messages.getMessage("borestop.error.server.upload-too-big", null, Locale.FRANCE))
+      .thenReturn("The file is too big to be send to the server");
+
+    ResponseEntity<BorestopError> response = handler.handleFileSizeException(new MaxUploadSizeExceededException(1024 * 1024 * 30));
+
+    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
+    assertThat(response.getBody().getErrorType()).isEqualTo("server.upload-too-big");
+    assertThat(response.getBody().getMessage()).isEqualTo("The file is too big to be send to the server");
+  }
+
+  @Test
+  public void shouldHandleMethodArgumentTypeMismatchException() {
+    MethodArgumentTypeMismatchException mismatchException = new MethodArgumentTypeMismatchException(null, null, null, null, null);
+
+    ResponseEntity<BorestopError> response = handler.handleMethodArgumentTypeMismatchException(mismatchException);
+
+    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
+    assertThat(response.getBody().getErrorType()).isEqualTo("user.bad-request");
+  }
+
+  @Test
+  public void shouldHandleMethodArgumentTypeMismatchExceptionWithBorestopError() {
+    RuntimeException cause = BorestopException.builder(StandardMessage.USER_MANDATORY).build();
+    MethodArgumentTypeMismatchException mismatchException = new MethodArgumentTypeMismatchException(null, null, null, null, cause);
+
+    ResponseEntity<BorestopError> response = handler.handleMethodArgumentTypeMismatchException(mismatchException);
+
+    assertThat(response.getBody().getErrorType()).isEqualTo("user.mandatory");
+  }
+
+  @Test
+  public void shouldLogUserErrorAsWarn() {
+    handler.handleBorestopException(
+      BorestopException.builder(StandardMessage.BAD_REQUEST).status(ErrorStatus.BAD_REQUEST).message("error message").build()
+    );
+
+    logs.assertLogged(Level.WARN, "error message");
+  }
+
+  @Test
+  public void shouldLogAuthenticationExceptionAsDebug() {
+    handler.handleAuthenticationException(new InsufficientAuthenticationException("oops"));
+
+    logs.assertLogged(Level.DEBUG, "oops");
+  }
+
+  @Test
+  public void shouldLogServerErrorAsError() {
+    handler.handleBorestopException(
+      BorestopException
+        .builder(StandardMessage.INTERNAL_SERVER_ERROR)
+        .status(ErrorStatus.INTERNAL_SERVER_ERROR)
+        .message("error message")
+        .build()
+    );
+
+    logs.assertLogged(Level.ERROR, "error message");
+  }
+
+  @Test
+  public void shouldLogErrorResponseBody() {
+    RestClientResponseException cause = new RestClientResponseException(
+      "error",
+      400,
+      "status",
+      null,
+      "service error response".getBytes(),
+      Charset.defaultCharset()
+    );
+
+    handler.handleBorestopException(
+      BorestopException
+        .builder(StandardMessage.INTERNAL_SERVER_ERROR)
+        .status(ErrorStatus.INTERNAL_SERVER_ERROR)
+        .message("error message")
+        .cause(cause)
+        .build()
+    );
+
+    logs.assertLogged(Level.ERROR, "error message");
+    logs.assertLogged(Level.ERROR, "service error response");
+  }
+}
diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorUnitTest.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorUnitTest.java
new file mode 100644
index 00000000..cb97f5e0
--- /dev/null
+++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopErrorUnitTest.java
@@ -0,0 +1,39 @@
+package com.ippon.borestop.common.infrastructure.primary;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+class BorestopErrorUnitTest {
+
+  @Test
+  public void shouldGetErrorInformation() {
+    BorestopFieldError fieldError = BorestopFieldErrorUnitTest.defaultFieldError();
+    BorestopError error = defaultError(fieldError);
+
+    assertThat(error.getErrorType()).isEqualTo("type");
+    assertThat(error.getMessage()).isEqualTo("message");
+    assertThat(error.getFieldsErrors()).containsExactly(fieldError);
+  }
+
+  @Test
+  public void shouldSerializeToJson() {
+    assertThat(TestJson.writeAsString(defaultError(BorestopFieldErrorUnitTest.defaultFieldError()))).isEqualTo(defaultJson());
+  }
+
+  @Test
+  void shouldDeserializeFromJson() {
+    assertThat(TestJson.readFromJson(defaultJson(), BorestopError.class))
+      .usingRecursiveComparison()
+      .isEqualTo(defaultError(BorestopFieldErrorUnitTest.defaultFieldError()));
+  }
+
+  private String defaultJson() {
+    return "{\"errorType\":\"type\",\"message\":\"message\",\"fieldsErrors\":[" + BorestopFieldErrorUnitTest.defaultJson() + "]}";
+  }
+
+  private BorestopError defaultError(BorestopFieldError fieldError) {
+    return new BorestopError("type", "message", List.of(fieldError));
+  }
+}
diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopFieldErrorUnitTest.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopFieldErrorUnitTest.java
new file mode 100644
index 00000000..887f0535
--- /dev/null
+++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/BorestopFieldErrorUnitTest.java
@@ -0,0 +1,30 @@
+package com.ippon.borestop.common.infrastructure.primary;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+class BorestopFieldErrorUnitTest {
+
+  @Test
+  public void shouldGetFieldErrorInformation() {
+    BorestopFieldError fieldError = defaultFieldError();
+
+    assertThat(fieldError.getFieldPath()).isEqualTo("path");
+    assertThat(fieldError.getReason()).isEqualTo("reason");
+    assertThat(fieldError.getMessage()).isEqualTo("message");
+  }
+
+  @Test
+  public void shouldSerializeToJson() {
+    assertThat(TestJson.writeAsString(defaultFieldError())).isEqualTo(defaultJson());
+  }
+
+  static BorestopFieldError defaultFieldError() {
+    return BorestopFieldError.builder().fieldPath("path").reason("reason").message("message").build();
+  }
+
+  static String defaultJson() {
+    return "{\"fieldPath\":\"path\",\"reason\":\"reason\",\"message\":\"message\"}";
+  }
+}
diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ComplicatedRequest.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ComplicatedRequest.java
new file mode 100644
index 00000000..407584cd
--- /dev/null
+++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ComplicatedRequest.java
@@ -0,0 +1,17 @@
+package com.ippon.borestop.common.infrastructure.primary;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.validation.constraints.Pattern;
+
+public class ComplicatedRequest {
+  private String value;
+
+  public ComplicatedRequest(@JsonProperty("value") String value) {
+    this.value = value;
+  }
+
+  @Pattern(message = ValidationMessage.WRONG_FORMAT, regexp = "complicated")
+  public String getValue() {
+    return value;
+  }
+}
diff --git a/borestop/src/test/java/com/ippon/borestop/infrastructure/primay/CucumberTestContext.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/CucumberTestContext.java
similarity index 98%
rename from borestop/src/test/java/com/ippon/borestop/infrastructure/primay/CucumberTestContext.java
rename to borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/CucumberTestContext.java
index 0b015bc7..0da329ad 100644
--- a/borestop/src/test/java/com/ippon/borestop/infrastructure/primay/CucumberTestContext.java
+++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/CucumberTestContext.java
@@ -1,4 +1,4 @@
-package com.ippon.borestop.infrastructure.primay;
+package com.ippon.borestop.common.infrastructure.primary;
 
 import com.jayway.jsonpath.Configuration;
 import com.jayway.jsonpath.JsonPath;
diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ErrorMessagesUnitTest.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ErrorMessagesUnitTest.java
new file mode 100644
index 00000000..b87a690b
--- /dev/null
+++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ErrorMessagesUnitTest.java
@@ -0,0 +1,83 @@
+package com.ippon.borestop.common.infrastructure.primary;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.ippon.borestop.common.domain.error.BorestopMessage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.junit.jupiter.api.Test;
+import org.reflections.Reflections;
+import org.reflections.scanners.SubTypesScanner;
+import org.reflections.util.ClasspathHelper;
+import org.reflections.util.ConfigurationBuilder;
+import org.reflections.util.FilterBuilder;
+
+public class ErrorMessagesUnitTest {
+  private static final Set<Class<? extends BorestopMessage>> errors = new Reflections(
+    new ConfigurationBuilder()
+      .setUrls(ClasspathHelper.forPackage("com.ippon"))
+      .setScanners(new SubTypesScanner())
+      .filterInputsBy(new FilterBuilder().includePackage("com.ippon"))
+  )
+  .getSubTypesOf(BorestopMessage.class);
+
+  @Test
+  public void shouldHaveOnlyEnumImplementations() {
+    errors.forEach(
+      error ->
+        assertThat(error.isEnum() || error.isInterface())
+          .as("Implementations of " + BorestopMessage.class.getName() + " must be enums and " + error.getName() + " wasn't")
+          .isTrue()
+    );
+  }
+
+  @Test
+  public void shouldHaveMessagesForAllKeys() {
+    Collection<Properties> messages = loadMessages();
+
+    errors
+      .stream()
+      .filter(error -> error.isEnum())
+      .forEach(error -> Arrays.stream(error.getEnumConstants()).forEach(value -> messages.forEach(assertMessageExist(value))));
+  }
+
+  private List<Properties> loadMessages() {
+    try {
+      return Files.list(Paths.get("src/main/resources/i18n")).map(toProperties()).collect(Collectors.toList());
+    } catch (IOException e) {
+      throw new AssertionError();
+    }
+  }
+
+  private Function<Path, Properties> toProperties() {
+    return file -> {
+      Properties properties = new Properties();
+      try (InputStream input = Files.newInputStream(file)) {
+        properties.load(input);
+      } catch (IOException e) {
+        throw new AssertionError();
+      }
+
+      return properties;
+    };
+  }
+
+  private Consumer<Properties> assertMessageExist(BorestopMessage value) {
+    return currentMessages -> {
+      assertThat(currentMessages.getProperty("borestop.error." + value.getMessageKey()))
+        .as("Can't find message for " + value.getMessageKey() + " in all files, check your configuration")
+        .isNotBlank();
+    };
+  }
+}
diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ErrorResource.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ErrorResource.java
new file mode 100644
index 00000000..c88e3860
--- /dev/null
+++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/ErrorResource.java
@@ -0,0 +1,77 @@
+package com.ippon.borestop.common.infrastructure.primary;
+
+import com.ippon.borestop.common.domain.error.BorestopException;
+import com.ippon.borestop.common.domain.error.ErrorStatus;
+import com.ippon.borestop.common.domain.error.StandardMessage;
+import javax.validation.constraints.Pattern;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.server.ResponseStatusException;
+
+/**
+ * Resource to expose errors endpoints
+ */
+@Validated
+@RestController
+@RequestMapping("/errors")
+class ErrorResource {
+  private static final Logger logger = LoggerFactory.getLogger(ErrorResource.class);
+
+  @GetMapping("/runtime-exceptions")
+  public void runtimeException() {
+    throw new RuntimeException();
+  }
+
+  @GetMapping("/access-denied")
+  public void accessDeniedException() {
+    throw new AccessDeniedException("You shall not pass!");
+  }
+
+  @PostMapping("/borestop-exceptions")
+  public void borestopException() {
+    throw BorestopException
+      .builder(StandardMessage.INTERNAL_SERVER_ERROR)
+      .cause(new RuntimeException())
+      .status(ErrorStatus.INTERNAL_SERVER_ERROR)
+      .message("Oops")
+      .argument("key", "value")
+      .build();
+  }
+
+  @PostMapping("/responsestatus-with-message-exceptions")
+  public void responseStatusWithMessageException() {
+    throw new ResponseStatusException(HttpStatus.NOT_FOUND, "oops");
+  }
+
+  @PostMapping("/responsestatus-without-message-exceptions")
+  public void responseStatusWithoutMessageException() {
+    throw new ResponseStatusException(HttpStatus.NOT_FOUND);
+  }
+
+  @GetMapping
+  public void queryStringWithRangedValue(@Validated QueryParameter parameter) {}
+
+  @GetMapping("/{complicated}")
+  public void complicatedArg(
+    @Validated @Pattern(message = ValidationMessage.WRONG_FORMAT, regexp = "complicated") @PathVariable("complicated") String complicated
+  ) {
+    logger.info("Congratulations you got it right!");
+  }
+
+  @PostMapping("/oops")
+  public void complicatedBody(@Validated @RequestBody ComplicatedRequest request) {
+    logger.info("You got it right!");
+  }
+
+  @PostMapping("/not-deserializables")
+  public void notDeserializable(@RequestBody NotDeserializable request) {}
+}
diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/NotDeserializable.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/NotDeserializable.java
new file mode 100644
index 00000000..a37e4d32
--- /dev/null
+++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/NotDeserializable.java
@@ -0,0 +1,13 @@
+package com.ippon.borestop.common.infrastructure.primary;
+
+public class NotDeserializable {
+  private String value;
+
+  public NotDeserializable(String value) {
+    this.value = value;
+  }
+
+  public String getValue() {
+    return value;
+  }
+}
diff --git a/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/QueryParameter.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/QueryParameter.java
new file mode 100644
index 00000000..b9411022
--- /dev/null
+++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/QueryParameter.java
@@ -0,0 +1,18 @@
+package com.ippon.borestop.common.infrastructure.primary;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+
+class QueryParameter {
+  private int parameter;
+
+  @Min(message = ValidationMessage.VALUE_TOO_LOW, value = 42)
+  @Max(message = ValidationMessage.VALUE_TOO_HIGH, value = 42)
+  public int getParameter() {
+    return parameter;
+  }
+
+  public void setParameter(int parameter) {
+    this.parameter = parameter;
+  }
+}
diff --git a/borestop/src/test/java/com/ippon/borestop/infrastructure/primay/TestJson.java b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/TestJson.java
similarity index 96%
rename from borestop/src/test/java/com/ippon/borestop/infrastructure/primay/TestJson.java
rename to borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/TestJson.java
index 99e86c5a..578d4527 100644
--- a/borestop/src/test/java/com/ippon/borestop/infrastructure/primay/TestJson.java
+++ b/borestop/src/test/java/com/ippon/borestop/common/infrastructure/primary/TestJson.java
@@ -1,4 +1,4 @@
-package com.ippon.borestop.infrastructure.primay;
+package com.ippon.borestop.common.infrastructure.primary;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.core.JsonProcessingException;
diff --git a/borestop/src/test/java/com/ippon/borestop/cucumber/CucumberConfiguration.java b/borestop/src/test/java/com/ippon/borestop/cucumber/CucumberConfiguration.java
index 093a7045..3a758d7c 100644
--- a/borestop/src/test/java/com/ippon/borestop/cucumber/CucumberConfiguration.java
+++ b/borestop/src/test/java/com/ippon/borestop/cucumber/CucumberConfiguration.java
@@ -1,8 +1,8 @@
 package com.ippon.borestop.cucumber;
 
 import com.ippon.borestop.BorestopApp;
+import com.ippon.borestop.common.infrastructure.primary.CucumberTestContext;
 import com.ippon.borestop.cucumber.CucumberConfiguration.CucumberSecurityContextConfiguration;
-import com.ippon.borestop.infrastructure.primay.CucumberTestContext;
 import io.cucumber.java.Before;
 import io.cucumber.spring.CucumberContextConfiguration;
 import io.github.jhipster.config.JHipsterConstants;
diff --git a/borestop/src/test/java/com/ippon/borestop/cucumber/HttpSteps.java b/borestop/src/test/java/com/ippon/borestop/cucumber/HttpSteps.java
index 20dd152e..bd2c39b4 100644
--- a/borestop/src/test/java/com/ippon/borestop/cucumber/HttpSteps.java
+++ b/borestop/src/test/java/com/ippon/borestop/cucumber/HttpSteps.java
@@ -2,7 +2,7 @@ package com.ippon.borestop.cucumber;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
-import com.ippon.borestop.infrastructure.primay.CucumberTestContext;
+import com.ippon.borestop.common.infrastructure.primary.CucumberTestContext;
 import io.cucumber.java.en.Then;
 import org.springframework.http.HttpStatus;
 
diff --git a/borestop/src/test/java/com/ippon/borestop/service/MailServiceIT.java b/borestop/src/test/java/com/ippon/borestop/service/MailServiceIT.java
index 8e9a8619..f48d5129 100644
--- a/borestop/src/test/java/com/ippon/borestop/service/MailServiceIT.java
+++ b/borestop/src/test/java/com/ippon/borestop/service/MailServiceIT.java
@@ -1,23 +1,17 @@
 package com.ippon.borestop.service;
 
-import static org.assertj.core.api.Assertions.*;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
 
 import com.ippon.borestop.BorestopApp;
 import com.ippon.borestop.config.Constants;
 import com.ippon.borestop.domain.User;
 import io.github.jhipster.config.JHipsterProperties;
 import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStreamReader;
-import java.net.URI;
-import java.net.URL;
-import java.nio.charset.Charset;
-import java.util.Properties;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import javax.mail.Multipart;
 import javax.mail.internet.MimeBodyPart;
 import javax.mail.internet.MimeMessage;
@@ -40,13 +34,6 @@ import org.thymeleaf.spring5.SpringTemplateEngine;
  */
 @SpringBootTest(classes = BorestopApp.class)
 public class MailServiceIT {
-  private static final String[] languages = {
-    "en"
-  // jhipster-needle-i18n-language-constant - JHipster will add/remove languages in this array
-  };
-  private static final Pattern PATTERN_LOCALE_3 = Pattern.compile("([a-z]{2})-([a-zA-Z]{4})-([a-z]{2})");
-  private static final Pattern PATTERN_LOCALE_2 = Pattern.compile("([a-z]{2})-([a-z]{2})");
-
   @Autowired
   private JHipsterProperties jHipsterProperties;
 
@@ -131,22 +118,6 @@ public class MailServiceIT {
     assertThat(part.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8");
   }
 
-  @Test
-  public void testSendEmailFromTemplate() throws Exception {
-    User user = new User();
-    user.setLogin("john");
-    user.setEmail("john.doe@example.com");
-    user.setLangKey("en");
-    mailService.sendEmailFromTemplate(user, "mail/testEmail", "email.test.title");
-    verify(javaMailSender).send(messageCaptor.capture());
-    MimeMessage message = messageCaptor.getValue();
-    assertThat(message.getSubject()).isEqualTo("test title");
-    assertThat(message.getAllRecipients()[0].toString()).isEqualTo(user.getEmail());
-    assertThat(message.getFrom()[0].toString()).isEqualTo(jHipsterProperties.getMail().getFrom());
-    assertThat(message.getContent().toString()).isEqualToNormalizingNewlines("<html>test title, http://127.0.0.1:8080, john</html>\n");
-    assertThat(message.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8");
-  }
-
   @Test
   public void testSendActivationEmail() throws Exception {
     User user = new User();
@@ -201,44 +172,4 @@ public class MailServiceIT {
       fail("Exception shouldn't have been thrown");
     }
   }
-
-  @Test
-  public void testSendLocalizedEmailForAllSupportedLanguages() throws Exception {
-    User user = new User();
-    user.setLogin("john");
-    user.setEmail("john.doe@example.com");
-    for (String langKey : languages) {
-      user.setLangKey(langKey);
-      mailService.sendEmailFromTemplate(user, "mail/testEmail", "email.test.title");
-      verify(javaMailSender, atLeastOnce()).send(messageCaptor.capture());
-      MimeMessage message = messageCaptor.getValue();
-
-      String propertyFilePath = "i18n/messages_" + getJavaLocale(langKey) + ".properties";
-      URL resource = this.getClass().getClassLoader().getResource(propertyFilePath);
-      File file = new File(new URI(resource.getFile()).getPath());
-      Properties properties = new Properties();
-      properties.load(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")));
-
-      String emailTitle = (String) properties.get("email.test.title");
-      assertThat(message.getSubject()).isEqualTo(emailTitle);
-      assertThat(message.getContent().toString())
-        .isEqualToNormalizingNewlines("<html>" + emailTitle + ", http://127.0.0.1:8080, john</html>\n");
-    }
-  }
-
-  /**
-   * Convert a lang key to the Java locale.
-   */
-  private String getJavaLocale(String langKey) {
-    String javaLangKey = langKey;
-    Matcher matcher2 = PATTERN_LOCALE_2.matcher(langKey);
-    if (matcher2.matches()) {
-      javaLangKey = matcher2.group(1) + "_" + matcher2.group(2).toUpperCase();
-    }
-    Matcher matcher3 = PATTERN_LOCALE_3.matcher(langKey);
-    if (matcher3.matches()) {
-      javaLangKey = matcher3.group(1) + "_" + matcher3.group(2) + "_" + matcher3.group(3).toUpperCase();
-    }
-    return javaLangKey;
-  }
 }
diff --git a/borestop/src/test/java/com/ippon/borestop/web/rest/AccountResourceIT.java b/borestop/src/test/java/com/ippon/borestop/web/rest/AccountResourceIT.java
index 52726498..b90d5d2e 100644
--- a/borestop/src/test/java/com/ippon/borestop/web/rest/AccountResourceIT.java
+++ b/borestop/src/test/java/com/ippon/borestop/web/rest/AccountResourceIT.java
@@ -1,11 +1,14 @@
 package com.ippon.borestop.web.rest;
 
-import static com.ippon.borestop.web.rest.AccountResourceIT.TEST_USER_LOGIN;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
 import com.ippon.borestop.BorestopApp;
+import com.ippon.borestop.common.infrastructure.primary.BorestopErrorHandler;
 import com.ippon.borestop.config.Constants;
 import com.ippon.borestop.domain.User;
 import com.ippon.borestop.repository.AuthorityRepository;
@@ -17,8 +20,12 @@ import com.ippon.borestop.service.dto.UserDTO;
 import com.ippon.borestop.web.rest.vm.KeyAndPasswordVM;
 import com.ippon.borestop.web.rest.vm.ManagedUserVM;
 import java.time.Instant;
-import java.util.*;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
 import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
@@ -27,13 +34,14 @@ import org.springframework.http.MediaType;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.test.context.support.WithMockUser;
 import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 import org.springframework.transaction.annotation.Transactional;
 
 /**
  * Integration tests for the {@link AccountResource} REST controller.
  */
 @AutoConfigureMockMvc
-@WithMockUser(value = TEST_USER_LOGIN)
+@WithMockUser(value = AccountResourceIT.TEST_USER_LOGIN)
 @SpringBootTest(classes = BorestopApp.class)
 public class AccountResourceIT {
   static final String TEST_USER_LOGIN = "test";
@@ -44,6 +52,9 @@ public class AccountResourceIT {
   @Autowired
   private AuthorityRepository authorityRepository;
 
+  @Autowired
+  private BorestopErrorHandler exceptionTranslator;
+
   @Autowired
   private UserService userService;
 
@@ -51,20 +62,24 @@ public class AccountResourceIT {
   private PasswordEncoder passwordEncoder;
 
   @Autowired
-  private MockMvc restAccountMockMvc;
+  private AccountResource accountResource;
+
+  private MockMvc mockMvc;
+
+  @BeforeEach
+  public void setup() {
+    mockMvc = MockMvcBuilders.standaloneSetup(accountResource).setControllerAdvice(exceptionTranslator).build();
+  }
 
   @Test
   @WithUnauthenticatedMockUser
   public void testNonAuthenticatedUser() throws Exception {
-    restAccountMockMvc
-      .perform(get("/api/authenticate").accept(MediaType.APPLICATION_JSON))
-      .andExpect(status().isOk())
-      .andExpect(content().string(""));
+    mockMvc.perform(get("/api/authenticate").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()).andExpect(content().string(""));
   }
 
   @Test
   public void testAuthenticatedUser() throws Exception {
-    restAccountMockMvc
+    mockMvc
       .perform(
         get("/api/authenticate")
           .with(
@@ -94,7 +109,7 @@ public class AccountResourceIT {
     user.setAuthorities(authorities);
     userService.createUser(user);
 
-    restAccountMockMvc
+    mockMvc
       .perform(get("/api/account").accept(MediaType.APPLICATION_JSON))
       .andExpect(status().isOk())
       .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
@@ -109,7 +124,7 @@ public class AccountResourceIT {
 
   @Test
   public void testGetUnknownAccount() throws Exception {
-    restAccountMockMvc.perform(get("/api/account").accept(MediaType.APPLICATION_PROBLEM_JSON)).andExpect(status().isInternalServerError());
+    mockMvc.perform(get("/api/account").accept(MediaType.APPLICATION_PROBLEM_JSON)).andExpect(status().isInternalServerError());
   }
 
   @Test
@@ -126,7 +141,7 @@ public class AccountResourceIT {
     validUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER));
     assertThat(userRepository.findOneByLogin("test-register-valid").isPresent()).isFalse();
 
-    restAccountMockMvc
+    mockMvc
       .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(validUser)))
       .andExpect(status().isCreated());
 
@@ -147,7 +162,7 @@ public class AccountResourceIT {
     invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE);
     invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER));
 
-    restAccountMockMvc
+    mockMvc
       .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser)))
       .andExpect(status().isBadRequest());
 
@@ -169,7 +184,7 @@ public class AccountResourceIT {
     invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE);
     invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER));
 
-    restAccountMockMvc
+    mockMvc
       .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser)))
       .andExpect(status().isBadRequest());
 
@@ -191,7 +206,7 @@ public class AccountResourceIT {
     invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE);
     invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER));
 
-    restAccountMockMvc
+    mockMvc
       .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser)))
       .andExpect(status().isBadRequest());
 
@@ -213,7 +228,7 @@ public class AccountResourceIT {
     invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE);
     invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER));
 
-    restAccountMockMvc
+    mockMvc
       .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser)))
       .andExpect(status().isBadRequest());
 
@@ -251,12 +266,12 @@ public class AccountResourceIT {
     secondUser.setAuthorities(new HashSet<>(firstUser.getAuthorities()));
 
     // First user
-    restAccountMockMvc
+    mockMvc
       .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(firstUser)))
       .andExpect(status().isCreated());
 
     // Second (non activated) user
-    restAccountMockMvc
+    mockMvc
       .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser)))
       .andExpect(status().isCreated());
 
@@ -266,7 +281,7 @@ public class AccountResourceIT {
     userRepository.save(testUser.get());
 
     // Second (already activated) user
-    restAccountMockMvc
+    mockMvc
       .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser)))
       .andExpect(status().is4xxClientError());
   }
@@ -286,7 +301,7 @@ public class AccountResourceIT {
     firstUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER));
 
     // Register first user
-    restAccountMockMvc
+    mockMvc
       .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(firstUser)))
       .andExpect(status().isCreated());
 
@@ -305,7 +320,7 @@ public class AccountResourceIT {
     secondUser.setAuthorities(new HashSet<>(firstUser.getAuthorities()));
 
     // Register second (non activated) user
-    restAccountMockMvc
+    mockMvc
       .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser)))
       .andExpect(status().isCreated());
 
@@ -328,7 +343,7 @@ public class AccountResourceIT {
     userWithUpperCaseEmail.setAuthorities(new HashSet<>(firstUser.getAuthorities()));
 
     // Register third (not activated) user
-    restAccountMockMvc
+    mockMvc
       .perform(
         post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userWithUpperCaseEmail))
       )
@@ -342,7 +357,7 @@ public class AccountResourceIT {
     userService.updateUser((new UserDTO(testUser4.get())));
 
     // Register 4th (already activated) user
-    restAccountMockMvc
+    mockMvc
       .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser)))
       .andExpect(status().is4xxClientError());
   }
@@ -361,7 +376,7 @@ public class AccountResourceIT {
     validUser.setLangKey(Constants.DEFAULT_LANGUAGE);
     validUser.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN));
 
-    restAccountMockMvc
+    mockMvc
       .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(validUser)))
       .andExpect(status().isCreated());
 
@@ -383,7 +398,7 @@ public class AccountResourceIT {
 
     userRepository.saveAndFlush(user);
 
-    restAccountMockMvc.perform(get("/api/activate?key={activationKey}", activationKey)).andExpect(status().isOk());
+    mockMvc.perform(get("/api/activate?key={activationKey}", activationKey)).andExpect(status().isOk());
 
     user = userRepository.findOneByLogin(user.getLogin()).orElse(null);
     assertThat(user.getActivated()).isTrue();
@@ -392,7 +407,7 @@ public class AccountResourceIT {
   @Test
   @Transactional
   public void testActivateAccountWithWrongKey() throws Exception {
-    restAccountMockMvc.perform(get("/api/activate?key=wrongActivationKey")).andExpect(status().isInternalServerError());
+    mockMvc.perform(get("/api/activate?key=wrongActivationKey")).andExpect(status().isInternalServerError());
   }
 
   @Test
@@ -416,7 +431,7 @@ public class AccountResourceIT {
     userDTO.setLangKey(Constants.DEFAULT_LANGUAGE);
     userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN));
 
-    restAccountMockMvc
+    mockMvc
       .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO)))
       .andExpect(status().isOk());
 
@@ -453,7 +468,7 @@ public class AccountResourceIT {
     userDTO.setLangKey(Constants.DEFAULT_LANGUAGE);
     userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN));
 
-    restAccountMockMvc
+    mockMvc
       .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO)))
       .andExpect(status().isBadRequest());
 
@@ -489,7 +504,7 @@ public class AccountResourceIT {
     userDTO.setLangKey(Constants.DEFAULT_LANGUAGE);
     userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN));
 
-    restAccountMockMvc
+    mockMvc
       .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO)))
       .andExpect(status().isBadRequest());
 
@@ -518,7 +533,7 @@ public class AccountResourceIT {
     userDTO.setLangKey(Constants.DEFAULT_LANGUAGE);
     userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN));
 
-    restAccountMockMvc
+    mockMvc
       .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO)))
       .andExpect(status().isOk());
 
@@ -537,7 +552,7 @@ public class AccountResourceIT {
     user.setEmail("change-password-wrong-existing-password@example.com");
     userRepository.saveAndFlush(user);
 
-    restAccountMockMvc
+    mockMvc
       .perform(
         post("/api/account/change-password")
           .contentType(MediaType.APPLICATION_JSON)
@@ -561,7 +576,7 @@ public class AccountResourceIT {
     user.setEmail("change-password@example.com");
     userRepository.saveAndFlush(user);
 
-    restAccountMockMvc
+    mockMvc
       .perform(
         post("/api/account/change-password")
           .contentType(MediaType.APPLICATION_JSON)
@@ -586,7 +601,7 @@ public class AccountResourceIT {
 
     String newPassword = RandomStringUtils.random(ManagedUserVM.PASSWORD_MIN_LENGTH - 1);
 
-    restAccountMockMvc
+    mockMvc
       .perform(
         post("/api/account/change-password")
           .contentType(MediaType.APPLICATION_JSON)
@@ -611,7 +626,7 @@ public class AccountResourceIT {
 
     String newPassword = RandomStringUtils.random(ManagedUserVM.PASSWORD_MAX_LENGTH + 1);
 
-    restAccountMockMvc
+    mockMvc
       .perform(
         post("/api/account/change-password")
           .contentType(MediaType.APPLICATION_JSON)
@@ -634,7 +649,7 @@ public class AccountResourceIT {
     user.setEmail("change-password-empty@example.com");
     userRepository.saveAndFlush(user);
 
-    restAccountMockMvc
+    mockMvc
       .perform(
         post("/api/account/change-password")
           .contentType(MediaType.APPLICATION_JSON)
@@ -656,7 +671,7 @@ public class AccountResourceIT {
     user.setEmail("password-reset@example.com");
     userRepository.saveAndFlush(user);
 
-    restAccountMockMvc.perform(post("/api/account/reset-password/init").content("password-reset@example.com")).andExpect(status().isOk());
+    mockMvc.perform(post("/api/account/reset-password/init").content("password-reset@example.com")).andExpect(status().isOk());
   }
 
   @Test
@@ -669,16 +684,12 @@ public class AccountResourceIT {
     user.setEmail("password-reset-upper-case@example.com");
     userRepository.saveAndFlush(user);
 
-    restAccountMockMvc
-      .perform(post("/api/account/reset-password/init").content("password-reset-upper-case@EXAMPLE.COM"))
-      .andExpect(status().isOk());
+    mockMvc.perform(post("/api/account/reset-password/init").content("password-reset-upper-case@EXAMPLE.COM")).andExpect(status().isOk());
   }
 
   @Test
   public void testRequestPasswordResetWrongEmail() throws Exception {
-    restAccountMockMvc
-      .perform(post("/api/account/reset-password/init").content("password-reset-wrong-email@example.com"))
-      .andExpect(status().isOk());
+    mockMvc.perform(post("/api/account/reset-password/init").content("password-reset-wrong-email@example.com")).andExpect(status().isOk());
   }
 
   @Test
@@ -696,7 +707,7 @@ public class AccountResourceIT {
     keyAndPassword.setKey(user.getResetKey());
     keyAndPassword.setNewPassword("new password");
 
-    restAccountMockMvc
+    mockMvc
       .perform(
         post("/api/account/reset-password/finish")
           .contentType(MediaType.APPLICATION_JSON)
@@ -723,7 +734,7 @@ public class AccountResourceIT {
     keyAndPassword.setKey(user.getResetKey());
     keyAndPassword.setNewPassword("foo");
 
-    restAccountMockMvc
+    mockMvc
       .perform(
         post("/api/account/reset-password/finish")
           .contentType(MediaType.APPLICATION_JSON)
@@ -742,7 +753,7 @@ public class AccountResourceIT {
     keyAndPassword.setKey("wrong reset key");
     keyAndPassword.setNewPassword("new password");
 
-    restAccountMockMvc
+    mockMvc
       .perform(
         post("/api/account/reset-password/finish")
           .contentType(MediaType.APPLICATION_JSON)
diff --git a/borestop/src/test/java/com/ippon/borestop/web/rest/AccountsSteps.java b/borestop/src/test/java/com/ippon/borestop/web/rest/AccountsSteps.java
index ea9274f8..59404234 100644
--- a/borestop/src/test/java/com/ippon/borestop/web/rest/AccountsSteps.java
+++ b/borestop/src/test/java/com/ippon/borestop/web/rest/AccountsSteps.java
@@ -2,7 +2,7 @@ package com.ippon.borestop.web.rest;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
-import com.ippon.borestop.infrastructure.primay.CucumberTestContext;
+import com.ippon.borestop.common.infrastructure.primary.CucumberTestContext;
 import io.cucumber.java.en.Then;
 import io.cucumber.java.en.When;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/borestop/src/test/java/com/ippon/borestop/web/rest/errors/ExceptionTranslatorIT.java b/borestop/src/test/java/com/ippon/borestop/web/rest/errors/ExceptionTranslatorIT.java
deleted file mode 100644
index 853322e5..00000000
--- a/borestop/src/test/java/com/ippon/borestop/web/rest/errors/ExceptionTranslatorIT.java
+++ /dev/null
@@ -1,117 +0,0 @@
-package com.ippon.borestop.web.rest.errors;
-
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-
-import com.ippon.borestop.BorestopApp;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.http.MediaType;
-import org.springframework.security.test.context.support.WithMockUser;
-import org.springframework.test.web.servlet.MockMvc;
-
-/**
- * Integration tests {@link ExceptionTranslator} controller advice.
- */
-@WithMockUser
-@AutoConfigureMockMvc
-@SpringBootTest(classes = BorestopApp.class)
-public class ExceptionTranslatorIT {
-  @Autowired
-  private MockMvc mockMvc;
-
-  @Test
-  public void testConcurrencyFailure() throws Exception {
-    mockMvc
-      .perform(get("/api/exception-translator-test/concurrency-failure"))
-      .andExpect(status().isConflict())
-      .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON))
-      .andExpect(jsonPath("$.message").value(ErrorConstants.ERR_CONCURRENCY_FAILURE));
-  }
-
-  @Test
-  public void testMethodArgumentNotValid() throws Exception {
-    mockMvc
-      .perform(post("/api/exception-translator-test/method-argument").content("{}").contentType(MediaType.APPLICATION_JSON))
-      .andExpect(status().isBadRequest())
-      .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON))
-      .andExpect(jsonPath("$.message").value(ErrorConstants.ERR_VALIDATION))
-      .andExpect(jsonPath("$.fieldErrors.[0].objectName").value("test"))
-      .andExpect(jsonPath("$.fieldErrors.[0].field").value("test"))
-      .andExpect(jsonPath("$.fieldErrors.[0].message").value("NotNull"));
-  }
-
-  @Test
-  public void testMissingServletRequestPartException() throws Exception {
-    mockMvc
-      .perform(get("/api/exception-translator-test/missing-servlet-request-part"))
-      .andExpect(status().isBadRequest())
-      .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON))
-      .andExpect(jsonPath("$.message").value("error.http.400"));
-  }
-
-  @Test
-  public void testMissingServletRequestParameterException() throws Exception {
-    mockMvc
-      .perform(get("/api/exception-translator-test/missing-servlet-request-parameter"))
-      .andExpect(status().isBadRequest())
-      .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON))
-      .andExpect(jsonPath("$.message").value("error.http.400"));
-  }
-
-  @Test
-  public void testAccessDenied() throws Exception {
-    mockMvc
-      .perform(get("/api/exception-translator-test/access-denied"))
-      .andExpect(status().isForbidden())
-      .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON))
-      .andExpect(jsonPath("$.message").value("error.http.403"))
-      .andExpect(jsonPath("$.detail").value("test access denied!"));
-  }
-
-  @Test
-  public void testUnauthorized() throws Exception {
-    mockMvc
-      .perform(get("/api/exception-translator-test/unauthorized"))
-      .andExpect(status().isUnauthorized())
-      .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON))
-      .andExpect(jsonPath("$.message").value("error.http.401"))
-      .andExpect(jsonPath("$.path").value("/api/exception-translator-test/unauthorized"))
-      .andExpect(jsonPath("$.detail").value("test authentication failed!"));
-  }
-
-  @Test
-  public void testMethodNotSupported() throws Exception {
-    mockMvc
-      .perform(post("/api/exception-translator-test/access-denied"))
-      .andExpect(status().isMethodNotAllowed())
-      .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON))
-      .andExpect(jsonPath("$.message").value("error.http.405"))
-      .andExpect(jsonPath("$.detail").value("Request method 'POST' not supported"));
-  }
-
-  @Test
-  public void testExceptionWithResponseStatus() throws Exception {
-    mockMvc
-      .perform(get("/api/exception-translator-test/response-status"))
-      .andExpect(status().isBadRequest())
-      .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON))
-      .andExpect(jsonPath("$.message").value("error.http.400"))
-      .andExpect(jsonPath("$.title").value("test response status"));
-  }
-
-  @Test
-  public void testInternalServerError() throws Exception {
-    mockMvc
-      .perform(get("/api/exception-translator-test/internal-server-error"))
-      .andExpect(status().isInternalServerError())
-      .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON))
-      .andExpect(jsonPath("$.message").value("error.http.500"))
-      .andExpect(jsonPath("$.title").value("Internal Server Error"));
-  }
-}
diff --git a/borestop/src/test/java/com/ippon/borestop/web/rest/errors/ExceptionTranslatorTestController.java b/borestop/src/test/java/com/ippon/borestop/web/rest/errors/ExceptionTranslatorTestController.java
deleted file mode 100644
index c3effbbe..00000000
--- a/borestop/src/test/java/com/ippon/borestop/web/rest/errors/ExceptionTranslatorTestController.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.ippon.borestop.web.rest.errors;
-
-import javax.validation.Valid;
-import javax.validation.constraints.NotNull;
-import org.springframework.dao.ConcurrencyFailureException;
-import org.springframework.http.HttpStatus;
-import org.springframework.security.access.AccessDeniedException;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.web.bind.annotation.*;
-
-@RestController
-@RequestMapping("/api/exception-translator-test")
-public class ExceptionTranslatorTestController {
-
-  @GetMapping("/concurrency-failure")
-  public void concurrencyFailure() {
-    throw new ConcurrencyFailureException("test concurrency failure");
-  }
-
-  @PostMapping("/method-argument")
-  public void methodArgument(@Valid @RequestBody TestDTO testDTO) {}
-
-  @GetMapping("/missing-servlet-request-part")
-  public void missingServletRequestPartException(@RequestPart String part) {}
-
-  @GetMapping("/missing-servlet-request-parameter")
-  public void missingServletRequestParameterException(@RequestParam String param) {}
-
-  @GetMapping("/access-denied")
-  public void accessdenied() {
-    throw new AccessDeniedException("test access denied!");
-  }
-
-  @GetMapping("/unauthorized")
-  public void unauthorized() {
-    throw new BadCredentialsException("test authentication failed!");
-  }
-
-  @GetMapping("/response-status")
-  public void exceptionWithResponseStatus() {
-    throw new TestResponseStatusException();
-  }
-
-  @GetMapping("/internal-server-error")
-  public void internalServerError() {
-    throw new RuntimeException();
-  }
-
-  public static class TestDTO {
-    @NotNull
-    private String test;
-
-    public String getTest() {
-      return test;
-    }
-
-    public void setTest(String test) {
-      this.test = test;
-    }
-  }
-
-  @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "test response status")
-  @SuppressWarnings("serial")
-  public static class TestResponseStatusException extends RuntimeException {}
-}
diff --git a/borestop/src/test/resources/i18n/messages_en.properties b/borestop/src/test/resources/i18n/messages_en.properties
deleted file mode 100644
index 7b3fbb99..00000000
--- a/borestop/src/test/resources/i18n/messages_en.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-email.test.title=test title
-# Value used for English locale unit test in MailServiceIT
-# as this file is loaded instead of real file
-email.activation.title=borestop account activation
-- 
GitLab