diff --git a/pom.xml b/pom.xml
index 79ab203f2971a47037d0456e217c377e7b06098d..838689e6331ce0697b8297284132b59a88e47616 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>fr.agrometinfo</groupId>
   <artifactId>www</artifactId>
-  <version>2.0.0-alpha-2</version>
+  <version>2.0.0-SNAPSHOT</version>
   <packaging>pom</packaging>
   <name>AgroMetInfo web app</name>
   <description>Website for AgroMetInfo in Jakarta EE 10 and GWT.</description>
@@ -91,7 +91,7 @@
       <artifactId>junit-jupiter-params</artifactId>
       <version>${junit.version}</version>
       <scope>test</scope>
-	</dependency>
+    </dependency>
   </dependencies>
 
   <dependencyManagement>
diff --git a/www-client/pom.xml b/www-client/pom.xml
index 384a0dc1b01beb7883260a259fef32cf9ef28232..f937fee3f69ed0d719124e1235674fec77570db1 100644
--- a/www-client/pom.xml
+++ b/www-client/pom.xml
@@ -7,7 +7,7 @@
     <parent>
         <groupId>fr.agrometinfo</groupId>
         <artifactId>www</artifactId>
-        <version>2.0.0-alpha-2</version>
+        <version>2.0.0-SNAPSHOT</version>
     </parent>
 
     <artifactId>www-client</artifactId>
diff --git a/www-server/pom.xml b/www-server/pom.xml
index 5f69e6e693d44bdbd61520aaeecf70c37b47774c..37ecee7301067d78cce05fef82a6916c2ff23eb8 100644
--- a/www-server/pom.xml
+++ b/www-server/pom.xml
@@ -7,7 +7,7 @@
   <parent>
     <groupId>fr.agrometinfo</groupId>
     <artifactId>www</artifactId>
-    <version>2.0.0-alpha-2</version>
+    <version>2.0.0-SNAPSHOT</version>
   </parent>
 
   <artifactId>www-server</artifactId>
@@ -125,14 +125,14 @@
     </dependency>
     <!-- Jakarta mail -->
     <dependency>
-        <groupId>jakarta.mail</groupId>
-        <artifactId>jakarta.mail-api</artifactId>
-        <version>2.1.3</version>
+      <groupId>jakarta.mail</groupId>
+      <artifactId>jakarta.mail-api</artifactId>
+      <version>2.1.3</version>
     </dependency>
     <dependency>
-        <groupId>org.eclipse.angus</groupId>
-        <artifactId>jakarta.mail</artifactId>
-        <version>2.0.3</version>
+      <groupId>org.eclipse.angus</groupId>
+      <artifactId>jakarta.mail</artifactId>
+      <version>2.0.3</version>
     </dependency>
     <!-- JPA -->
     <dependency>
@@ -155,17 +155,28 @@
       <artifactId>postgresql</artifactId>
       <version>42.7.3</version>
     </dependency>
+    <!-- JSTL -->
+    <dependency>
+      <groupId>jakarta.servlet.jsp.jstl</groupId>
+      <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
+      <version>3.0.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.glassfish.web</groupId>
+      <artifactId>jakarta.servlet.jsp.jstl</artifactId>
+      <version>3.0.0</version>
+    </dependency>
     <!-- fast-serialization -->
     <!-- https://mvnrepository.com/artifact/de.ruedigermoeller/fst -->
     <dependency>
-        <groupId>com.fasterxml.jackson.core</groupId>
-        <artifactId>jackson-core</artifactId>
-        <version>2.16.1</version>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-core</artifactId>
+      <version>2.16.1</version>
     </dependency>
     <dependency>
-        <groupId>de.ruedigermoeller</groupId>
-        <artifactId>fst</artifactId>
-        <version>3.0.4-jdk17</version>
+      <groupId>de.ruedigermoeller</groupId>
+      <artifactId>fst</artifactId>
+      <version>3.0.4-jdk17</version>
     </dependency>
     <!-- SAVA -->
     <dependency>
@@ -182,20 +193,20 @@
     </dependency>
     <!-- Tests for Jersey -->
     <dependency>
-        <groupId>org.glassfish.jersey.test-framework</groupId>
-        <artifactId>jersey-test-framework-core</artifactId>
-        <scope>test</scope>
+      <groupId>org.glassfish.jersey.test-framework</groupId>
+      <artifactId>jersey-test-framework-core</artifactId>
+      <scope>test</scope>
     </dependency>
     <dependency>
-        <groupId>org.glassfish.jersey.test-framework.providers</groupId>
-        <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
-        <scope>test</scope>
+      <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+      <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
+      <scope>test</scope>
     </dependency>
     <dependency>
-        <groupId>org.mockito</groupId>
-        <artifactId>mockito-core</artifactId>
-        <version>${mockito-core.version}</version>
-        <scope>test</scope>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>${mockito-core.version}</version>
+      <scope>test</scope>
     </dependency>
   </dependencies>
 
diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/ErrorHandlerServlet.java b/www-server/src/main/java/fr/agrometinfo/www/server/ErrorHandlerServlet.java
new file mode 100644
index 0000000000000000000000000000000000000000..90a9e3b827dba53819c2f7726580d64d7b1c1ec7
--- /dev/null
+++ b/www-server/src/main/java/fr/agrometinfo/www/server/ErrorHandlerServlet.java
@@ -0,0 +1,93 @@
+package fr.agrometinfo.www.server;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import fr.agrometinfo.www.server.AgroMetInfoConfiguration.ConfigurationKey;
+import fr.agrometinfo.www.server.util.LocaleUtils;
+import jakarta.inject.Inject;
+import jakarta.servlet.RequestDispatcher;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+/**
+ * Servlet to display user friendly error messages.
+ *
+ * @author Olivier Maury
+ */
+@WebServlet(urlPatterns = "/errorHandler")
+public final class ErrorHandlerServlet extends HttpServlet {
+    /**
+     * UID.
+     */
+    private static final long serialVersionUID = 1003353995341179825L;
+
+    /**
+     * Application config.
+     */
+    @Inject
+    private AgroMetInfoConfiguration config;
+
+    @Override
+    protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
+            throws IOException, ServletException {
+        final String appUrl = config.get(ConfigurationKey.APP_URL) + "../";
+        final Exception exception = (Exception) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
+
+        final Locale locale = LocaleUtils.getLocale(request);
+        final I18n i18n = new I18n("fr.agrometinfo.www.server.i18n", locale);
+        String body;
+        final String title = i18n.format(response.getStatus(), "http.status.title");
+        switch (response.getStatus()) {
+        case HttpServletResponse.SC_BAD_REQUEST:
+            body = "<p>La demande n'a pas pu être comprise par le serveur à cause d'une syntaxe mal formée.<br />"
+                    + "Vous NE DEVRIEZ PAS répéter la demande sans modifications.</p>";
+            break;
+        case HttpServletResponse.SC_NOT_FOUND:
+            body = "<p>La page que vous cherchez n'existe pas, mais vous n'êtes pas "
+                    + "pour autant dans une impasse. Voici quelques options disponibles :</p>" //
+                    + "<ul>"
+                    + "<li>Assurez-vous d'utiliser la bonne adresse URL.</li>" + "<li>Consultez <a href=\"" + appUrl
+                    + "\" target=\"_blank\">la documentation</a>.</li>"
+                    + "<li>Obtenez de l'aide en écrivant une demande de support dans l'application ou <a href=\""
+                    + appUrl + "/contact.html\">contactez-nous</a>.</li>" //
+                    + "</ul>";
+            break;
+        case HttpServletResponse.SC_INTERNAL_SERVER_ERROR:
+            body = "<p>Le serveur a rencontré une condition inattendue qui l'empêche de satisfaire la demande.</p>"
+                    + "<p><b>Erreur&nbsp;:</b> " + exception + "</p>" + "<p><b>Cause&nbsp;:</b> " + exception.getCause()
+                    + "</p>";
+            break;
+        case HttpServletResponse.SC_SERVICE_UNAVAILABLE:
+            body = "<p>Le serveur est incapable de traiter actuellement la demande à cause d'une surcharge temporaire "
+                    + "ou de la maintenance du serveur.<br />"
+                    + "Il s'agit d'une condition temporaire qui sera levée après un certain temps.</p>";
+            break;
+        default:
+            body = "<p><b>Erreur&nbsp;:</b> " + exception + "</p>";
+            if (exception != null) {
+                body += "<p><b>Message d'erreur&nbsp;:</b>" + exception.getMessage() + "</p>";
+            }
+            body += "<p><b>Status code:</b> " + response.getStatus() + "</p>";
+            body += "<p><b>Request URI:</b> " + request.getScheme() + "://" + request.getServerName()
+            + request.getRequestURI() + "</p>";
+            body += "<p><b>URL:</b> " + request.getRequestURI() + "</p>";
+            body += "<p><b>Context path:</b> " + getServletContext().getContextPath() + "</p>";
+            if (exception != null) {
+                body += "<p><b>Cause:</b> " + exception.getCause() + "</p>";
+            }
+            break;
+        }
+        request.setAttribute("appUrl", appUrl);
+        request.setAttribute("body", body);
+        request.setAttribute("locale", locale.getLanguage());
+        request.setAttribute("statusCode", response.getStatus());
+        request.setAttribute("title", title);
+
+        response.setContentType("text/html;charset=UTF-8");
+        getServletContext().getRequestDispatcher("/WEB-INF/error.jsp").forward(request, response);
+    }
+}
diff --git a/www-server/src/main/resources/fr/agrometinfo/www/server/i18n.properties b/www-server/src/main/resources/fr/agrometinfo/www/server/i18n.properties
index e3e82d997585f9f235ebd2f9954836faaafebab4..1f20f45a12cc535529c0351d806b59e28b7dc842 100644
--- a/www-server/src/main/resources/fr/agrometinfo/www/server/i18n.properties
+++ b/www-server/src/main/resources/fr/agrometinfo/www/server/i18n.properties
@@ -5,3 +5,8 @@ error.MAIL.SEND_FAILED=The e-mail "{0}" for the recipients {1} was not sent: {2}
 error.MAIL.SEND_FAILED_INVALID=the e-mail "{0}" was not sent to these invalid addresses: {1}.
 error.MAIL.SEND_FAILED_VALID=The e-mail "{0}" was not sent to these valid addresses: {1}.
 error.START=Start
+http.status.title=Error
+http.status.title[\=400]=Bad request
+http.status.title[\=404]=Document not found
+http.status.title[\=500]=Internal server error
+http.status.title[\=503]=Service unavailable
diff --git a/www-server/src/main/resources/fr/agrometinfo/www/server/i18n_fr.properties b/www-server/src/main/resources/fr/agrometinfo/www/server/i18n_fr.properties
index ef082e9cbd6ca47f415dddff57f89a6203ee046a..b26453f2643bb6fcd689f99f12bad4b7280591ba 100644
--- a/www-server/src/main/resources/fr/agrometinfo/www/server/i18n_fr.properties
+++ b/www-server/src/main/resources/fr/agrometinfo/www/server/i18n_fr.properties
@@ -5,3 +5,8 @@ error.MAIL.SEND_FAILED=Le courriel « {0} » dont les destinataires sont {1} n'a
 error.MAIL.SEND_FAILED_INVALID=Le courriel « {0} » n'a pas été envoyé à ces adresses qui sont invalides : {1}.
 error.MAIL.SEND_FAILED_VALID=Le courriel « {0} » n'a pas été envoyé à ces adresses qui sont valides : {1}.
 error.START=Démarrage
+http.status.title=Erreur
+http.status.title[\=400]=Mauvaise demande
+http.status.title[\=404]=Document non trouvé
+http.status.title[\=500]=Erreur interne du serveur
+http.status.title[\=503]=Service indisponible
diff --git a/www-server/src/main/resources/fr/agrometinfo/www/server/jsp.properties b/www-server/src/main/resources/fr/agrometinfo/www/server/jsp.properties
new file mode 100644
index 0000000000000000000000000000000000000000..dd42edfa1e7a6ff93ceebcf84bd66c21552e891c
--- /dev/null
+++ b/www-server/src/main/resources/fr/agrometinfo/www/server/jsp.properties
@@ -0,0 +1,9 @@
+# Ce fichier est encodé en UTF-8
+page.cookies=Cookies
+page.error=Error page
+page.legal-notice=Legal notice
+page.privacy=Privacy policy
+page.credits=Credits
+page.citation=Citations
+page.release-notes=Release notes
+page.contact=Contact us
diff --git a/www-server/src/main/resources/fr/agrometinfo/www/server/jsp_fr.properties b/www-server/src/main/resources/fr/agrometinfo/www/server/jsp_fr.properties
new file mode 100644
index 0000000000000000000000000000000000000000..1092f583512211a9f358a67e71df3d4578a10988
--- /dev/null
+++ b/www-server/src/main/resources/fr/agrometinfo/www/server/jsp_fr.properties
@@ -0,0 +1,9 @@
+# Ce fichier est encodé en UTF-8
+page.cookies=Cookies
+page.error=Page d'erreur
+page.legal-notice=Mentions légales
+page.privacy=Politique de confidentialité
+page.credits=Crédits
+page.citation=Citations
+page.release-notes=Notes de version
+page.contact=Contactez-nous
diff --git a/www-server/src/main/webapp/WEB-INF/error.jsp b/www-server/src/main/webapp/WEB-INF/error.jsp
new file mode 100644
index 0000000000000000000000000000000000000000..c060ee8c7e5b663048bf4c80a6f7cae5c6a47779
--- /dev/null
+++ b/www-server/src/main/webapp/WEB-INF/error.jsp
@@ -0,0 +1,12 @@
+<%@page language="java" isErrorPage="true"
+        contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
+<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<fmt:setLocale value="${locale}" />
+<fmt:setBundle basename="fr.agrometinfo.www.server.jsp" />
+<%@taglib prefix="t" tagdir="/WEB-INF/tags" %>
+<t:page>
+        <jsp:attribute name="appUrl">${appUrl}</jsp:attribute>
+        <jsp:attribute name="locale">${locale}</jsp:attribute>
+        <jsp:attribute name="title"><fmt:message key="page.error" /> ${statusCode} − ${title}</jsp:attribute>
+        <jsp:body>${body}</jsp:body>
+</t:page>
diff --git a/www-server/src/main/webapp/WEB-INF/tags/page.tag b/www-server/src/main/webapp/WEB-INF/tags/page.tag
new file mode 100644
index 0000000000000000000000000000000000000000..b54979fd27ff34e0b2cac6ddcb32b3edacef4926
--- /dev/null
+++ b/www-server/src/main/webapp/WEB-INF/tags/page.tag
@@ -0,0 +1,84 @@
+<%@tag description="Overall Page template" pageEncoding="UTF-8"%>
+<%@attribute name="appUrl" required="true" %>
+<%@attribute name="locale" required="true" %>
+<%@attribute name="title" required="true" %>
+<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<fmt:setLocale value="${locale}" />
+<fmt:setBundle basename="fr.agrometinfo.www.server.jsp" />
+<!DOCTYPE html>
+<html lang="fr" dir="ltr">
+    <head>
+        <meta charset="utf-8">
+        <meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0, minimum-scale=1.0">
+        <meta name="description" content="AgroMetInfo - mise à disposition d’indicateurs agroclimatiques et des indicateurs de suivi de culture d’hiver (culture type  blé tendre) et de printemps (culture type maïs) par le modèle STICS en temps réel sous forme de cartes et de graphiques.">
+        <link rel="license" href="https://www.etalab.gouv.fr/licence-ouverte-open-licence/">
+        <link href="${appUrl}images/favicon.ico" rel="icon" type="image/x-icon" sizes="any">
+        <link href="${appUrl}css/fontawesome-all.min.css" rel="stylesheet" media="print" onload="this.media = 'all';
+                this.onload = null;">
+        <link href="${appUrl}css/home.css" rel="stylesheet">
+        <link href="${appUrl}css/fonts.css" rel="stylesheet" media="print" onload="this.media = 'all';
+                this.onload = null;">
+        <title>AgroMetInfo − ${title}</title>
+        <script>
+          const baseUriFull='${appUrl}';
+          const localStorageThemeKey = baseUriFull + 'variant';
+          function setTheme(theme) {
+            document.documentElement.setAttribute("data-theme", theme);
+            localStorage.setItem(localStorageThemeKey, "zen-" + theme);
+            var classes = "fas fa-toggle-on";
+            if (theme == "dark") {
+              classes = "fas fa-toggle-off";
+            }
+            var elem = document.getElementById("toggle-theme").className = classes;
+          }
+          function getTheme() {
+            var theme = "light";
+            if (localStorage.getItem(localStorageThemeKey)) {
+              if (localStorage.getItem(localStorageThemeKey) == "zen-dark") {
+                theme = "dark";
+              }
+            } else if (document.documentElement.getAttribute("data-theme")) {
+              if (document.documentElement.getAttribute("data-theme") == "dark") {
+                theme = "dark";
+              }
+            } else if(window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
+              theme = "dark";
+            }
+            return theme;
+          }
+          function toggleTheme() {
+            const theme = getTheme();
+            if (theme === 'dark') {
+              setTheme('light');
+            } else {
+              setTheme('dark');
+            }
+          }
+          setTheme(getTheme());
+        </script>
+    </head>
+    <body>
+    	<nav>
+    		    <ul>
+    		    	<li class="brand">
+    		    		<a href="${appUrl}"><img alt="logo" src="${appUrl}images/logo_etat-agrometinfo.svg" /></a>
+  		    		</li>
+		    	</ul>
+    	</nav>
+        <article>
+            <jsp:doBody/>
+        </article>
+        <footer>
+            <ul>
+                <li><a href="${appUrl}cookies-use.html"><fmt:message key="page.cookies" /></a></li>
+                <li><a href="${appUrl}legal-notice.html"><fmt:message key="page.legal-notice" /></a></li>
+                <li><a href="${appUrl}privacy.html"><fmt:message key="page.privacy" /></a></li>
+                <li><a href="${appUrl}credits.html"><fmt:message key="page.credits" /></a></li>
+                <li><a href="${appUrl}citation.html"><fmt:message key="page.citation" /></a></li>
+                <li><a href="${appUrl}release-notes.html"><fmt:message key="page.release-notes" /></a></li>
+                <li><a href="${appUrl}contact.html"><fmt:message key="page.contact" /></a></li>
+            </ul>
+            <div class="license">Sauf mention contraire, tous les textes de ce site sont sous <a href="https://www.etalab.gouv.fr/licence-ouverte-open-licence/" target="_blank">licence etalab-2.0</a>.</div>
+        </footer>
+    </body>
+</html>
diff --git a/www-server/src/main/webapp/WEB-INF/web.xml b/www-server/src/main/webapp/WEB-INF/web.xml
index aa82e1ca5173ed52b21131ba6a0f7b9b11d7af9a..1687cf814296a6f1c4af1daf9f402700bf619df3 100644
--- a/www-server/src/main/webapp/WEB-INF/web.xml
+++ b/www-server/src/main/webapp/WEB-INF/web.xml
@@ -38,6 +38,9 @@
         <servlet-name>OpenApi</servlet-name>
         <url-pattern>/openapi/*</url-pattern>
     </servlet-mapping>
+    <error-page>
+        <location>/errorHandler</location>
+    </error-page>
     <!-- Default page to serve -->
     <welcome-file-list>
         <welcome-file>index.html</welcome-file>
diff --git a/www-shared/pom.xml b/www-shared/pom.xml
index 6d48e0d8bf0c9737f2f0bf1cda9ba2da70fc1ae5..205c0f1bdf607fd98a929bf41ab4bbb6adff993c 100644
--- a/www-shared/pom.xml
+++ b/www-shared/pom.xml
@@ -7,7 +7,7 @@
   <parent>
     <groupId>fr.agrometinfo</groupId>
     <artifactId>www</artifactId>
-    <version>2.0.0-alpha-2</version>
+    <version>2.0.0-SNAPSHOT</version>
   </parent>
 
   <artifactId>www-shared</artifactId>