diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 119da58a6477e8a163a4bfced6e7bd2c92245492..37ac49db72f6682f582258cb279983bf3036850d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -6,9 +6,7 @@ image: registry.forgemia.inra.fr/agroclim/common/docker-projets-java:latest
 stages:
     - build
     - test
-    - checkstyle
-    - pmd
-    - cpd
+    - code-check
     - package
     - deploy
 
@@ -18,38 +16,75 @@ cache:
 
 build_job:
   stage: build
-  script: 
+  script:
     - echo "Maven compile started"
-    - mvn compile
+    - mvn clean compile test-compile
     - ls -lha /usr/bin/tokei
     - /usr/bin/tokei --version
+  artifacts:
+    paths:
+      - target
 
 test_job:
   stage: test
-  script: 
+  needs: ["build_job"]
+  script:
     - echo "Maven test started"
     - mvn test
+  artifacts:
+    when: always
+    paths:
+      - target
+    reports:
+      junit:
+        - target/surefire-reports/TEST-*.xml
+        - target/failsafe-reports/TEST-*.xml
 
 checkstyle_job:
-  stage: checkstyle
+  stage: code-check
+  needs: ["build_job"]
   script:
     - mvn checkstyle:checkstyle
+  artifacts:
+    paths:
+      - target
 
 pmd_job:
-  stage: pmd
+  stage: code-check
+  needs: ["build_job"]
   script:
     - mvn pmd:pmd
+  artifacts:
+    paths:
+      - target
 
 cpd_job:
-  stage: cpd
+  stage: code-check
+  needs: ["build_job"]
   script:
     - mvn pmd:cpd
+  artifacts:
+    paths:
+      - target
+
+cobertura_job:
+  stage: deploy
+  needs: ["test_job"]
+  script:
+    # convert report from jacoco to cobertura, using relative project path
+    - python /opt/cover2cover.py target/site/jacoco/jacoco.xml $CI_PROJECT_DIR/src/main/java/ > target/cobertura.xml
+  artifacts:
+    paths:
+      - target
 
 package_job:
   stage: package
-  script: 
+  script:
     - echo "Maven packaging started"
     - mvn package -DskipTests
+  artifacts:
+    paths:
+      - target
 
 deploy_job:
   stage: deploy
@@ -57,3 +92,17 @@ deploy_job:
     - main
   script:
     - echo "Maven deploy started"
+
+pages:
+  stage: deploy
+  needs: ["package_job"]
+  script:
+    - mvn exec:java -DskipTests -Dexec.mainClass=fr.inrae.agroclim.indicators.GenerateMarkdown -Dexec.args='${basedir}/src/site/markdown -'
+    - mv src/site/markdown/*-en.md src/site/en/markdown/
+    - mvn site -DskipTests
+  artifacts:
+    paths:
+      - target/site
+  publish: target/site
+  only:
+    - tags
diff --git a/CITATION.cff b/CITATION.cff
index fee74dc442629b105ee4c6b6a4886d0d631d6533..68322cdbb71bad89408d6c6be978112d40237296 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -13,8 +13,8 @@ keywords:
 - Java
 - library
 - indicators
-version: 1.2.5-SNAPSHOT
+version: 1.3.0-SNAPSHOT
 doi: 10.15454/IZUFAP
-date-released: 2023-12-19
+date-released: 2023-12-21
 license: GPL-3.0
 repository-code: https://forgemia.inra.fr/agroclim/Indicators/indicators-java.git
diff --git a/README.md b/README.md
index 2b748cc0e27f0c1b5dfcbe24ff3264f3cea7a872..f614643f290bc3a548e3a80385b3ecfe56983c45 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ The library is used in the free software [GETARI](https://agroclim.inrae.fr/geta
 
 ## 🧐 Features
 
-It contains predefined indicators : 91 daily and 48 hourly.
+It contains predefined indicators : 89 daily and 48 hourly.
 
 ## 🛠️ Tech Stack
 
diff --git a/codemeta.json b/codemeta.json
index a5f97b95043b9a572f5ff162e2b73ed3a5b64045..1429d446ee116286bc2bc11a4ef6b29ca7efa603 100644
--- a/codemeta.json
+++ b/codemeta.json
@@ -99,5 +99,5 @@
             "name": "lombok"
         }
     ],
-    "version": "1.2.5-SNAPSHOT"
+    "version": "1.3.0-SNAPSHOT"
 }
diff --git a/pom.xml b/pom.xml
index d6e135a251f84f48b4dc0b27325dfa8a3bb5c8f0..e8fcf48789f635f162a8471a4b0cff86f25ade50 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@ along with Indicators. If not, see <https://www.gnu.org/licenses/>.
     <name>Indicators</name>
     <description>Library of agro- and eco-climatic indicators.</description>
     <inceptionYear>2018</inceptionYear>
-    <version>1.2.5-SNAPSHOT</version>
+    <version>2.0.0-SNAPSHOT</version>
     <packaging>jar</packaging>
     <licenses>
         <license>
@@ -148,6 +148,12 @@ along with Indicators. If not, see <https://www.gnu.org/licenses/>.
             <artifactId>jackson-dataformat-csv</artifactId>
             <version>2.15.2</version>
         </dependency>
+        <!-- JSON for error output -->
+        <dependency>
+            <groupId>org.json</groupId>
+            <artifactId>json</artifactId>
+            <version>20190722</version>
+        </dependency>
         <!-- JEXL -->
         <dependency>
             <groupId>org.apache.commons</groupId>
@@ -287,6 +293,14 @@ along with Indicators. If not, see <https://www.gnu.org/licenses/>.
                             <goal>report</goal>
                         </goals>
                     </execution>
+                    <execution>
+                        <!-- Generate coverage report XML in target/site/jacoco/ from target/jacoco.exec -->
+                        <id>report-test</id>
+                        <phase>test</phase>
+                        <goals>
+                          <goal>report</goal>
+                        </goals>
+                    </execution>
                     <execution>
                         <!-- Generate coverage report html in target/site/jacoco/ from target/jacoco.exec -->
                         <id>report</id>
@@ -391,12 +405,23 @@ along with Indicators. If not, see <https://www.gnu.org/licenses/>.
                     <artifactId>maven-deploy-plugin</artifactId>
                     <version>2.8.2</version>
                 </plugin>
+                <plugin>
+                    <artifactId>maven-project-info-reports-plugin</artifactId>
+                    <version>3.5.0</version>
+                </plugin>
                 <plugin>
                     <artifactId>maven-site-plugin</artifactId>
                     <version>3.12.1</version>
                     <configuration>
-                        <locales>fr</locales>
+                        <locales>fr,en</locales>
                     </configuration>
+                    <dependencies>
+                      <dependency>
+                        <groupId>org.apache.maven.doxia</groupId>
+                        <artifactId>doxia-module-markdown</artifactId>
+                        <version>2.0.0-M7</version>
+                      </dependency>
+                    </dependencies>
                 </plugin>
                 <!-- -->
                 <plugin>
@@ -517,6 +542,20 @@ along with Indicators. If not, see <https://www.gnu.org/licenses/>.
                         <sourceFileExclude>module-info.*</sourceFileExclude>
                     </sourceFileExcludes>
                 </configuration>
+                <reportSets>
+                    <reportSet>
+                        <id>default</id>
+                        <reports>
+                            <report>javadoc</report>
+                        </reports>
+                    </reportSet>
+                    <reportSet>
+                        <id>aggregate</id>
+                        <reports>
+                            <report>aggregate</report>
+                        </reports>
+                    </reportSet>
+                </reportSets>
             </plugin>
 
             <!-- Include JaCoCo report into site -->
diff --git a/publiccode.yml b/publiccode.yml
index c7537c5b1373a42b9be911ef612b051b1cda0618..3cc75557155f74279d59d244131eeffbe3c2f373 100644
--- a/publiccode.yml
+++ b/publiccode.yml
@@ -49,9 +49,9 @@ maintenance:
     name: "J\xE9r\xE9mie D\xE9c\xF4me"
   type: internal
 name: Indicators
-releaseDate: 2023-12-19
+releaseDate: 2023-12-21
 softwareType: library
-softwareVersion: 1.2.5-SNAPSHOT
+softwareVersion: 1.3.0-SNAPSHOT
 url: https://forgemia.inra.fr/agroclim/Indicators/indicators-java.git
 usedBy:
 - INRAE AgroClim
diff --git a/src/main/java/fr/inrae/agroclim/indicators/GenerateMarkdown.java b/src/main/java/fr/inrae/agroclim/indicators/GenerateMarkdown.java
new file mode 100644
index 0000000000000000000000000000000000000000..6303dd3f920123bb41b7466801c15307142cbf71
--- /dev/null
+++ b/src/main/java/fr/inrae/agroclim/indicators/GenerateMarkdown.java
@@ -0,0 +1,390 @@
+package fr.inrae.agroclim.indicators;
+
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
+import fr.inrae.agroclim.indicators.exception.type.CommonErrorType;
+import fr.inrae.agroclim.indicators.exception.type.ComputationErrorType;
+import fr.inrae.agroclim.indicators.exception.type.ResourceErrorType;
+import fr.inrae.agroclim.indicators.exception.type.XmlErrorType;
+import fr.inrae.agroclim.indicators.model.Knowledge;
+import fr.inrae.agroclim.indicators.model.LocalizedString;
+import fr.inrae.agroclim.indicators.model.Note;
+import fr.inrae.agroclim.indicators.model.Parameter;
+import fr.inrae.agroclim.indicators.model.TimeScale;
+import fr.inrae.agroclim.indicators.model.indicator.CompositeIndicator;
+import fr.inrae.agroclim.indicators.model.indicator.Indicator;
+import fr.inrae.agroclim.indicators.resources.I18n;
+import fr.inrae.agroclim.indicators.util.StringUtils;
+import fr.inrae.agroclim.indicators.util.Utf8BufferedWriter;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+
+/**
+ * Utility to create Markdown and CSV files for Hugo and Maven site.
+ *
+ * @author Olivier Maury
+ */
+@Log4j2
+@RequiredArgsConstructor
+public class GenerateMarkdown {
+
+    /**
+     * Mardown Yaml front matter.
+     */
+    private static final String FRONT_MATTER = """
+                                       ---
+                                       title: %s
+                                       description: %s
+                                       keywords: %s
+                                       date: %s
+                                       ---
+
+                                       """;
+    /**
+     * Creation date for front matter.
+     */
+    private String created;
+
+    /**
+     * Separator between file base name and language code.
+     */
+    private final String languageSep;
+    /**
+     * The directory where Markdown files are generated.
+     */
+    private final String outDir;
+
+    /**
+     * @param args arguments : outDir, languageSep
+     * @throws IndicatorsException while loading knowledge
+     * @throws java.io.IOException while writing file
+     */
+    public static void main(final String[] args) throws IndicatorsException, IOException {
+        LOGGER.traceEntry("Arguments: {}", Arrays.asList(args));
+        final String outDir;
+        final String languageSep;
+        if (args != null && args.length > 0) {
+            outDir = args[0];
+            if (args.length > 1) {
+                languageSep = args[1];
+            } else {
+                languageSep = ".";
+            }
+        } else {
+            outDir = System.getProperty("java.io.tmpdir");
+            languageSep = ".";
+        }
+        var instance = new GenerateMarkdown(languageSep, outDir);
+        instance.run();
+    }
+
+    private void run() throws IndicatorsException, IOException {
+        final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+        created = df.format(new Date());
+
+        for (final Locale l : Arrays.asList(Locale.ENGLISH, Locale.FRENCH)) {
+            writeErrorMdFile(l);
+        }
+        for (final TimeScale timescale : TimeScale.values()) {
+            LOGGER.trace("Generating files for {}...", timescale);
+            final Knowledge knowledge = Knowledge.load(timescale);
+            for (final Locale l : Arrays.asList(Locale.ENGLISH, Locale.FRENCH)) {
+                writeIndicatorsMdFiles(knowledge, timescale, l);
+            }
+            writeIndicatorsCsvFiles(knowledge, timescale);
+            writeParametersCsvFiles(knowledge, timescale);
+            LOGGER.trace("Generating files for {}... done", timescale);
+        }
+        // TODO error codes
+    }
+
+    /**
+     * Write the Markdown file showing all error codes and descriptions.
+     *
+     * @param locale locale for the strings
+     * @throws IOException file not found or error while writting
+     */
+    private void writeErrorMdFile(final Locale locale) throws IOException {
+        final String languageCode = locale.getLanguage();
+        final Path path = Paths.get(outDir, "errors" + languageSep + languageCode + ".md");
+        final String bundleName = "fr.inrae.agroclim.indicators.resources.messages";
+        final I18n i18n = new I18n(bundleName, locale);
+        LOGGER.trace(path);
+        try (BufferedWriter writer = new Utf8BufferedWriter(path)) {
+            writer.write(String.format(FRONT_MATTER,
+                    i18n.get("markdown.error.title"),
+                    i18n.get("markdown.error.description"),
+                    i18n.get("markdown.error.keywords"),
+                    created));
+            writeLn(writer, i18n.get("markdown.error.fullcode"), i18n.get("markdown.error.name"),
+                    i18n.get("markdown.error.message"));
+            writer.write("|:----------|:-----------|:-----------|\n");
+            final List<CommonErrorType> types = new ArrayList<>();
+            types.addAll(Arrays.asList(XmlErrorType.values()));
+            types.addAll(Arrays.asList(ResourceErrorType.values()));
+            types.addAll(Arrays.asList(ComputationErrorType.values()));
+            String previousCat = "";
+            for (final CommonErrorType type : types) {
+                var cat = type.getCategory().getCategory(i18n);
+                if (!previousCat.equals(cat)) {
+                    var catCode = type.getCategory().getCode();
+                    writeLn(writer, "**" + i18n.get("markdown.error.category") + " `" + catCode + "` - " + cat + "**");
+                    previousCat = cat;
+                }
+                var fullCode = type.getFullCode();
+                var name = type.getName();
+                var description = i18n.get(type.getI18nKey());
+                writeLn(writer, fullCode, name, description);
+            }
+        }
+    }
+
+    /**
+     * @param knowledge knowledge to export as files
+     * @param timescale timescale of related indicators
+     * @throws IOException
+     */
+    private void writeIndicatorsCsvFiles(final Knowledge knowledge, final TimeScale timescale) throws IOException {
+        final String suffix = "-" + timescale.name().toLowerCase();
+        final Path path = Paths.get(outDir, "indicators" + suffix + ".csv");
+        LOGGER.trace(path);
+        try (BufferedWriter writer = new Utf8BufferedWriter(path)) {
+            writer.write("id;nom_en;nom_fr;description_en;description_fr;variables;param\u00e8tres;notes\n");
+            for (final CompositeIndicator comp : knowledge.getIndicators()) {
+                for (final Indicator ind : comp.getIndicators()) {
+                    writer.write(ind.getId());
+                    writer.write(";");
+                    writer.write(ind.getName("en"));
+                    writer.write(";");
+                    if (!ind.getName("fr").equals(ind.getName("en"))) {
+                        writer.write(ind.getName("fr"));
+                    }
+                    writer.write(";");
+                    final String description = ind.getDescription("fr");
+                    if (!description.equals(ind.getDescription("en"))) {
+                        writer.write(ind.getDescription("en"));
+                    }
+                    writer.write(";");
+                    writer.write(description);
+                    writer.write(";");
+                    final List<String> variables = new LinkedList<>();
+                    if (ind.getVariables() != null && !ind.getVariables().isEmpty()) {
+                        ind.getVariables().forEach(variable -> variables.add(variable.getName()));
+                        Collections.sort(variables);
+                        writer.write(String.join(", ", variables));
+                    }
+                    writer.write(";");
+                    final List<String> parameters = new LinkedList<>();
+                    if (ind.getParameters() != null
+                            && !ind.getParameters().isEmpty()) {
+                        ind.getParameters().forEach(param -> parameters.add(param.getId()));
+                        Collections.sort(parameters);
+                        writer.write(String.join(", ", parameters));
+                    }
+                    // affichage des références des notes de l'indicateurs
+                    writer.write(";");
+                    final List<String> notes = new LinkedList<>();
+                    if (ind.getNotes() != null && !ind.getNotes().isEmpty()) {
+                        ind.getNotes().forEach(note -> notes.add(note.getId()));
+                        writer.write(String.join(", ", notes));
+                    }
+                    writer.write("\n");
+                }
+            }
+        }
+    }
+
+    /**
+     * @param knowledge knowledge to export as files
+     * @param timescale timescale of related indicators
+     * @param locale    locale to write
+     * @throws IOException
+     */
+    private void writeIndicatorsMdFiles(final Knowledge knowledge, final TimeScale timescale, final Locale locale)
+            throws IOException {
+        final String languageCode = locale.getLanguage();
+        final String suffix = "-" + timescale.name().toLowerCase();
+        final Path mdPath = Paths.get(outDir, "indicators" + suffix + languageSep + languageCode + ".md");
+        final String bundleName = "fr.inrae.agroclim.indicators.resources.messages";
+        final I18n i18n = new I18n(bundleName, locale);
+
+        LOGGER.trace(mdPath);
+        try (BufferedWriter mdWriter = new Utf8BufferedWriter(mdPath);) {
+            final long nb = knowledge.getIndicators().stream().mapToInt(comp -> comp.getIndicators().size()).sum();
+            final String indicatorsVersion = fr.inrae.agroclim.indicators.resources.Version.getString("version");
+            final String frontMatter = """
+                                       ---
+                                       title: %s
+                                       description: %s
+                                       keywords: %s
+                                       date: %s
+                                       ---
+
+                                       """;
+            mdWriter.write(String.format(frontMatter,
+                    i18n.get("markdown.title." + timescale.name().toLowerCase()),
+                    i18n.get("markdown.description." + timescale.name().toLowerCase()),
+                    i18n.get("markdown.keywords"),
+                    created));
+            mdWriter.write(i18n.format("markdown.indicators.version", indicatorsVersion) + "\n\n"
+                    + "## " + i18n.format("markdown.indicators." + timescale.name().toLowerCase(), nb) + "\n");
+            writeLn(mdWriter, i18n.get("markdown.id"), i18n.get("markdown.name"), i18n.get("markdown.description"),
+                    i18n.get("markdown.variables"), i18n.get("markdown.parameters"),
+                    i18n.get("markdown.unit") + " [^1]", i18n.get("markdown.notes"));
+            mdWriter.write("|:---|:-----|:------------|:----------|:-----------|:-----------|:-----------|\n");
+
+            final Set<String> allVariables = new HashSet<>();
+            for (final CompositeIndicator comp : knowledge.getIndicators()) {
+                writeLn(mdWriter, "**" + comp.getName(languageCode) + "**");
+                for (final Indicator ind : comp.getIndicators()) {
+                    final List<String> variables = new LinkedList<>();
+                    if (ind.getVariables() != null
+                            && !ind.getVariables().isEmpty()) {
+                        ind.getVariables().forEach(variable -> variables.add(variable.getName()));
+                        Collections.sort(variables);
+                        allVariables.addAll(variables);
+                    }
+                    final List<String> parameters = new LinkedList<>();
+                    if (ind.getParameters() != null
+                            && !ind.getParameters().isEmpty()) {
+                        ind.getParameters().forEach(param -> parameters.add(param.getId()));
+                        Collections.sort(parameters);
+                    }
+                    String unit = "";
+                    if (ind.getUnit() != null) {
+                        List<LocalizedString> symbols = ind.getUnit().getSymbols();
+                        if (symbols != null && !symbols.isEmpty()) {
+                            unit = LocalizedString.getString(symbols, languageCode);
+                        }
+                        if (unit == null || unit.isBlank()) {
+                            final List<LocalizedString> labels = ind.getUnit().getLabels();
+                            if (labels != null && !labels.isEmpty()) {
+                                unit = LocalizedString.getString(labels, languageCode);
+                            }
+                        }
+                    }
+                    // affichage des références des notes de l'indicateurs
+                    final List<String> notes = new LinkedList<>();
+                    if (ind.getNotes() != null && !ind.getNotes().isEmpty()) {
+                        ind.getNotes().forEach(note -> {
+                            final String anchor = note.getId();
+                            notes.add("<a href='#" + anchor + "'>" + note.getId() + "</a>");
+                        });
+                    }
+                    writeLn(mdWriter, ind.getId(), ind.getName(languageCode),
+                            ind.getDescription(languageCode), String.join(", ", variables),
+                            String.join(", ", parameters), unit, String.join(", ", notes));
+                }
+            }
+
+            mdWriter.write("""
+
+                           ###\s""" + i18n.get("markdown.parameters") + "\n"
+                    + "| " + i18n.get("markdown.id") + " | " + i18n.get("markdown.description") + " |\n"
+                    + "|:---|:------------|\n");
+
+            for (final Parameter param : knowledge.getParameters()) {
+                writeLn(mdWriter, param.getId(), param.getDescription(languageCode));
+            }
+
+            mdWriter.write("""
+
+                           ###\s""" + i18n.get("markdown.variables") + "\n"
+                    + "| " + i18n.get("markdown.id") + " | " + i18n.get("markdown.description") + " |\n"
+                    + "|:---|:------------|\n");
+            allVariables.stream().sorted().forEach(variable -> {
+                try {
+                    mdWriter.write("| ");
+                    mdWriter.write(variable);
+                    mdWriter.write(" | ");
+                    mdWriter.write(i18n.get("Variable." + variable.toUpperCase() + ".description"));
+                    mdWriter.write(" |\n");
+                } catch (final IOException ex) {
+                    LOGGER.catching(ex);
+                }
+            });
+
+            // Ecriture de l'ensemble des notes présentes
+            if (knowledge.getNotes() != null && !knowledge.getNotes().isEmpty()) {
+                mdWriter.write("""
+
+                               ###\s""" + i18n.get("markdown.notes") + "\n"
+                        + "| " + i18n.get("markdown.reference") + " | " + i18n.get("markdown.description") + " |\n"
+                        + "|:---|:------------|\n");
+                for (final Note note : knowledge.getNotes()) {
+                    final String anchor;
+
+                    // si il s'agit d'un DOI, on affiche le lien
+                    final String id = note.getId();
+                    if (StringUtils.isDoiRef(id)) {
+                        anchor = String.format(
+                                "<a id=\"%1$s\" href=\"https://doi.org/%1$s\" target=\"_blank\">%1$s</a>",
+                                id);
+                    } else {
+                        anchor = String.format("<a id=\"%1$s\">%1$s</a>", id);
+                    }
+
+                    writeLn(mdWriter, anchor, note.getDescription());
+                }
+            }
+
+            mdWriter.write("\n\n[^1]: " + i18n.get("markdown.unit.footnote"));
+        }
+    }
+
+    /**
+     * Write a line in a table.
+     *
+     * @param writer the writer to user
+     * @param values strings to write
+     * @throws IOException when using BufferedWriter.write
+     */
+    private static void writeLn(final BufferedWriter writer,
+            final String... values) throws IOException {
+        final int nb = values.length;
+        writer.write("| ");
+        for (int i = 0; i < nb; i++) {
+            writer.write(values[i]);
+            if (i < nb - 1) {
+                writer.write(" | ");
+            }
+        }
+        writer.write(" |\n");
+    }
+
+    /**
+     * @param knowledge knowledge to export as files
+     * @param timescale timescale of related indicators
+     * @throws IOException
+     */
+    private void writeParametersCsvFiles(final Knowledge knowledge, final TimeScale timescale) throws IOException {
+        final String suffix = "-" + timescale.name().toLowerCase();
+        final Path paramPath = Paths.get(outDir, "parameters" + suffix + ".csv");
+        LOGGER.trace(paramPath);
+        try (BufferedWriter paramWriter = new Utf8BufferedWriter(paramPath)) {
+            paramWriter.write("id;description_fr;description_en\n");
+            for (final Parameter param : knowledge.getParameters()) {
+                paramWriter.write(param.getId());
+                paramWriter.write(";");
+                paramWriter.write(param.getDescription("fr"));
+                paramWriter.write(";");
+                paramWriter.write(param.getDescription("en"));
+                paramWriter.write("\n");
+            }
+        }
+    }
+}
diff --git a/src/main/java/fr/inrae/agroclim/indicators/exception/AbstractException.java b/src/main/java/fr/inrae/agroclim/indicators/exception/AbstractException.java
deleted file mode 100644
index 86b51eb9ba9e24ebffa1f7a6c1f1c0b8b158047a..0000000000000000000000000000000000000000
--- a/src/main/java/fr/inrae/agroclim/indicators/exception/AbstractException.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * This file is part of Indicators.
- *
- * Indicators is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Indicators is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Indicators. If not, see <https://www.gnu.org/licenses/>.
- */
-package fr.inrae.agroclim.indicators.exception;
-
-/**
- * Base class for FunctionalException and TechnicalException.
- */
-public abstract class AbstractException extends Exception {
-    /**
-     * UUID for Serializable.
-     */
-    private static final long serialVersionUID = 6030595237342400001L;
-
-    /**
-     * Root exception.
-     */
-    private final Exception rootException;
-
-    /**
-     * @param msg
-     *            exception message
-     */
-    protected AbstractException(final String msg) {
-        super(msg);
-        rootException = null;
-    }
-
-    /**
-     * @param msg
-     *            exception message
-     * @param e
-     *            root exception
-     */
-    protected AbstractException(final String msg, final Exception e) {
-        super(msg);
-        this.rootException = e;
-    }
-
-    /**
-     * @return Root exception.
-     */
-    public final Exception getRootException() {
-        return rootException;
-    }
-}
diff --git a/src/main/java/fr/inrae/agroclim/indicators/exception/ErrorCategory.java b/src/main/java/fr/inrae/agroclim/indicators/exception/ErrorCategory.java
index fa2ed983fe71539ab7af83e863c5990e56ad402f..201e2c1e256d7f788ce8ba34607cfbeda4393440 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/exception/ErrorCategory.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/exception/ErrorCategory.java
@@ -17,7 +17,7 @@ public interface ErrorCategory {
      * @return category description for the category code in I18n
      */
     default String getCategory(final I18n i18n) {
-        return i18n.get("error.category." + getCode());
+        return i18n.get("error.category." + getName().toLowerCase());
     }
 
     /**
@@ -25,4 +25,9 @@ public interface ErrorCategory {
      */
     String getCode();
 
+    /**
+     * @return short name, formatted as enum name.
+     */
+    String getName();
+
 }
diff --git a/src/main/java/fr/inrae/agroclim/indicators/exception/ErrorMessage.java b/src/main/java/fr/inrae/agroclim/indicators/exception/ErrorMessage.java
index bcf2e83bd77908c1d6509070e80691bb21c0752c..674639f3b54b5e5b046ed78df02fcf0688b226e4 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/exception/ErrorMessage.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/exception/ErrorMessage.java
@@ -25,6 +25,7 @@ import lombok.EqualsAndHashCode;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 import lombok.ToString;
+import org.json.JSONObject;
 
 /**
  * Error messages for the user.
@@ -115,28 +116,21 @@ public final class ErrorMessage implements Serializable {
      * @return JSON representation
      */
     public String toJSON() {
-        final StringBuilder sb = new StringBuilder();
-        sb.append("{");
-        sb.append("\"bundleName\": \"").append(bundleName).append("\", ");
-        sb.append("\"errorCode\": \"").append(type.getFullCode()).append("\", ");
-        sb.append("\"errorName\": \"").append(type.getName()).append("\", ");
-        sb.append("\"arguments\": [");
+        final JSONObject json = new JSONObject();
+        json.put("bundleName", bundleName);
+        if (type.getCategory() != null) {
+            final JSONObject cat = new JSONObject();
+            cat.put("code", type.getCategory().getCode());
+            cat.put("name", type.getCategory().getName());
+            json.put("category", cat);
+        }
+        final JSONObject error = new JSONObject();
+        error.put("code", type.getFullCode());
+        error.put("name", type.getName());
+        json.put("error", error);
         if (arguments != null) {
-            boolean first = true;
-            for (final Object arg : arguments) {
-                if (first) {
-                    first = false;
-                } else {
-                    sb.append(", ");
-                }
-                sb.append("\"")
-                .append(arg.toString()
-                        .replace("\"", "\\\"")
-                        .replace("'", "\\'"))
-                .append("\"");
-            }
+            json.put("arguments", arguments.stream().map(Object::toString).toList());
         }
-        sb.append("]}");
-        return sb.toString();
+        return json.toString(2);
     }
 }
diff --git a/src/main/java/fr/inrae/agroclim/indicators/exception/ErrorMessageException.java b/src/main/java/fr/inrae/agroclim/indicators/exception/ErrorMessageException.java
index 0d58a8afe20ca9fe15944b7949cbd99b1d98cbb1..524fb1acf3e87fa3dad5f09d5890eac94d79dc77 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/exception/ErrorMessageException.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/exception/ErrorMessageException.java
@@ -1,5 +1,6 @@
 package fr.inrae.agroclim.indicators.exception;
 
+import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 
 /**
@@ -8,17 +9,33 @@ import lombok.RequiredArgsConstructor;
  * @author omaury
  */
 @RequiredArgsConstructor
-public class ErrorMessageException extends Exception {
+public abstract class ErrorMessageException extends Exception {
     /**
      * UUID for Serializable.
      */
-    private static final long serialVersionUID = 6030595237342400004L;
+    private static final long serialVersionUID = 6030595237342400005L;
 
     /**
      * The object with details.
      */
+    @Getter
     private final ErrorMessage errorMessage;
 
+    /**
+     * Constructor.
+     *
+     * @param message The object with details.
+     * @param  cause the cause (which is saved for later retrieval by the
+     *         {@link #getCause()} method).  (A {@code null} value is
+     *         permitted, and indicates that the cause is nonexistent or
+     *         unknown.)
+
+     */
+    public ErrorMessageException(final ErrorMessage message, final Throwable cause) {
+        super("", cause);
+        this.errorMessage = message;
+    }
+
     @Override
     public final String getMessage() {
         return errorMessage.getMessage();
diff --git a/src/main/java/fr/inrae/agroclim/indicators/exception/FunctionalException.java b/src/main/java/fr/inrae/agroclim/indicators/exception/FunctionalException.java
deleted file mode 100644
index e36e2d1dd6a28b8b47c415666c9f38ab9ab5af88..0000000000000000000000000000000000000000
--- a/src/main/java/fr/inrae/agroclim/indicators/exception/FunctionalException.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * This file is part of Indicators.
- *
- * Indicators is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Indicators is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Indicators. If not, see <https://www.gnu.org/licenses/>.
- */
-package fr.inrae.agroclim.indicators.exception;
-
-/**
- * For Indicator, Evaluation and AggregationFunction.
- */
-public class FunctionalException extends AbstractException {
-    /**
-     * UUID for Serializable.
-     */
-    private static final long serialVersionUID = 6030595237342400002L;
-
-    /**
-     * @param msg
-     *            exception message
-     */
-    public FunctionalException(final String msg) {
-        super(msg);
-    }
-
-    /**
-     * @param msg
-     *            exception message
-     * @param e
-     *            root exception
-     */
-    public FunctionalException(final String msg, final Exception e) {
-        super(msg, e);
-    }
-}
diff --git a/src/main/java/fr/inrae/agroclim/indicators/exception/IndicatorErrorCategory.java b/src/main/java/fr/inrae/agroclim/indicators/exception/IndicatorsErrorCategory.java
similarity index 52%
rename from src/main/java/fr/inrae/agroclim/indicators/exception/IndicatorErrorCategory.java
rename to src/main/java/fr/inrae/agroclim/indicators/exception/IndicatorsErrorCategory.java
index 70ded7ac6978e9e37f1fdcd54f6738cca18b3e37..1e25aa3a3a4e9a61f226325620f441cf8752e534 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/exception/IndicatorErrorCategory.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/exception/IndicatorsErrorCategory.java
@@ -10,18 +10,26 @@ import lombok.RequiredArgsConstructor;
 /**
  * Category for error types in the Indicators library.
  *
- * Last change $Date$
+ * Last change $Date: 2023-03-16 17:36:45 +0100 (jeu. 16 mars 2023) $
  *
  * @author omaury
- * @author $Author$
- * @version $Revision$
+ * @author $Author: omaury $
+ * @version $Revision: 644 $
  */
 @RequiredArgsConstructor
-public enum IndicatorErrorCategory implements ErrorCategory {
+public enum IndicatorsErrorCategory implements ErrorCategory {
+    /**
+     * While loading XML.
+     */
+    XML("IND01"),
     /**
      * For {@link ResourceManager}.
      */
-    RESOURCES("IND01");
+    RESOURCES("IND02"),
+    /**
+     * While {@link Indicator#compute()}.
+     */
+    COMPUTATION("IND03");
 
     /**
      * Category code: prefix for {@link ErrorType#getFullCode()}.
@@ -29,4 +37,9 @@ public enum IndicatorErrorCategory implements ErrorCategory {
     @Getter
     private final String code;
 
+    @Override
+    public String getName() {
+        return name();
+    }
+
 }
diff --git a/src/main/java/fr/inrae/agroclim/indicators/exception/IndicatorsException.java b/src/main/java/fr/inrae/agroclim/indicators/exception/IndicatorsException.java
new file mode 100644
index 0000000000000000000000000000000000000000..d6f4bf2a241e3b949219ab63173ec1928dcf7f65
--- /dev/null
+++ b/src/main/java/fr/inrae/agroclim/indicators/exception/IndicatorsException.java
@@ -0,0 +1,45 @@
+package fr.inrae.agroclim.indicators.exception;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * An exception with {@link ErrorMessage} to describe the error in the Indicators library to the user.
+ *
+ * @author Olivier Maury
+ */
+public class IndicatorsException extends ErrorMessageException {
+    /**
+     * Path of .property resource.
+     */
+    private static final String BUNDLE_NAME = "fr.inrae.agroclim.indicators.resources.messages";
+
+    /**
+     * UUID for Serializable.
+     */
+    private static final long serialVersionUID = 6030595237342400006L;
+
+    /**
+     * Constructor.
+     *
+     * @param errorType Error type.
+     * @param arguments Arguments for the message.
+     */
+    public IndicatorsException(final ErrorType errorType, final Serializable... arguments) {
+        super(new ErrorMessage(BUNDLE_NAME, errorType, List.of(arguments)));
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param errorType Error type.
+     * @param arguments Arguments for the message.
+     * @param  cause the cause (which is saved for later retrieval by the
+     *         {@link #getCause()} method).  (A {@code null} value is
+     *         permitted, and indicates that the cause is nonexistent or
+     *         unknown.)
+     */
+    public IndicatorsException(final ErrorType errorType, final Throwable cause, final Serializable... arguments) {
+        super(new ErrorMessage(BUNDLE_NAME, errorType, List.of(arguments)), cause);
+    }
+}
diff --git a/src/main/java/fr/inrae/agroclim/indicators/exception/ThrowingToDoubleFunction.java b/src/main/java/fr/inrae/agroclim/indicators/exception/ThrowingToDoubleFunction.java
new file mode 100644
index 0000000000000000000000000000000000000000..b96cf2c46ac127bf55360471d74f45f7f0dd0a5a
--- /dev/null
+++ b/src/main/java/fr/inrae/agroclim/indicators/exception/ThrowingToDoubleFunction.java
@@ -0,0 +1,40 @@
+package fr.inrae.agroclim.indicators.exception;
+
+import java.util.function.ToDoubleFunction;
+
+/**
+ * ToDoubleFunction to handle exceptions.
+ *
+ * Inspired by https://www.baeldung.com/java-lambda-exceptions.
+ *
+ * @author Olivier Maury
+ * @param <T> the type of the input to the function
+ * @param <E> the type of the thrown exception
+ */
+@FunctionalInterface
+public interface ThrowingToDoubleFunction<T, E extends Exception> {
+
+    /**
+     * Applies this function to the given argument.
+     *
+     * @param value the function argument
+     * @return the function result
+     * @throws E exception
+     */
+    double applyAsDouble(T value) throws E;
+
+    /**
+     * @param <U> the type of the input to the function
+     * @param throwingFunction the function
+     * @return function which throws checked exception
+     */
+    static <U> ToDoubleFunction wrap(final ThrowingToDoubleFunction<U, Exception> throwingFunction) {
+        return (ToDoubleFunction) (Object value) -> {
+            try {
+                return throwingFunction.applyAsDouble((U) value);
+            } catch (final Exception ex) {
+                throw new RuntimeException(ex);
+            }
+        };
+    }
+}
diff --git a/src/main/java/fr/inrae/agroclim/indicators/exception/type/CommonErrorType.java b/src/main/java/fr/inrae/agroclim/indicators/exception/type/CommonErrorType.java
new file mode 100644
index 0000000000000000000000000000000000000000..5d936c0b1e564f96ad4e68a4874fad61e2b2511d
--- /dev/null
+++ b/src/main/java/fr/inrae/agroclim/indicators/exception/type/CommonErrorType.java
@@ -0,0 +1,50 @@
+package fr.inrae.agroclim.indicators.exception.type;
+
+import fr.inrae.agroclim.indicators.exception.ErrorType;
+import java.util.StringJoiner;
+
+/**
+ * Common methods to implements ErrorType.
+ *
+ * @author Olivier Maury
+ */
+public interface CommonErrorType extends ErrorType {
+    /**
+     * @return partial I18n key for messages.properties
+     */
+    private String getShortKey() {
+        return getName().toLowerCase().replace("_", ".");
+    }
+
+    /**
+     * @return parent error
+     */
+    CommonErrorType getParent();
+
+    /**
+     * @return error name (as {@link Enum#name()}).
+     */
+    String name();
+
+    /**
+     * @return Key for Resource/I18nResource.
+     */
+    @Override
+    default String getI18nKey() {
+        final String cat = getCategory().getName().toLowerCase();
+        final StringJoiner sj = new StringJoiner(".", "error.", "");
+        if (!getShortKey().startsWith(cat)) {
+            sj.add(cat);
+        }
+        if (getParent() != null && !getShortKey().startsWith(getParent().getShortKey())) {
+            sj.add(getParent().getShortKey());
+        }
+        sj.add(getShortKey());
+        return sj.toString();
+    }
+
+    @Override
+    default String getName() {
+        return name();
+    }
+}
diff --git a/src/main/java/fr/inrae/agroclim/indicators/exception/type/ComputationErrorType.java b/src/main/java/fr/inrae/agroclim/indicators/exception/type/ComputationErrorType.java
new file mode 100644
index 0000000000000000000000000000000000000000..3c3a79d06d6a3b2c902b7a87e06ceb1d38aec43b
--- /dev/null
+++ b/src/main/java/fr/inrae/agroclim/indicators/exception/type/ComputationErrorType.java
@@ -0,0 +1,167 @@
+package fr.inrae.agroclim.indicators.exception.type;
+
+import fr.inrae.agroclim.indicators.exception.ErrorCategory;
+import fr.inrae.agroclim.indicators.exception.IndicatorsErrorCategory;
+import fr.inrae.agroclim.indicators.model.indicator.CompositeIndicator;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * Keys from messages.properties used to warn about errors in {@link Indicator#compute()}.
+ *
+ * @author Olivier Maury
+ */
+@RequiredArgsConstructor
+public enum ComputationErrorType implements CommonErrorType {
+    /**
+     * Definition of the indicator is not good.
+     *
+     * Parameter 1 : explaination.
+     */
+    WRONG_DEFINITION(null, "001"),
+    /**
+     * When input causes an exception.
+     *
+     * No parameter.
+     */
+    INPUT(null, "100"),
+    /**
+     * When an indicator in a {@link CompositeIndicator} fails to compute.
+     *
+     * Parameter 1 : indicator id.
+     */
+    COMPOSITE_COMPUTATION(INPUT, "101"),
+    /**
+     * Criteria should be NoCriteria or SimpleCriteria.
+     *
+     * Parameter 1 : indicator id.
+     */
+    CRITERIA_ISNT_NOCRITERIA_SIMPLECRITERIA(WRONG_DEFINITION, "002"),
+    /**
+     * Criteria should not be null.
+     *
+     * No parameter.
+     */
+    CRITERIA_NULL(WRONG_DEFINITION, "003"),
+    /**
+     * Daily data must not be null.
+     *
+     * No parameter.
+     */
+    DATA_NULL(INPUT, "113"),
+    /**
+     * Dividend indicator should never be null.
+     *
+     * No parameter.
+     */
+    QUOTIENT_DIVIDEND_NULL(WRONG_DEFINITION, "005"),
+    /**
+     * Computation of dividend failed.
+     *
+     * Parameter 1 : indicator id.
+     */
+    QUOTIENT_DIVIDEND_EXCEPTION(INPUT, "110"),
+    /**
+     * Computation of divisor failed.
+     *
+     * Parameter 1 : indicator id.
+     */
+    QUOTIENT_DIVISOR_EXCEPTION(INPUT, "111"),
+    /**
+     * Cannot compute quotient as result of divisor is zero.
+     *
+     * Parameter 1 : indicator id.
+     */
+    QUOTIENT_DIVISOR_ZERO(INPUT, "112"),
+    /**
+     * Divisor indicator should never be null.
+     *
+     * No parameter.
+     */
+    QUOTIENT_DIVISOR_NULL(WRONG_DEFINITION, "006"),
+    /**
+     * Execution of formula failed.
+     *
+     * parameter 1 : expression
+     */
+    FORMULA(null, "200"),
+    /**
+     * Agregation to apply must not be null.
+     *
+     * No parameter.
+     */
+    FORMULA_AGGREGATION_NULL(FORMULA, "211"),
+    /**
+     * Formula expression must not be null.
+     *
+     * No parameter.
+     */
+    FORMULA_EXPRESSION_NULL(FORMULA, "221"),
+    /**
+     * Formula expression must not be blank.
+     *
+     * No parameter.
+     */
+    FORMULA_EXPRESSION_BLANK(FORMULA, "222"),
+    /**
+     * Parsing error due to missing parenthesis.
+     *
+     * parameter 1 : expression
+     */
+    FORMULA_EXPRESSION_PARENTHESIS(FORMULA, "223"),
+    /**
+     * Parsing error.
+     *
+     * parameter 1 : expression
+     */
+    FORMULA_EXPRESSION_PARSING(FORMULA, "224"),
+    /**
+     * Unknown function.
+     *
+     * parameter 1 : expression
+     * parameter 2 : function
+     */
+    FORMULA_FUNCTION_UNKNOWN(FORMULA, "231"),
+    /**
+     * Unknown variable.
+     *
+     * parameter 1 : expression
+     * parameter 2 : variable
+     */
+    FORMULA_VARIABLE_UNDEFINED(FORMULA, "241"),
+    /**
+     * Threshold must not be null.
+     *
+     * No parameter.
+     */
+    THRESHOLD_NULL(WRONG_DEFINITION, "004"),
+    /**
+     * Variable name must not be null.
+     */
+    VARIABLE_NAME_NULL(WRONG_DEFINITION, "007"),
+    /**
+     * Value for the variable must not be null.
+     *
+     * parameter 1 : date
+     * parameter 2 : variable name
+     */
+    VARIABLE_VALUE_NULL(INPUT, "114");
+
+    /**
+     * Parent refers to the resource part.
+     */
+    @Getter
+    private final ComputationErrorType parent;
+
+    /**
+     * Subcode for the error.
+     */
+    @Getter
+    private final String subCode;
+
+    @Override
+    public ErrorCategory getCategory() {
+        return IndicatorsErrorCategory.COMPUTATION;
+    }
+
+}
diff --git a/src/main/java/fr/inrae/agroclim/indicators/exception/type/ResourceErrorType.java b/src/main/java/fr/inrae/agroclim/indicators/exception/type/ResourceErrorType.java
new file mode 100644
index 0000000000000000000000000000000000000000..6be38b76df132563dd4e3ed8be6ee643a0a9d98f
--- /dev/null
+++ b/src/main/java/fr/inrae/agroclim/indicators/exception/type/ResourceErrorType.java
@@ -0,0 +1,134 @@
+package fr.inrae.agroclim.indicators.exception.type;
+
+import fr.inrae.agroclim.indicators.exception.ErrorCategory;
+import fr.inrae.agroclim.indicators.exception.IndicatorsErrorCategory;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * Keys from messages.properties used to warn about errors in {@link ResourceManager}.
+ *
+ * @author Olivier Maury
+ */
+@RequiredArgsConstructor
+public enum ResourceErrorType implements CommonErrorType {
+    /**
+     * Climate, topic.
+     *
+     * No parameter.
+     */
+    CLIMATE("100", null),
+    /**
+     * No climate data.
+     *
+     * No parameter.
+     */
+    CLIMATE_EMPTY("110", CLIMATE),
+    /**
+     * No climate data for the phase.
+     *
+     * parameter 0 : start stage name parameter 1 : end stage name parameter 2 : start stage day parameter 3 : end stage
+     * day parameter 4 : year parameter 5 : first available day parameter 6 ! last available day
+     */
+    CLIMATE_EMPTY_FOR_PHASE("111", CLIMATE_EMPTY),
+    /**
+     * Not enough data.
+     *
+     * No parameter.
+     */
+    CLIMATE_SIZE_WRONG("101", CLIMATE),
+    /**
+     * Years of climate, topic.
+     *
+     * No parameter.
+     */
+    CLIMATE_YEARS("120", null),
+    /**
+     * No years of climate.
+     *
+     * No parameter.
+     */
+    CLIMATE_YEARS_EMPTY("121", CLIMATE_YEARS),
+    /**
+     * Not enough data.
+     *
+     * No parameter.
+     */
+    CLIMATE_YEARS_MISSING("122", CLIMATE_YEARS),
+    /**
+     * Phenology, topic.
+     *
+     * No parameter.
+     */
+    PHENO("200", null),
+    /**
+     * No phenological data.
+     *
+     * No parameter.
+     */
+    PHENO_EMPTY("201", PHENO),
+    /**
+     * Years of phenology, topic.
+     *
+     * No parameter.
+     */
+    PHENO_YEARS("210", PHENO),
+    /**
+     * No years of phenology.
+     *
+     * No parameter.
+     */
+    PHENO_YEARS_EMPTY("211", PHENO_YEARS),
+    /**
+     * Setting not set.
+     *
+     * No parameter.
+     */
+    RESOURCES_CROPDEVELOPMENT_YEARS("001", null),
+    /**
+     * Soil, topic.
+     *
+     * No parameter.
+     */
+    SOIL("300", null),
+    /**
+     * Not enough data.
+     *
+     * No parameter.
+     */
+    SOIL_SIZE_WRONG("301", SOIL),
+    /**
+     * Variables, topic.
+     *
+     * No parameter.
+     */
+    VARIABLES("400", null),
+    /**
+     * No variable.
+     *
+     * No parameter.
+     */
+    VARIABLES_EMPTY("401", VARIABLES),
+    /**
+     * No variale.
+     *
+     * No parameter.
+     */
+    VARIABLES_MISSING("402", VARIABLES);
+    /**
+     * Subcode for the error.
+     */
+    @Getter
+    private final String subCode;
+    /**
+     * Parent refers to the resource part.
+     */
+    @Getter
+    private final ResourceErrorType parent;
+
+    @Override
+    public ErrorCategory getCategory() {
+        return IndicatorsErrorCategory.RESOURCES;
+    }
+
+}
diff --git a/src/main/java/fr/inrae/agroclim/indicators/exception/type/XmlErrorType.java b/src/main/java/fr/inrae/agroclim/indicators/exception/type/XmlErrorType.java
new file mode 100644
index 0000000000000000000000000000000000000000..aba636aa4930ab5671fe1dd16a0a8520a16a92d1
--- /dev/null
+++ b/src/main/java/fr/inrae/agroclim/indicators/exception/type/XmlErrorType.java
@@ -0,0 +1,49 @@
+package fr.inrae.agroclim.indicators.exception.type;
+
+import fr.inrae.agroclim.indicators.exception.ErrorCategory;
+import fr.inrae.agroclim.indicators.exception.IndicatorsErrorCategory;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * Keys from messages.properties used to warn about errors in {@link Indicator#compute()}.
+ *
+ * @author Olivier Maury
+ */
+@RequiredArgsConstructor
+public enum XmlErrorType implements CommonErrorType {
+    /**
+     * XML file not found.
+     *
+     * Parameter 0 : file path
+     */
+    FILE_NOT_FOUND(null, "001"),
+    /**
+     * Unable to load.
+     *
+     * No parameter.
+     */
+    UNABLE_TO_LOAD(null, "002"),
+    /**
+     * Unable to serialize.
+     *
+     * No parameter.
+     */
+    UNABLE_TO_SERIALIZE(null, "003");
+    /**
+     * Parent refers to the resource part.
+     */
+    @Getter
+    private final XmlErrorType parent;
+
+    /**
+     * Subcode for the error.
+     */
+    @Getter
+    private final String subCode;
+
+    @Override
+    public ErrorCategory getCategory() {
+        return IndicatorsErrorCategory.XML;
+    }
+}
diff --git a/src/main/java/fr/inrae/agroclim/indicators/exception/TechnicalException.java b/src/main/java/fr/inrae/agroclim/indicators/exception/type/package-info.java
similarity index 53%
rename from src/main/java/fr/inrae/agroclim/indicators/exception/TechnicalException.java
rename to src/main/java/fr/inrae/agroclim/indicators/exception/type/package-info.java
index 6915c8af0f8a22553c5ba340c47a333eb4ded028..647131bfe0e1b2c0d9ff930902b76e0925997871 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/exception/TechnicalException.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/exception/type/package-info.java
@@ -1,43 +1,20 @@
-/**
- * This file is part of Indicators.
- *
- * Indicators is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Indicators is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Indicators. If not, see <https://www.gnu.org/licenses/>.
- */
-package fr.inrae.agroclim.indicators.exception;
-
-/**
- * For XMLUtils.
- */
-public class TechnicalException extends AbstractException {
-
-    /**
-     * UUID for Serializable.
-     */
-    private static final long serialVersionUID = 6030595237342400003L;
-
-    /**
-     * @param msg exception message
-     */
-    public TechnicalException(final String msg) {
-        super(msg);
-    }
-
-    /**
-     * @param msg exception message
-     * @param e root exception
-     */
-    public TechnicalException(final String msg, final Exception e) {
-        super(msg, e);
-    }
-}
+/**
+ * This file is part of Indicators.
+ *
+ * Indicators is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Indicators is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Indicators. If not, see <https://www.gnu.org/licenses/>.
+ */
+/**
+ * {@link ErrorType} implementations for error handling.
+ */
+package fr.inrae.agroclim.indicators.exception.type;
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java b/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
index 57d1497ed170c6cb0707346a54176f32da739ad7..2648642cafecb4e3f2949f153f330eb5bbec0beb 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/Evaluation.java
@@ -26,14 +26,13 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
+import fr.inrae.agroclim.indicators.exception.type.ResourceErrorType;
 import fr.inrae.agroclim.indicators.model.data.ResourceManager;
 import fr.inrae.agroclim.indicators.model.data.Variable;
 import fr.inrae.agroclim.indicators.model.data.Variable.Type;
@@ -53,7 +52,6 @@ import fr.inrae.agroclim.indicators.model.indicator.listener.IndicatorEvent;
 import fr.inrae.agroclim.indicators.model.result.EvaluationResult;
 import fr.inrae.agroclim.indicators.model.result.IndicatorResult;
 import fr.inrae.agroclim.indicators.model.result.PhaseResult;
-import fr.inrae.agroclim.indicators.resources.I18n;
 import fr.inrae.agroclim.indicators.resources.Messages;
 import fr.inrae.agroclim.indicators.util.DateUtils;
 import fr.inrae.agroclim.indicators.util.StageUtils;
@@ -302,12 +300,10 @@ public final class Evaluation extends CompositeIndicator {
     /**
      * Compute indicator results.
      *
-     * @throws TechnicalException
-     *             from Indicator.compute()
-     * @throws FunctionalException
+     * @throws IndicatorsException
      *             from Indicator.compute()
      */
-    public void compute() throws TechnicalException, FunctionalException {
+    public void compute() throws IndicatorsException {
         LOGGER.trace("start computing evaluation \"" + getName() + "\"");
         fireIndicatorEvent(IndicatorEvent.Type.COMPUTE_START.event(this));
 
@@ -319,12 +315,10 @@ public final class Evaluation extends CompositeIndicator {
             throw new RuntimeException("Phase list is empty!");
         }
         if (resourceManager.getClimaticResource().isEmpty()) {
-            throw new FunctionalException(
-                    "There is not any climatic daily data!");
+            throw new IndicatorsException(ResourceErrorType.CLIMATE_EMPTY);
         }
         if (resourceManager.getPhenologicalResource().isEmpty()) {
-            throw new FunctionalException(
-                    "There is not any phenological data !");
+            throw new IndicatorsException(ResourceErrorType.PHENO_EMPTY);
         }
 
         clearResults();
@@ -408,26 +402,21 @@ public final class Evaluation extends CompositeIndicator {
                 final ClimaticResource climaticData = resourceManager
                         .getClimaticResource().getClimaticDataByPhaseAndYear(startDate, endDate);
                 if (climaticData.isEmpty()) {
-                    final I18n i18n = new I18n("fr.inrae.agroclim.indicators.resources.messages", Locale.getDefault());
-                    final StringBuilder sb = new StringBuilder();
-                    sb.append(i18n.format("error.climate.missing",
-                            startStageName, endStageName, startStage, endStage, dateYear));
                     if (resourceManager.getClimaticResource().isEmpty()) {
-                        sb.append(i18n.format("error.climate.no.data"));
-                        throw new RuntimeException(sb.toString());
-                    } else {
-                        final int yearToSearch = dateYear;
-                        final List<ClimaticDailyData> ddataList = resourceManager.getClimaticResource().getData()
-                                .stream().filter(f -> f.getYear() == yearToSearch).collect(Collectors.toList());
-
-                        final ClimaticDailyData startData = ddataList.get(0);
-                        final ClimaticDailyData endData = ddataList.get(ddataList.size() - 1);
-
-                        sb.append(i18n.format("error.climate.dates", dateYear,
-                                startData.getYear() + "-" + startData.getMonth() + "-" + startData.getDay(),
-                                endData.getYear() + "-" + endData.getMonth() + "-" + endData.getDay()));
+                        throw new IndicatorsException(ResourceErrorType.CLIMATE_EMPTY);
                     }
-                    throw new FunctionalException(sb.toString());
+                    final int yearToSearch = dateYear;
+                    final List<ClimaticDailyData> ddataList = resourceManager.getClimaticResource().getData()
+                            .stream().filter(f -> f.getYear() == yearToSearch).collect(Collectors.toList());
+
+                    final ClimaticDailyData startData = ddataList.get(0);
+                    final ClimaticDailyData endData = ddataList.get(ddataList.size() - 1);
+
+                    throw new IndicatorsException(ResourceErrorType.CLIMATE_EMPTY_FOR_PHASE,
+                            startStageName, endStageName, startStage, endStage, dateYear, //
+                            startData.getYear() + "-" + startData.getMonth() + "-" + startData.getDay(), //
+                            endData.getYear() + "-" + endData.getMonth() + "-" + endData.getDay()
+                    );
                 }
                 /* #9451 - En cas de données manquantes, on passe à l'année suivante
                  * Par défaut, le résultat de l'évaluation du couple phase/année vaut NA (null).
@@ -459,9 +448,9 @@ public final class Evaluation extends CompositeIndicator {
      * Pour chaque année, Pour chaque phase, valeurs.onIndicatorAdd(valeur phase
      * p, année n); Fin pour aggregation(valeurs); Fin pour;
      *
-     * @throws FunctionalException raised by AggregationFunction.aggregate()
+     * @throws IndicatorsException raised by AggregationFunction.aggregate()
      */
-    private void computeFaisability() throws FunctionalException {
+    private void computeFaisability() throws IndicatorsException {
         LOGGER.traceEntry();
         if (getType() == EvaluationType.WITHOUT_AGGREGATION) {
             return;
@@ -831,7 +820,7 @@ public final class Evaluation extends CompositeIndicator {
         getPhases().forEach(phase -> values.put(phase.getId(), 1.));
         try {
             getAggregationFunction().aggregate(values);
-        } catch (final FunctionalException ex) {
+        } catch (final IndicatorsException ex) {
             LOGGER.info("Invalid aggregation: {}", ex.getLocalizedMessage());
             return false;
         }
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/EvaluationSettings.java b/src/main/java/fr/inrae/agroclim/indicators/model/EvaluationSettings.java
index 47934cd8ab103fda26421ce1020be8795aa5d3d0..1c26ae7c088b54c58835ff5ae2d2640686eb7ae6 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/EvaluationSettings.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/EvaluationSettings.java
@@ -16,6 +16,7 @@
  */
 package fr.inrae.agroclim.indicators.model;
 
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import java.io.Serializable;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -35,7 +36,6 @@ import javax.xml.bind.annotation.XmlTransient;
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
 import fr.inrae.agroclim.indicators.model.criteria.ComparisonCriteria;
 import fr.inrae.agroclim.indicators.model.criteria.CompositeCriteria;
 import fr.inrae.agroclim.indicators.model.criteria.FormulaCriteria;
@@ -289,13 +289,10 @@ public final class EvaluationSettings implements Cloneable, Serializable {
 
     /**
      * Load Knowledge.
+     * @throws IndicatorsException while loading Knowledge
      */
-    public void initializeKnowledge() {
-        try {
-            knowledge = Knowledge.load(timescale);
-        } catch (final TechnicalException e) {
-            LOGGER.error("Loading Knowledge failed! {}", e);
-        }
+    public void initializeKnowledge() throws IndicatorsException {
+        knowledge = Knowledge.load(timescale);
     }
 
     /**
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/JEXLFormula.java b/src/main/java/fr/inrae/agroclim/indicators/model/JEXLFormula.java
index c3c52afe2c981056d823590744152013265eca3b..4eb2e1ef1939d8c98565ff345be2fa78458c5608 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/JEXLFormula.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/JEXLFormula.java
@@ -20,7 +20,6 @@ package fr.inrae.agroclim.indicators.model;
 
 import java.util.HashSet;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -35,11 +34,11 @@ import org.apache.commons.jexl3.JexlFeatures;
 import org.apache.commons.jexl3.JexlScript;
 import org.apache.commons.jexl3.MapContext;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
+import fr.inrae.agroclim.indicators.exception.type.ComputationErrorType;
 import fr.inrae.agroclim.indicators.model.criteria.FormulaCriteria;
 import fr.inrae.agroclim.indicators.model.data.Variable;
 import fr.inrae.agroclim.indicators.model.function.aggregation.MathMethod;
-import fr.inrae.agroclim.indicators.resources.I18n;
 import fr.inrae.agroclim.indicators.util.StringUtils;
 import lombok.Getter;
 import lombok.Setter;
@@ -55,11 +54,6 @@ import org.apache.commons.jexl3.introspection.JexlPermissions;
  */
 public class JEXLFormula {
 
-    /**
-     * I18N key prefix for error messages.
-     */
-    private static final String ERROR_PREFIX = JEXLFormula.class.getSimpleName() + ".error.";
-
     /**
      * org.apache.commons.jexl3.JexlEngine .
      */
@@ -100,18 +94,6 @@ public class JEXLFormula {
                 .create();
     }
 
-    /**
-     * Return error message with inlined arguments.
-     *
-     * @param key message key, without prefix
-     * @param messageArguments arguments for the message.
-     * @return message with arguments
-     */
-    private String errorMessage(final String key, final Object... messageArguments) {
-        final I18n res = new I18n("fr.inrae.agroclim.indicators.resources.messages", Locale.getDefault());
-        return res.format(ERROR_PREFIX + key, messageArguments);
-    }
-
     /**
      * Evaluates the expression with the variables.
      *
@@ -119,15 +101,15 @@ public class JEXLFormula {
      * @param values variable name ⮕ value
      * @param clazz result class
      * @return The result of this evaluation
-     * @throws fr.inrae.agroclim.indicators.exception.FunctionalException on any error
+     * @throws IndicatorsException on any error
      */
     @SuppressWarnings("unchecked")
-    public <T> T evaluate(final Map<String, Double> values, final Class<T> clazz) throws FunctionalException {
+    public <T> T evaluate(final Map<String, Double> values, final Class<T> clazz) throws IndicatorsException {
         try {
             if (getExpression() == null) {
-                throw new FunctionalException(errorMessage("expression.null"));
-            } else if (getExpression().isEmpty()) {
-                throw new FunctionalException(errorMessage("expression.empty"));
+                throw new IndicatorsException(ComputationErrorType.FORMULA_EXPRESSION_NULL);
+            } else if (getExpression().isBlank()) {
+                throw new IndicatorsException(ComputationErrorType.FORMULA_EXPRESSION_BLANK);
             }
 
             final JexlExpression exp = jexl.createExpression(getExpression());
@@ -141,18 +123,18 @@ public class JEXLFormula {
                 context.set(text, values.get(key));
             });
             return (T) exp.evaluate(context);
-        } catch (final JexlException.Variable variable) {
-            throw new FunctionalException(errorMessage("variable.undefined", getExpression(), variable.getVariable()),
-                    variable);
-        } catch (final JexlException.Method method) {
-            throw new FunctionalException(errorMessage("function.unknown", getExpression(), method.getMethod()),
-                    method);
-        } catch (final JexlException.Parsing parsing) {
-            throw new FunctionalException(errorMessage("expression.parsing", getExpression()), parsing);
-        } catch (final JexlException.Tokenization token) {
-            throw new FunctionalException(errorMessage("expression.parenthesis", getExpression()), token);
-        } catch (final JexlException e) {
-            throw new FunctionalException(errorMessage("execution", getExpression()) + e.getLocalizedMessage(), e);
+        } catch (final JexlException.Variable ex) {
+            throw new IndicatorsException(ComputationErrorType.FORMULA_VARIABLE_UNDEFINED, ex, getExpression(),
+                    ex.getVariable());
+        } catch (final JexlException.Method ex) {
+            throw new IndicatorsException(ComputationErrorType.FORMULA_FUNCTION_UNKNOWN, ex, getExpression(),
+                    ex.getMethod());
+        } catch (final JexlException.Parsing ex) {
+            throw new IndicatorsException(ComputationErrorType.FORMULA_EXPRESSION_PARSING, ex, getExpression());
+        } catch (final JexlException.Tokenization ex) {
+            throw new IndicatorsException(ComputationErrorType.FORMULA_EXPRESSION_PARENTHESIS, ex, getExpression());
+        } catch (final JexlException ex) {
+            throw new IndicatorsException(ComputationErrorType.FORMULA, ex, getExpression());
         }
     }
 
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/Knowledge.java b/src/main/java/fr/inrae/agroclim/indicators/model/Knowledge.java
index 44ed7a054ccaf5805a287c25e0aaa4c75e706bd1..637484e0036340ba9a9afe389ee2ff229ceb4eda 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/Knowledge.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/Knowledge.java
@@ -16,24 +16,12 @@
  */
 package fr.inrae.agroclim.indicators.model;
 
-import java.io.BufferedWriter;
-import java.io.IOException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import java.io.InputStream;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
 import java.util.EnumMap;
-import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
-import java.util.Set;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -41,7 +29,6 @@ import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
 import javax.xml.bind.annotation.XmlRootElement;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
 import fr.inrae.agroclim.indicators.model.criteria.ComparisonCriteria;
 import fr.inrae.agroclim.indicators.model.criteria.CompositeCriteria;
 import fr.inrae.agroclim.indicators.model.criteria.FormulaCriteria;
@@ -75,9 +62,6 @@ import fr.inrae.agroclim.indicators.model.indicator.Quotient;
 import fr.inrae.agroclim.indicators.model.indicator.SimpleIndicator;
 import fr.inrae.agroclim.indicators.model.indicator.Sum;
 import fr.inrae.agroclim.indicators.model.indicator.Tamm;
-import fr.inrae.agroclim.indicators.resources.I18n;
-import fr.inrae.agroclim.indicators.util.StringUtils;
-import fr.inrae.agroclim.indicators.util.Utf8BufferedWriter;
 import fr.inrae.agroclim.indicators.xml.XMLUtil;
 import javax.xml.bind.annotation.XmlAttribute;
 import lombok.Getter;
@@ -89,10 +73,7 @@ import lombok.extern.log4j.Log4j2;
 /**
  * knowledge.xml.
  *
- * Last change $Date$
- *
- * @author $Author$
- * @version $Revision$
+ * @author Olivier Maury
  */
 @XmlRootElement
 @XmlAccessorType(XmlAccessType.FIELD)
@@ -152,9 +133,9 @@ public final class Knowledge implements Cloneable {
      * Deserialize Knowledge from embedded file for daily indicators.
      *
      * @return deserialized Knowledge
-     * @throws TechnicalException error while loading knowledge
+     * @throws IndicatorsException error while loading knowledge
      */
-    public static Knowledge load() throws TechnicalException {
+    public static Knowledge load() throws IndicatorsException {
         return load(TimeScale.DAILY);
     }
 
@@ -164,11 +145,10 @@ public final class Knowledge implements Cloneable {
      * @param stream
      *            input stream
      * @return deserialized Knowledge
-     * @throws TechnicalException
+     * @throws IndicatorsException
      *             error while loading knowledge
      */
-    private static Knowledge load(final InputStream stream)
-            throws TechnicalException {
+    private static Knowledge load(final InputStream stream) throws IndicatorsException {
         try {
             final Knowledge knowledge = (Knowledge) XMLUtil.loadResource(stream,
                     CLASSES_FOR_JAXB);
@@ -179,7 +159,7 @@ public final class Knowledge implements Cloneable {
             knowledge.culturalPractices.forEach(ind ->
             ind.setIndicatorCategory(IndicatorCategory.CULTURAL_PRATICES));
             return knowledge;
-        } catch (final TechnicalException ex) {
+        } catch (final IndicatorsException ex) {
             LOGGER.error(ex);
             throw ex;
         }
@@ -190,258 +170,13 @@ public final class Knowledge implements Cloneable {
      *
      * @param timescale timescale of indicators
      * @return deserialized Knowledge
-     * @throws TechnicalException error while loading knowledge
+     * @throws IndicatorsException error while loading knowledge
      */
-    public static Knowledge load(final TimeScale timescale) throws TechnicalException {
+    public static Knowledge load(final TimeScale timescale) throws IndicatorsException {
         final InputStream stream = Knowledge.class.getResourceAsStream(RESOURCES.get(timescale));
         return load(stream);
     }
 
-    /**
-     * @param args not used
-     * @throws TechnicalException while loading knowledge
-     * @throws java.io.IOException while writing file
-     */
-    public static void main(final String[] args) throws TechnicalException,
-    IOException {
-        for (final TimeScale timescale: TimeScale.values()) {
-            LOGGER.trace("Generating files for {}...", timescale);
-            final Knowledge knowledge = Knowledge.load(timescale);
-            for (final Locale l: Arrays.asList(Locale.ENGLISH, Locale.FRENCH)) {
-                writeMarkDownFiles(knowledge, timescale, l);
-            }
-            writeFiles(knowledge, timescale);
-            LOGGER.trace("Generating files for {}... done", timescale);
-        }
-    }
-    /**
-     * @param knowledge knowledge to export as files
-     * @param timescale timescale of related indicators
-     * @throws IOException
-     */
-    private static void writeFiles(final Knowledge knowledge, final TimeScale timescale) throws IOException {
-        final String suffix = "-" + timescale.name().toLowerCase();
-        final String tmpDir = System.getProperty("java.io.tmpdir");
-        final Path path = Paths.get(tmpDir, "indicators" + suffix + ".csv");
-        final Path paramPath = Paths.get(tmpDir, "parameters" + suffix + ".csv");
-        LOGGER.trace(path);
-        LOGGER.trace(paramPath);
-        try (
-                BufferedWriter writer = new Utf8BufferedWriter(path);
-                BufferedWriter paramWriter = new Utf8BufferedWriter(paramPath);) {
-            writer.write("""
-                         id;nom_en;nom_fr;description_en;description_fr;variables;param\u00e8tres;notes
-                         """);
-            for (final CompositeIndicator comp : knowledge.getIndicators()) {
-                for (final Indicator ind : comp.getIndicators()) {
-                    writer.write(ind.getId());
-                    writer.write(";");
-                    writer.write(ind.getName("en"));
-                    writer.write(";");
-                    if (!ind.getName("fr").equals(ind.getName("en"))) {
-                        writer.write(ind.getName("fr"));
-                    }
-                    writer.write(";");
-                    final String description = ind.getDescription("fr");
-                    if (!description.equals(ind.getDescription("en"))) {
-                        writer.write(ind.getDescription("en"));
-                    }
-                    writer.write(";");
-                    writer.write(description);
-                    writer.write(";");
-                    final List<String> variables = new LinkedList<>();
-                    if (ind.getVariables() != null
-                            && !ind.getVariables().isEmpty()) {
-                        ind.getVariables().forEach(variable -> variables.add(variable.getName()));
-                        Collections.sort(variables);
-                        writer.write(String.join(", ", variables));
-                    }
-                    writer.write(";");
-                    final List<String> parameters = new LinkedList<>();
-                    if (ind.getParameters() != null
-                            && !ind.getParameters().isEmpty()) {
-                        ind.getParameters().forEach(param -> parameters.add(param.getId()));
-                        Collections.sort(parameters);
-                        writer.write(String.join(", ", parameters));
-                    }
-                    // affichage des références des notes de l'indicateurs
-                    writer.write(";");
-                    final List<String> notes = new LinkedList<>();
-                    if (ind.getNotes() != null && !ind.getNotes().isEmpty()) {
-                        ind.getNotes().forEach(note -> notes.add(note.getId()));
-                        writer.write(String.join(", ", notes));
-                    }
-                    writer.write("\n");
-                }
-            }
-            paramWriter.write("id;description_fr;description_en\n");
-            for (final Parameter param : knowledge.getParameters()) {
-                paramWriter.write(param.getId());
-                paramWriter.write(";");
-                paramWriter.write(param.getDescription("fr"));
-                paramWriter.write(";");
-                paramWriter.write(param.getDescription("en"));
-                paramWriter.write("\n");
-            }
-        }
-    }
-
-    /**
-     * Write a line.
-     *
-     * @param writer the writer to user
-     * @param values strings to write
-     * @throws IOException when using BufferedWriter.write
-     */
-    private static void writeLn(final BufferedWriter writer,
-            final String... values) throws IOException {
-        final int nb = values.length;
-        writer.write("| ");
-        for (int i = 0; i < nb; i++) {
-            writer.write(values[i]);
-            if (i < nb - 1) {
-                writer.write(" | ");
-            }
-        }
-        writer.write(" |\n");
-    }
-
-    /**
-     * @param knowledge knowledge to export as files
-     * @param timescale timescale of related indicators
-     * @param locale locale to write
-     * @throws IOException
-     */
-    private static void writeMarkDownFiles(final Knowledge knowledge, final TimeScale timescale, final Locale locale)
-            throws IOException {
-        final String languageCode = locale.getLanguage();
-        final String suffix = "-" + timescale.name().toLowerCase();
-        final String tmpDir = System.getProperty("java.io.tmpdir");
-        final Path mdPath = Paths.get(tmpDir, "indicators" + suffix + "." + languageCode + ".md");
-        final String bundleName = "fr.inrae.agroclim.indicators.resources.messages";
-        final I18n i18n = new I18n(bundleName, locale);
-
-        final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
-        final String created = df.format(new Date());
-
-        LOGGER.trace(mdPath);
-        try (BufferedWriter mdWriter = new Utf8BufferedWriter(mdPath);) {
-            final long nb = knowledge.getIndicators().stream().mapToInt(comp -> comp.getIndicators().size()).sum();
-            final String indicatorsVersion = fr.inrae.agroclim.indicators.resources.Version.getString("version");
-            mdWriter.write("+++\n"
-                    + "# $Id: $\n"
-                    + "title = \"" + i18n.get("markdown.title." + timescale.name().toLowerCase()) + "\"\n"
-                    + "description = \"" + i18n.get("markdown.description." + timescale.name().toLowerCase()) + "\"\n"
-                    + "keywords = [" + i18n.get("markdown.keywords") + "]\n"
-                    + "date = " + created + "\n"
-                    + "+++\n"
-                    + i18n.format("markdown.indicators.version", indicatorsVersion) + "\n\n"
-                    + "## " + i18n.format("markdown.indicators." + timescale.name().toLowerCase(), nb) + "\n"
-                    + "| " + String.join(" | ",
-                            i18n.get("markdown.id"), i18n.get("markdown.name"), i18n.get("markdown.description"),
-                            i18n.get("markdown.variables"), i18n.get("markdown.parameters"),
-                            i18n.get("markdown.unit") + " [^1]", i18n.get("markdown.notes")) + " |\n"
-                            + "|:---|:-----|:------------|:----------|:-----------|:-----------|:-----------|\n");
-
-            final Set<String> allVariables = new HashSet<>();
-            for (final CompositeIndicator comp : knowledge.getIndicators()) {
-                writeLn(mdWriter, "**" + comp.getName(languageCode) + "**");
-                for (final Indicator ind : comp.getIndicators()) {
-                    final List<String> variables = new LinkedList<>();
-                    if (ind.getVariables() != null
-                            && !ind.getVariables().isEmpty()) {
-                        ind.getVariables().forEach(variable -> variables.add(variable.getName()));
-                        Collections.sort(variables);
-                        allVariables.addAll(variables);
-                    }
-                    final List<String> parameters = new LinkedList<>();
-                    if (ind.getParameters() != null
-                            && !ind.getParameters().isEmpty()) {
-                        ind.getParameters().forEach(param -> parameters.add(param.getId()));
-                        Collections.sort(parameters);
-                    }
-                    String unit = "";
-                    if (ind.getUnit() != null) {
-                        List<LocalizedString> symbols = ind.getUnit().getSymbols();
-                        if (symbols != null && !symbols.isEmpty()) {
-                            unit = LocalizedString.getString(symbols, languageCode);
-                        }
-                        if (unit == null || unit.isBlank()) {
-                            final List<LocalizedString> labels = ind.getUnit().getLabels();
-                            if (labels != null && !labels.isEmpty()) {
-                                unit = LocalizedString.getString(labels, languageCode);
-                            }
-                        }
-                    }
-                    // affichage des références des notes de l'indicateurs
-                    final List<String> notes = new LinkedList<>();
-                    if (ind.getNotes() != null && !ind.getNotes().isEmpty()) {
-                        ind.getNotes().forEach(note -> {
-                            final String anchor = note.getId();
-                            notes.add("<a href='#" + anchor + "'>" + note.getId() + "</a>");
-                        });
-                    }
-                    writeLn(mdWriter, ind.getId(), ind.getName(languageCode),
-                            ind.getDescription(languageCode), String.join(", ", variables),
-                            String.join(", ", parameters), unit, String.join(", ", notes));
-                }
-            }
-
-            mdWriter.write("""
-
-                           ###\s""" + i18n.get("markdown.parameters") + "\n"
-                    + "| " + i18n.get("markdown.id") + " | " + i18n.get("markdown.description") + " |\n"
-                    + "|:---|:------------|\n");
-
-            for (final Parameter param : knowledge.getParameters()) {
-                writeLn(mdWriter, param.getId(), param.getDescription(languageCode));
-            }
-
-            mdWriter.write("""
-
-                           ###\s""" + i18n.get("markdown.variables") + "\n"
-                    + "| " + i18n.get("markdown.id") + " | " + i18n.get("markdown.description") + " |\n"
-                    + "|:---|:------------|\n");
-            allVariables.stream().sorted().forEach(variable -> {
-                try {
-                    mdWriter.write("| ");
-                    mdWriter.write(variable);
-                    mdWriter.write(" | ");
-                    mdWriter.write(i18n.get("Variable." + variable.toUpperCase() + ".description"));
-                    mdWriter.write(" |\n");
-                } catch (final IOException ex) {
-                    LOGGER.catching(ex);
-                }
-            });
-
-            // Ecriture de l'ensemble des notes présentes
-            if (knowledge.getNotes() != null && !knowledge.getNotes().isEmpty()) {
-                mdWriter.write("""
-
-                               ###\s""" + i18n.get("markdown.notes") + "\n"
-                        + "| " + i18n.get("markdown.reference") + " | " + i18n.get("markdown.description") + " |\n"
-                        + "|:---|:------------|\n");
-                for (final Note note : knowledge.getNotes()) {
-                    final String anchor;
-
-                    // si il s'agit d'un DOI, on affiche le lien
-                    final String id = note.getId();
-                    if (StringUtils.isDoiRef(id)) {
-                        anchor = String.format(
-                                "<a id=\"%1$s\" href=\"https://doi.org/%1$s\" target=\"_blank\">%1$s</a>",
-                                id);
-                    } else {
-                        anchor = String.format("<a id=\"%1$s\">%1$s</a>", id);
-                    }
-
-                    writeLn(mdWriter, anchor, note.getDescription());
-                }
-            }
-
-            mdWriter.write("\n\n[^1]: " + i18n.get("markdown.unit.footnote"));
-        }
-    }
-
     /**
      * Indicators for cultural practices.
      */
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/Quantifiable.java b/src/main/java/fr/inrae/agroclim/indicators/model/Quantifiable.java
index 44472bddf0f97aa78c06d05cb533731c773814b5..6eca288129a7ecdd3dbdf748467f4ea2a38ddb04 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/Quantifiable.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/Quantifiable.java
@@ -16,8 +16,7 @@
  */
 package fr.inrae.agroclim.indicators.model;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.data.DailyData;
 import fr.inrae.agroclim.indicators.model.data.Resource;
 
@@ -34,11 +33,7 @@ public interface Quantifiable {
      * @param data
      *            resource of daily data
      * @return single result for resource of daily data
-     * @throws TechnicalException
-     *             technical exception
-     * @throws FunctionalException
-     *             functional exception
+     * @throws IndicatorsException
      */
-    Double compute(Resource<? extends DailyData> data)
-            throws TechnicalException, FunctionalException;
+    Double compute(Resource<? extends DailyData> data) throws IndicatorsException;
 }
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/criteria/ComparisonCriteria.java b/src/main/java/fr/inrae/agroclim/indicators/model/criteria/ComparisonCriteria.java
index 453b405556380ef22162a87a34a5f4ae8604d443..5779acb8642cef542dd1fb8e423bd3a67c772fb1 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/criteria/ComparisonCriteria.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/criteria/ComparisonCriteria.java
@@ -18,7 +18,8 @@
  */
 package fr.inrae.agroclim.indicators.model.criteria;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
+import fr.inrae.agroclim.indicators.exception.type.ComputationErrorType;
 import fr.inrae.agroclim.indicators.model.Parameter;
 import fr.inrae.agroclim.indicators.model.data.DailyData;
 import fr.inrae.agroclim.indicators.model.data.Resource;
@@ -99,9 +100,10 @@ public final class ComparisonCriteria extends Criteria {
     }
 
     @Override
-    public boolean eval(final DailyData dailydata) throws TechnicalException {
+    public boolean eval(final DailyData dailydata) throws IndicatorsException {
         if (relationalOperator == null) {
-            throw new RuntimeException("The relational operator must be set!");
+            throw new IndicatorsException(ComputationErrorType.WRONG_DEFINITION,
+                    "The relational operator must be set!");
         }
         if (dailydata == null) {
             return false;
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/criteria/CompositeCriteria.java b/src/main/java/fr/inrae/agroclim/indicators/model/criteria/CompositeCriteria.java
index 37bb407f377ae1d92e8f9cbed936017b72da9e6f..3f09a9f975240d8fd7a0e2c8d8f3c1186c8d82ae 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/criteria/CompositeCriteria.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/criteria/CompositeCriteria.java
@@ -16,12 +16,13 @@
  */
 package fr.inrae.agroclim.indicators.model.criteria;
 
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.type.ComputationErrorType;
 import fr.inrae.agroclim.indicators.model.Parameter;
 import fr.inrae.agroclim.indicators.model.data.DailyData;
 import fr.inrae.agroclim.indicators.model.data.Resource;
@@ -87,10 +88,9 @@ public final class CompositeCriteria extends Criteria {
     }
 
     @Override
-    public boolean eval(final DailyData dailyData)
-            throws TechnicalException {
+    public boolean eval(final DailyData dailyData) throws IndicatorsException {
         if (logicalOperator == null) {
-            throw new RuntimeException("The logical operator must be set!");
+            throw new IndicatorsException(ComputationErrorType.WRONG_DEFINITION, "The logical operator must be set!");
         }
         switch (logicalOperator) {
         case AND:
@@ -125,11 +125,10 @@ public final class CompositeCriteria extends Criteria {
      * @param data
      *            climatic data
      * @return formula
-     * @throws TechnicalException
+     * @throws IndicatorsException
      *             exception while getting data
      */
-    public String getFormula(final ClimaticDailyData data)
-            throws TechnicalException {
+    public String getFormula(final ClimaticDailyData data) throws IndicatorsException {
         StringBuilder sb = new StringBuilder();
         boolean first = true;
         for (final Criteria aCriteria : criteria) {
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/criteria/Criteria.java b/src/main/java/fr/inrae/agroclim/indicators/model/criteria/Criteria.java
index 4e3681556c61f47be885f6dfb92d8241b01da824..8934fd793f0aa4eb08806ec81f13171bde2e14f2 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/criteria/Criteria.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/criteria/Criteria.java
@@ -16,6 +16,7 @@
  */
 package fr.inrae.agroclim.indicators.model.criteria;
 
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import java.beans.PropertyChangeListener;
 import java.beans.PropertyChangeSupport;
 import java.io.Serializable;
@@ -28,7 +29,6 @@ import javax.xml.bind.annotation.XmlElementWrapper;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlTransient;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
 import fr.inrae.agroclim.indicators.model.Computable;
 import fr.inrae.agroclim.indicators.model.HasParameters;
 import fr.inrae.agroclim.indicators.model.Parameter;
@@ -114,11 +114,10 @@ Computable, HasParameters, Serializable, UseVariables {
      * @param data
      *            data to compare
      * @return comparison result
-     * @throws TechnicalException
+     * @throws IndicatorsException
      *             exception during evaluation
      */
-    public abstract boolean eval(DailyData data)
-            throws TechnicalException;
+    public abstract boolean eval(DailyData data) throws IndicatorsException;
 
     /**
      * @param indent
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/criteria/FormulaCriteria.java b/src/main/java/fr/inrae/agroclim/indicators/model/criteria/FormulaCriteria.java
index 29c23fd4c3fa0161c9633cee01e3d3914ba227a8..02c5a31d08f3f8829c5644c678f84cf48c54d0ab 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/criteria/FormulaCriteria.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/criteria/FormulaCriteria.java
@@ -24,15 +24,15 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlType;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
+import fr.inrae.agroclim.indicators.exception.type.ComputationErrorType;
+import fr.inrae.agroclim.indicators.exception.type.ResourceErrorType;
 import fr.inrae.agroclim.indicators.model.ExpressionParameter;
 import fr.inrae.agroclim.indicators.model.JEXLFormula;
 import fr.inrae.agroclim.indicators.model.Parameter;
@@ -143,9 +143,13 @@ public final class FormulaCriteria extends Criteria {
     }
 
     @Override
-    public boolean eval(final DailyData data) throws TechnicalException {
-        Objects.requireNonNull(expression, "expression must not be null!");
-        Objects.requireNonNull(data, "Resource data must not be null!");
+    public boolean eval(final DailyData data) throws IndicatorsException {
+        if (expression == null) {
+            throw new IndicatorsException(ComputationErrorType.FORMULA_EXPRESSION_NULL);
+        }
+        if (data == null) {
+            throw new IndicatorsException(ResourceErrorType.CLIMATE_EMPTY);
+        }
         formula.setExpression(expression);
         final Map<String, Double> values = new HashMap<>();
         getVariables().forEach(variable -> values.put(variable.name(), data.getValue(variable)));
@@ -157,8 +161,8 @@ public final class FormulaCriteria extends Criteria {
         }
         try {
             return formula.evaluate(values, Boolean.class);
-        } catch (final FunctionalException ex) {
-            throw new TechnicalException("Failed to evaluate the criteria: " + ex.getLocalizedMessage(), ex);
+        } catch (final IndicatorsException ex) {
+            throw new IndicatorsException(ComputationErrorType.FORMULA, ex, "Failed to evaluate the criteria.");
         }
     }
 
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/criteria/NoCriteria.java b/src/main/java/fr/inrae/agroclim/indicators/model/criteria/NoCriteria.java
index 98660e2586b7223263201926025f26dfd82ac25b..d6c88efe995819628f4aeb25b57996b6f4b2ab29 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/criteria/NoCriteria.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/criteria/NoCriteria.java
@@ -18,13 +18,13 @@
  */
 package fr.inrae.agroclim.indicators.model.criteria;
 
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import java.util.Map;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlType;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
 import fr.inrae.agroclim.indicators.model.Parameter;
 import fr.inrae.agroclim.indicators.model.data.DailyData;
 import fr.inrae.agroclim.indicators.util.Doublet;
@@ -59,7 +59,7 @@ public final class NoCriteria extends VariableCriteria {
     }
 
     @Override
-    public boolean eval(final DailyData data) throws TechnicalException {
+    public boolean eval(final DailyData data) throws IndicatorsException {
         return true;
     }
 
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/criteria/SimpleCriteria.java b/src/main/java/fr/inrae/agroclim/indicators/model/criteria/SimpleCriteria.java
index 4c59c441a3bbfc8fea1e48744845871998ad8e7a..337033567a371bd3470c76237b127354b3f85d88 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/criteria/SimpleCriteria.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/criteria/SimpleCriteria.java
@@ -16,6 +16,7 @@
  */
 package fr.inrae.agroclim.indicators.model.criteria;
 
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -25,7 +26,6 @@ import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlTransient;
 import javax.xml.bind.annotation.XmlType;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
 import fr.inrae.agroclim.indicators.model.Parameter;
 import fr.inrae.agroclim.indicators.model.data.DailyData;
 import fr.inrae.agroclim.indicators.model.data.Variable;
@@ -139,7 +139,7 @@ public final class SimpleCriteria extends VariableCriteria {
     }
 
     @Override
-    public boolean eval(final DailyData dailydata) throws TechnicalException {
+    public boolean eval(final DailyData dailydata) throws IndicatorsException {
         if (dailydata == null) {
             return false;
         }
@@ -168,11 +168,10 @@ public final class SimpleCriteria extends VariableCriteria {
      * @param data
      *            climatic data
      * @return formula
-     * @throws TechnicalException
+     * @throws IndicatorsException
      *             exception while getting data
      */
-    public String getFormula(final ClimaticDailyData data)
-            throws TechnicalException {
+    public String getFormula(final ClimaticDailyData data) throws IndicatorsException {
         final StringBuilder sb = new StringBuilder();
         sb.append(getValueOf(data));
         sb.append(" ");
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/criteria/VariableCriteria.java b/src/main/java/fr/inrae/agroclim/indicators/model/criteria/VariableCriteria.java
index 0f9c14a5da7ae99c61d862eb09743f078c0c6294..5e58c4f83b1c3236ec9844aff4213fbf093fff65 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/criteria/VariableCriteria.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/criteria/VariableCriteria.java
@@ -18,7 +18,8 @@
  */
 package fr.inrae.agroclim.indicators.model.criteria;
 
-import java.util.HashSet;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
+import fr.inrae.agroclim.indicators.exception.type.ComputationErrorType;
 import java.util.Objects;
 import java.util.Set;
 
@@ -67,25 +68,25 @@ public abstract class VariableCriteria extends Criteria {
      *
      * @param data climatic data
      * @return value of "variable"
+     * @throws IndicatorsException in case of wrong definition or while getting value
      */
-    public final double getValueOf(final DailyData data) {
+    public final double getValueOf(final DailyData data) throws IndicatorsException {
         if (data == null) {
-            throw new IllegalArgumentException("DailyData is null!");
+            throw new IndicatorsException(ComputationErrorType.DATA_NULL);
         }
         if (variable == null) {
-            throw new NullPointerException("variable is null!");
+            throw new IndicatorsException(ComputationErrorType.VARIABLE_NAME_NULL);
         }
-        if (data.getValue(variable) == null) {
-            throw new NullPointerException("At " + data.getDate() + ", " + variable + " is null!");
+        final var value = data.getValue(variable);
+        if (value == null) {
+            throw new IndicatorsException(ComputationErrorType.VARIABLE_VALUE_NULL, data.getDate(), variable);
         }
-        return data.getValue(variable);
+        return value;
     }
 
     @Override
     public final Set<Variable> getVariables() {
-        final Set<Variable> variables = new HashSet<>();
-        variables.add(variable);
-        return variables;
+        return Set.of(variable);
     }
 
     /**
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/data/ResourceManager.java b/src/main/java/fr/inrae/agroclim/indicators/model/data/ResourceManager.java
index 9d373087ec110b91932b1ab19632bac6fe483353..31b74dd20881b8a513272e43d04828e22c9bff8c 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/data/ResourceManager.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/data/ResourceManager.java
@@ -26,10 +26,8 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
-import fr.inrae.agroclim.indicators.exception.ErrorCategory;
 import fr.inrae.agroclim.indicators.exception.ErrorMessage;
-import fr.inrae.agroclim.indicators.exception.ErrorType;
-import fr.inrae.agroclim.indicators.exception.IndicatorErrorCategory;
+import fr.inrae.agroclim.indicators.exception.type.ResourceErrorType;
 import fr.inrae.agroclim.indicators.model.TimeScale;
 import fr.inrae.agroclim.indicators.model.data.Variable.Type;
 import fr.inrae.agroclim.indicators.model.data.climate.ClimaticResource;
@@ -37,7 +35,6 @@ import fr.inrae.agroclim.indicators.model.data.phenology.PhenologicalResource;
 import fr.inrae.agroclim.indicators.util.DateUtils;
 import lombok.Getter;
 import lombok.NonNull;
-import lombok.RequiredArgsConstructor;
 import lombok.Setter;
 import lombok.extern.log4j.Log4j2;
 
@@ -47,141 +44,14 @@ import lombok.extern.log4j.Log4j2;
  *
  * Its responsibility is data storage and checking data consistency.
  *
- * Last changed : $Date$
+ * Last changed : $Date: 2023-03-16 17:36:45 +0100 (jeu. 16 mars 2023) $
  *
- * @author $Author$
- * @version $Revision$
+ * @author $Author: omaury $
+ * @version $Revision: 644 $
  */
 @Log4j2
 public final class ResourceManager implements Serializable, Cloneable {
 
-    /**
-     * Keys from messages.properties used to warn about errors.
-     */
-    @RequiredArgsConstructor
-    public enum ErrorI18nKey implements ErrorType {
-        /**
-         * Climate, topic.
-         */
-        CLIMATE("01", null, "resource.climatic"),
-        /**
-         * No climate data.
-         */
-        CLIMATE_EMPTY("02", CLIMATE, "empty"),
-        /**
-         * Not enough data.
-         */
-        CLIMATE_SIZE_WRONG("03", CLIMATE, "size.wrong"),
-        /**
-         * Years of climate, topic.
-         */
-        CLIMATIC_YEARS("04", null, "resource.climatic.years"),
-        /**
-         * No years of climate.
-         */
-        CLIMATE_YEARS_EMPTY("05", CLIMATIC_YEARS, "empty"),
-        /**
-         * Not enough data.
-         */
-        CLIMATE_YEARS_MISSING("06", CLIMATIC_YEARS, "missing"),
-        /**
-         * Phenology, topic.
-         */
-        PHENO("07", null, "resource.pheno"),
-        /**
-         * No phenological data.
-         */
-        PHENO_EMPTY("08", PHENO, "empty"),
-        /**
-         * Years of phenology, topic.
-         */
-        PHENO_YEARS("09", null, "resource.pheno.years"),
-        /**
-         * No years of phenology.
-         */
-        PHENO_YEARS_EMPTY("10", PHENO_YEARS, "empty"),
-        /**
-         * Not enough data.
-         */
-        PHENO_YEARS_MISSING("11", PHENO_YEARS, "missing"),
-        /**
-         * Resource in general, topic.
-         */
-        RESOURCE("12", null, "resource"),
-        /**
-         * Setting not set.
-         */
-        RESOURCE_CROPDEVELOPMENT_YEARS("13", RESOURCE, "cropdevelopmentyears.null"),
-        /**
-         * Soil, topic.
-         */
-        SOIL("14", null, "resource.soil"),
-        /**
-         * Not enough data.
-         */
-        SOIL_SIZE_WRONG("15", SOIL, "size.wrong"),
-        /**
-         * Variables, topic.
-         */
-        VARIABLES("16", null, "resource.variables"),
-        /**
-         * No variable.
-         */
-        VARIABLES_EMPTY("17", VARIABLES, "empty"),
-        /**
-         * No variale.
-         */
-        VARIABLES_MISSING("18", VARIABLES, "missing");
-        /**
-         * Key for Resource/I18nResource.
-         */
-        private final String key;
-        /**
-         * Subcode for the error.
-         */
-        @Getter
-        private final String subCode;
-        /**
-         * Parent refers to the resource part.
-         */
-        @Getter
-        private final ErrorI18nKey parent;
-
-        /**
-         * Constructor.
-         *
-         * @param c Subcode for the error.
-         * @param p Parent refers to the resource part.
-         * @param k Key for Resource/I18nResource.
-         */
-        ErrorI18nKey(final String c, final ErrorI18nKey p, final String k) {
-            parent = p;
-            key = k;
-            subCode = c;
-        }
-
-        @Override
-        public ErrorCategory getCategory() {
-            return IndicatorErrorCategory.RESOURCES;
-        }
-
-        /**
-         * @return Key for Resource/I18nResource.
-         */
-        @Override
-        public String getI18nKey() {
-            if (parent != null) {
-                return "error.evaluation." + parent.key + "." + key;
-            }
-            return "error.evaluation." + key;
-        }
-
-        @Override
-        public String getName() {
-            return name();
-        }
-    }
-
     /**
      * UUID for Serializable.
      */
@@ -191,8 +61,8 @@ public final class ResourceManager implements Serializable, Cloneable {
      * @param errorI18nKey Message key from .property resource.
      */
     private static void addErrorMessage(
-            final Map<ErrorI18nKey, ErrorMessage> errors,
-            final ErrorI18nKey errorI18nKey) {
+            final Map<ResourceErrorType, ErrorMessage> errors,
+            final ResourceErrorType errorI18nKey) {
         errors.put(errorI18nKey.getParent(), new ErrorMessage(
                 "fr.inrae.agroclim.indicators.resources.messages",
                 errorI18nKey, null));
@@ -259,21 +129,21 @@ public final class ResourceManager implements Serializable, Cloneable {
     /**
      * @return consistency errors
      */
-    public Map<ErrorI18nKey, ErrorMessage> getConsitencyErrors() {
-        final Map<ErrorI18nKey, ErrorMessage> errors = new EnumMap<>(ErrorI18nKey.class);
+    public Map<ResourceErrorType, ErrorMessage> getConsitencyErrors() {
+        final Map<ResourceErrorType, ErrorMessage> errors = new EnumMap<>(ResourceErrorType.class);
         // variables not set ?
         if (variables == null) {
-            addErrorMessage(errors, ErrorI18nKey.VARIABLES_MISSING);
+            addErrorMessage(errors, ResourceErrorType.VARIABLES_MISSING);
             return errors;
         }
         if (variables.isEmpty()) {
-            addErrorMessage(errors, ErrorI18nKey.VARIABLES_EMPTY);
+            addErrorMessage(errors, ResourceErrorType.VARIABLES_EMPTY);
             return errors;
         }
         // empty climate
         final List<Integer> climaticYears = climaticResource.getYears();
         if (hasClimaticVariables() && climaticResource.getData().isEmpty()) {
-            addErrorMessage(errors, ErrorI18nKey.CLIMATE_EMPTY);
+            addErrorMessage(errors, ResourceErrorType.CLIMATE_EMPTY);
         } else {
             // missing days or hours
             final int nbClimatic = climaticResource.getData().size();
@@ -285,12 +155,12 @@ public final class ResourceManager implements Serializable, Cloneable {
                 nb = nb * DateUtils.NB_OF_HOURS_IN_DAY;
             }
             if (nbClimatic != nb) {
-                addErrorMessage(errors, ErrorI18nKey.CLIMATE_SIZE_WRONG);
+                addErrorMessage(errors, ResourceErrorType.CLIMATE_SIZE_WRONG);
             }
         }
         // empty phenology
         if (phenologicalResource.isEmpty()) {
-            addErrorMessage(errors, ErrorI18nKey.PHENO_EMPTY);
+            addErrorMessage(errors, ResourceErrorType.PHENO_EMPTY);
         }
         if (!errors.isEmpty()) {
             return errors;
@@ -298,16 +168,16 @@ public final class ResourceManager implements Serializable, Cloneable {
         // period
         final List<Integer> phenoYears = phenologicalResource.getYears();
         if (hasClimaticVariables() && climaticYears.isEmpty()) {
-            addErrorMessage(errors, ErrorI18nKey.CLIMATE_YEARS_EMPTY);
+            addErrorMessage(errors, ResourceErrorType.CLIMATE_YEARS_EMPTY);
         }
         if (phenoYears.isEmpty()) {
-            addErrorMessage(errors, ErrorI18nKey.PHENO_YEARS_EMPTY);
+            addErrorMessage(errors, ResourceErrorType.PHENO_YEARS_EMPTY);
         }
         if (!errors.isEmpty()) {
             return errors;
         }
         if (cropDevelopmentYears == null) {
-            addErrorMessage(errors, ErrorI18nKey.RESOURCE_CROPDEVELOPMENT_YEARS);
+            addErrorMessage(errors, ResourceErrorType.RESOURCES_CROPDEVELOPMENT_YEARS);
             return errors;
         }
         // Phenology data drives evaluation, so
@@ -325,9 +195,9 @@ public final class ResourceManager implements Serializable, Cloneable {
             theMissing.removeAll(climaticYears);
             final ErrorMessage error = new ErrorMessage(
                     "fr.inrae.agroclim.indicators.resources.messages",
-                    ErrorI18nKey.CLIMATE_YEARS_MISSING,
+                    ResourceErrorType.CLIMATE_YEARS_MISSING,
                     theMissing);
-            errors.put(ErrorI18nKey.CLIMATE_YEARS_MISSING.getParent(), error);
+            errors.put(ResourceErrorType.CLIMATE_YEARS_MISSING.getParent(), error);
         }
         if (!errors.isEmpty()) {
             return errors;
@@ -340,7 +210,7 @@ public final class ResourceManager implements Serializable, Cloneable {
             }
             final int nbSoil = climaticResource.getData().size();
             if (nbSoil != nbDays) {
-                addErrorMessage(errors, ErrorI18nKey.SOIL_SIZE_WRONG);
+                addErrorMessage(errors, ResourceErrorType.SOIL_SIZE_WRONG);
             }
         }
         if (errors.isEmpty()) {
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/function/aggregation/AggregationFunction.java b/src/main/java/fr/inrae/agroclim/indicators/model/function/aggregation/AggregationFunction.java
index 3498f290b9d2d3fc54944cb511b1ad31a1033a30..0816bf082855489af2202a3782b337d00b805686 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/function/aggregation/AggregationFunction.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/function/aggregation/AggregationFunction.java
@@ -23,7 +23,7 @@ import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlAttribute;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import lombok.EqualsAndHashCode;
 import lombok.Getter;
 import lombok.Setter;
@@ -56,11 +56,10 @@ public abstract class AggregationFunction implements Serializable {
      * @param values
      *            values to use for aggregation
      * @return aggregated value
-     * @throws FunctionalException
+     * @throws IndicatorsException
      *             exception
      */
-    public abstract double aggregate(Map<String, Double> values)
-            throws FunctionalException;
+    public abstract double aggregate(Map<String, Double> values) throws IndicatorsException;
 
     /**
      * @return function is valid
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/function/aggregation/JEXLFunction.java b/src/main/java/fr/inrae/agroclim/indicators/model/function/aggregation/JEXLFunction.java
index 498d0ee3f2b7a9611d01583b2cc1ba62e275b744..1e14519ffab41edabd5328df784b693eef7c72f8 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/function/aggregation/JEXLFunction.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/function/aggregation/JEXLFunction.java
@@ -6,7 +6,7 @@ import java.util.Map;
 
 import javax.xml.bind.annotation.XmlRootElement;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.JEXLFormula;
 import lombok.EqualsAndHashCode;
 
@@ -41,7 +41,7 @@ public final class JEXLFunction extends AggregationFunction {
     }
 
     @Override
-    public double aggregate(final Map<String, Double> values) throws FunctionalException {
+    public double aggregate(final Map<String, Double> values) throws IndicatorsException {
         formula.setExpression(getExpression());
         return formula.evaluate(values, Double.class);
     }
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/AggregationIndicator.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/AggregationIndicator.java
index 60969fdbff05df0b7eff3afb56af6130450cac60..33ad49c735d3b3e570835ccf2db0be033be077a8 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/AggregationIndicator.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/AggregationIndicator.java
@@ -7,8 +7,9 @@ import java.util.stream.DoubleStream;
 
 import javax.xml.bind.annotation.XmlElement;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
+import fr.inrae.agroclim.indicators.exception.ThrowingToDoubleFunction;
+import fr.inrae.agroclim.indicators.exception.type.ComputationErrorType;
 import fr.inrae.agroclim.indicators.model.criteria.Criteria;
 import fr.inrae.agroclim.indicators.model.criteria.NoCriteria;
 import fr.inrae.agroclim.indicators.model.criteria.SimpleCriteria;
@@ -24,7 +25,7 @@ import lombok.Setter;
  * Aggregate value defined in a {@link SimpleCriteria} using a {@link Function}
  * on {@link DoubleStream} created for {@link AggregationIndicator#variable}.
  *
- * Last changed : $Date$
+ * Last changed : $Date: 2023-06-16 10:36:46 +0200 (ven., 16 juin 2023) $
  *
  * @author omaury
  */
@@ -52,6 +53,8 @@ public abstract class AggregationIndicator extends SimpleIndicatorWithCriteria i
 
     /**
      * Override {@link SimpleIndicatorWithCriteria#clone()}.
+     * @return clone
+     * @throws CloneNotSupportedException should not occur
      */
     @Override
     public AggregationIndicator clone() throws CloneNotSupportedException {
@@ -64,35 +67,44 @@ public abstract class AggregationIndicator extends SimpleIndicatorWithCriteria i
     }
 
     @Override
-    public final double computeSingleValue(final Resource<? extends DailyData> res)
-            throws TechnicalException, FunctionalException {
+    public final double computeSingleValue(final Resource<? extends DailyData> res) throws IndicatorsException {
         final DoubleStream stream;
-        if (getCriteria() instanceof final NoCriteria criteria) {
-            Objects.requireNonNull(criteria.getVariable(),
-                    getId() + ".getCriteria().getVariable() must not be null! " + getVariable());
-            // syntax changed to hack Eclipse.
-            stream = res.getData().stream().mapToDouble(d -> criteria.getValueOf(d));
-        } else if (getCriteria() instanceof final SimpleCriteria criteria) {
-            final Predicate<DailyData> predicate = d -> {
-                try {
-                    return criteria.eval(d);
-                } catch (final TechnicalException e) {
-                    return false;
-                }
-            };
-            stream = res.getData().stream() //
-                    .filter(predicate) //
-                    .mapToDouble(criteria::getValueOf);
-        } else {
-            throw new FunctionalException("criteria is neither NoCriteria nor SimpleCriteria!");
+        try {
+            if (getCriteria() instanceof final NoCriteria criteria) {
+                Objects.requireNonNull(criteria.getVariable(),
+                        getId() + ".getCriteria().getVariable() must not be null! " + getVariable());
+                // syntax changed to hack Eclipse.
+                stream = res.getData().stream() //
+                        .mapToDouble(ThrowingToDoubleFunction.wrap(criteria::getValueOf));
+            } else if (getCriteria() instanceof final SimpleCriteria criteria) {
+                final Predicate<DailyData> predicate = d -> {
+                    try {
+                        return criteria.eval(d);
+                    } catch (final IndicatorsException e) {
+                        return false;
+                    }
+                };
+                stream = res.getData().stream() //
+                        .filter(predicate) //
+                        .mapToDouble(ThrowingToDoubleFunction.wrap(criteria::getValueOf));
+            } else {
+                throw new IndicatorsException(ComputationErrorType.CRITERIA_ISNT_NOCRITERIA_SIMPLECRITERIA, getId());
+            }
+            return aggregate(stream);
+        } catch (final RuntimeException ex) {
+            if (ex.getCause() instanceof IndicatorsException iex) {
+                throw iex;
+            }
+            throw ex;
         }
-        return aggregate(stream);
     }
 
     @Override
     public final Criteria getCriteria() {
         if (super.getCriteria() == null) {
-            super.setCriteria(new NoCriteria());
+            final var criteria = new NoCriteria();
+            criteria.setVariable(variable);
+            super.setCriteria(criteria);
         }
         if (super.getCriteria() instanceof VariableCriteria && this.variable != null) {
             ((VariableCriteria) super.getCriteria()).setVariable(this.variable);
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/AverageOfDiff.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/AverageOfDiff.java
index 469c63164bca3502721912e290a8cde8bf4d8a4f..6b5a1dde4378848115fa5f296c87402890d3fa5d 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/AverageOfDiff.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/AverageOfDiff.java
@@ -16,14 +16,13 @@
  */
 package fr.inrae.agroclim.indicators.model.indicator;
 
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import java.util.HashSet;
 import java.util.Set;
 
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlType;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
 import fr.inrae.agroclim.indicators.model.data.DailyData;
 import fr.inrae.agroclim.indicators.model.data.Resource;
 import fr.inrae.agroclim.indicators.model.data.Variable;
@@ -73,8 +72,7 @@ implements Detailable {
     }
 
     @Override
-    public double computeSingleValue(final Resource<? extends DailyData> res)
-            throws TechnicalException, FunctionalException {
+    public double computeSingleValue(final Resource<? extends DailyData> res) throws IndicatorsException {
         double value = 0;
         for (final DailyData data : res.getData()) {
             value += data.getValue(variable1)
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java
index 4d39518526acf8c012bc121ea2684816b152f5e5..1de200579ef216ec319b9b70322c1678a21d9fdb 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/CompositeIndicator.java
@@ -30,8 +30,8 @@ import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlTransient;
 import javax.xml.bind.annotation.XmlType;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
+import fr.inrae.agroclim.indicators.exception.type.ComputationErrorType;
 import fr.inrae.agroclim.indicators.model.Evaluation;
 import fr.inrae.agroclim.indicators.model.EvaluationType;
 import fr.inrae.agroclim.indicators.model.Knowledge;
@@ -252,8 +252,7 @@ DataLoadingListener, Detailable, HasDataLoadingListener, Comparable<Indicator> {
     }
 
     @Override
-    public final Double compute(final Resource<? extends DailyData> climRessource)
-            throws TechnicalException, FunctionalException {
+    public final Double compute(final Resource<? extends DailyData> climRessource) throws IndicatorsException {
         if (climRessource.getYears().isEmpty()) {
             throw new RuntimeException(
                     String.format(
@@ -277,8 +276,8 @@ DataLoadingListener, Detailable, HasDataLoadingListener, Comparable<Indicator> {
             try {
                 final double value = indicator.compute(climRessource);
                 results.put(indicator.getId(), value);
-            } catch (FunctionalException | TechnicalException e) {
-                LOGGER.error("Failed to compute indicator '{}': {}", indicator.getId(), e.getMessage());
+            } catch (final IndicatorsException e) {
+                throw new IndicatorsException(ComputationErrorType.COMPOSITE_COMPUTATION, e, indicator.getId());
             }
         }
         if (isAggregationNeeded()) {
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/DayOfYear.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/DayOfYear.java
index 457b4229aa63740f32a2e15269215ea0faed7815..8a5dcb18ae3a326ff76577e395125a4a10aacef2 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/DayOfYear.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/DayOfYear.java
@@ -20,8 +20,8 @@ import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlRootElement;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
+import fr.inrae.agroclim.indicators.exception.type.ComputationErrorType;
 import fr.inrae.agroclim.indicators.model.data.DailyData;
 import fr.inrae.agroclim.indicators.model.data.Resource;
 import lombok.Getter;
@@ -73,10 +73,9 @@ implements Detailable {
     }
 
     @Override
-    public double computeSingleValue(final Resource<? extends DailyData> data)
-            throws TechnicalException, FunctionalException {
+    public double computeSingleValue(final Resource<? extends DailyData> data) throws IndicatorsException {
         if (first == null) {
-            throw new FunctionalException("first must not be null!");
+            throw new IndicatorsException(ComputationErrorType.WRONG_DEFINITION, "first must not be null");
         }
         double res = 0;
         boolean resultCriteria;
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/DiffOfSum.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/DiffOfSum.java
index 9b7418e38fecb1881a1a9dab23a005374af267ac..e0468ca609029a5d4a9dea8529334cdef680ad2a 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/DiffOfSum.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/DiffOfSum.java
@@ -26,8 +26,7 @@ import java.util.Set;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlType;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.Knowledge;
 import fr.inrae.agroclim.indicators.model.Parameter;
 import fr.inrae.agroclim.indicators.model.data.DailyData;
@@ -92,8 +91,7 @@ public final class DiffOfSum extends SimpleIndicator implements Detailable, Indi
     }
 
     @Override
-    public double computeSingleValue(final Resource<? extends DailyData> res)
-            throws TechnicalException, FunctionalException {
+    public double computeSingleValue(final Resource<? extends DailyData> res) throws IndicatorsException {
         final double sum1 = getSumVariable1().compute(res);
         final double sum2 = getSumVariable2().compute(res);
         return sum1 - sum2;
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Formula.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Formula.java
index 41b3610119d2e61451e77668d6ee242a585f86d3..bad3df9f30d89310739df11152188a537e6f7395 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Formula.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Formula.java
@@ -31,8 +31,9 @@ import java.util.stream.DoubleStream;
 
 import javax.xml.bind.annotation.XmlType;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
+import fr.inrae.agroclim.indicators.exception.type.ComputationErrorType;
+import fr.inrae.agroclim.indicators.exception.type.ResourceErrorType;
 import fr.inrae.agroclim.indicators.model.ExpressionParameter;
 import fr.inrae.agroclim.indicators.model.JEXLFormula;
 import fr.inrae.agroclim.indicators.model.Knowledge;
@@ -134,11 +135,14 @@ public final class Formula extends SimpleIndicator implements Detailable {
     private transient Map<String, Double> parametersValues = new HashMap<>();
 
     @Override
-    public double computeSingleValue(final Resource<? extends DailyData> res)
-            throws TechnicalException, FunctionalException {
+    public double computeSingleValue(final Resource<? extends DailyData> res) throws IndicatorsException {
+        if (aggregation == null) {
+            throw new IndicatorsException(ComputationErrorType.FORMULA_AGGREGATION_NULL);
+        }
         Objects.requireNonNull(res, "Resource must not be null!");
-        Objects.requireNonNull(res.getData(), "Resource data must not be null!");
-        Objects.requireNonNull(aggregation, "aggregation must not be null!");
+        if (res.getData() == null) {
+            throw new IndicatorsException(ResourceErrorType.CLIMATE_EMPTY);
+        }
         final Set<Variable> dataVariables = getVariables();
         final List<Double> computedValues = new ArrayList<>();
         for (final DailyData data : res.getData()) {
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Frequency.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Frequency.java
index 2fc65a2d86dec4078867d43355fe860fb468cbbf..30d8e4371c0b1394b62e4dc40837ca9f71277df9 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Frequency.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Frequency.java
@@ -22,8 +22,7 @@ import java.util.Set;
 
 import javax.xml.bind.annotation.XmlRootElement;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.Knowledge;
 import fr.inrae.agroclim.indicators.model.Parameter;
 import fr.inrae.agroclim.indicators.model.data.DailyData;
@@ -71,8 +70,7 @@ public final class Frequency extends SimpleIndicator implements Detailable {
     }
 
     @Override
-    public double computeSingleValue(final Resource<? extends DailyData> data)
-            throws TechnicalException, FunctionalException {
+    public double computeSingleValue(final Resource<? extends DailyData> data) throws IndicatorsException {
         final double cent = 100.;
         final int days = data.getData().size();
         return numberOfDays.compute(data) * cent / days;
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/InjectedParameter.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/InjectedParameter.java
index 778617d9e05d26833fb01e9de7fbe08cace7ad9a..8ac932829f16fe5119e4fcc82f85a6ccac629af7 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/InjectedParameter.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/InjectedParameter.java
@@ -30,8 +30,7 @@ import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlType;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.Knowledge;
 import fr.inrae.agroclim.indicators.model.Parameter;
 import fr.inrae.agroclim.indicators.model.data.DailyData;
@@ -77,8 +76,7 @@ public final class InjectedParameter extends SimpleIndicator {
     private Double parameterValue;
 
     @Override
-    public double computeSingleValue(final Resource<? extends DailyData> data)
-            throws TechnicalException, FunctionalException {
+    public double computeSingleValue(final Resource<? extends DailyData> data) throws IndicatorsException {
         if (parameterValue == null) {
             return 0;
         }
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/MaxWaveLength.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/MaxWaveLength.java
index fa8168791f94e52fd306d6432a3c791d95f0c6fc..dca5dd741d6f1b88615d78d0c8730e1ba48d86fd 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/MaxWaveLength.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/MaxWaveLength.java
@@ -20,8 +20,8 @@ import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlRootElement;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
+import fr.inrae.agroclim.indicators.exception.type.ComputationErrorType;
 import fr.inrae.agroclim.indicators.model.data.DailyData;
 import fr.inrae.agroclim.indicators.model.data.Resource;
 import lombok.EqualsAndHashCode;
@@ -67,13 +67,12 @@ public final class MaxWaveLength extends SimpleIndicatorWithCriteria implements
     }
 
     @Override
-    public double computeSingleValue(final Resource<? extends DailyData> climatic)
-            throws TechnicalException, FunctionalException {
+    public double computeSingleValue(final Resource<? extends DailyData> climatic) throws IndicatorsException {
         if (getCriteria() == null) {
-            throw new RuntimeException("criteria must not be null!");
+            throw new IndicatorsException(ComputationErrorType.CRITERIA_NULL);
         }
         if (threshold == null) {
-            throw new RuntimeException("threshold must not be null!");
+            throw new IndicatorsException(ComputationErrorType.THRESHOLD_NULL);
         }
         int waveLength = 0;
         int max = 0;
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/NumberOfDays.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/NumberOfDays.java
index 048102c85f8a0d326212579d283348c38977a6bd..9ffa5fbfa3419612efaca7b1b8bddcac45e4aef5 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/NumberOfDays.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/NumberOfDays.java
@@ -20,8 +20,7 @@ import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlRootElement;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.data.DailyData;
 import fr.inrae.agroclim.indicators.model.data.Resource;
 
@@ -57,8 +56,7 @@ public final class NumberOfDays extends SimpleIndicatorWithCriteria implements D
     }
 
     @Override
-    public double computeSingleValue(final Resource<? extends DailyData> res)
-            throws TechnicalException, FunctionalException {
+    public double computeSingleValue(final Resource<? extends DailyData> res) throws IndicatorsException {
         if (getCriteria() == null) {
             return res.getData().size();
         }
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/NumberOfWaves.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/NumberOfWaves.java
index 06b8074f0fbfb367eae417f773f180f7de9f3c10..24ce21faa881fab6830704bc30ef7a7a97901677 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/NumberOfWaves.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/NumberOfWaves.java
@@ -20,8 +20,8 @@ import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlElement;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
+import fr.inrae.agroclim.indicators.exception.type.ComputationErrorType;
 import fr.inrae.agroclim.indicators.model.Parameter;
 import fr.inrae.agroclim.indicators.model.data.DailyData;
 import fr.inrae.agroclim.indicators.model.data.Resource;
@@ -77,13 +77,12 @@ public final class NumberOfWaves extends SimpleIndicatorWithCriteria implements
     }
 
     @Override
-    public double computeSingleValue(final Resource<? extends DailyData> climatic)
-            throws TechnicalException, FunctionalException {
+    public double computeSingleValue(final Resource<? extends DailyData> climatic) throws IndicatorsException {
         if (getCriteria() == null) {
-            throw new RuntimeException("criteria must not be null!");
+            throw new IndicatorsException(ComputationErrorType.CRITERIA_NULL);
         }
         if (nbDays == null) {
-            throw new RuntimeException("nbDays must not be null!");
+            throw new IndicatorsException(ComputationErrorType.WRONG_DEFINITION, "nbDays must not be null!");
         }
         int index = 0;
         int nb = 0;
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/PhaseLength.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/PhaseLength.java
index 0347a8711f7f9f2c9575a3d62f5532b215fd7ea1..12ace4a41ff58490ad17a8766d3355f6e1f9543e 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/PhaseLength.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/PhaseLength.java
@@ -19,8 +19,7 @@ package fr.inrae.agroclim.indicators.model.indicator;
 import java.util.HashSet;
 import java.util.Set;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.data.DailyData;
 import fr.inrae.agroclim.indicators.model.data.Resource;
 import fr.inrae.agroclim.indicators.model.data.Variable;
@@ -50,8 +49,7 @@ implements Detailable {
     }
 
     @Override
-    public double computeSingleValue(final Resource<? extends DailyData> data)
-            throws TechnicalException, FunctionalException {
+    public double computeSingleValue(final Resource<? extends DailyData> data) throws IndicatorsException {
         return data.getData().size();
     }
 
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/PotentialSowingDaysFrequency.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/PotentialSowingDaysFrequency.java
index ffc4ccc870feaf397cf36abc7e63b9109bdb3182..54dc714d70d4099d255081646ff4767992e4fa85 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/PotentialSowingDaysFrequency.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/PotentialSowingDaysFrequency.java
@@ -24,8 +24,7 @@ import java.util.Set;
 
 import javax.xml.bind.annotation.XmlType;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.Knowledge;
 import fr.inrae.agroclim.indicators.model.Parameter;
 import fr.inrae.agroclim.indicators.model.data.DailyData;
@@ -35,6 +34,7 @@ import fr.inrae.agroclim.indicators.model.data.climate.ClimaticDailyData;
 import fr.inrae.agroclim.indicators.model.data.climate.ClimaticResource;
 import fr.inrae.agroclim.indicators.util.Doublet;
 import java.util.ArrayList;
+import java.util.Objects;
 import java.util.Optional;
 import lombok.Getter;
 import lombok.Setter;
@@ -158,12 +158,8 @@ public final class PotentialSowingDaysFrequency extends SimpleIndicator implemen
     }
 
     @Override
-    public double computeSingleValue(
-            final Resource<? extends DailyData> resource)
-                    throws TechnicalException, FunctionalException {
-        if (resource == null) {
-            throw new IllegalArgumentException("resource must not be null!");
-        }
+    public double computeSingleValue(final Resource<? extends DailyData> resource) throws IndicatorsException {
+        Objects.requireNonNull(resource, "Resource must not be null!");
         if (!(resource instanceof ClimaticResource)) {
             throw new IllegalArgumentException(
                     getClass().getName()
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Quotient.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Quotient.java
index b3d9e5203f9f1ca8fea3300b4dfcdccd6c1babd9..87ec9eefcab85ffbb41212dcb21e64cdf949d447 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Quotient.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Quotient.java
@@ -28,8 +28,8 @@ import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlType;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
+import fr.inrae.agroclim.indicators.exception.type.ComputationErrorType;
 import fr.inrae.agroclim.indicators.model.Knowledge;
 import fr.inrae.agroclim.indicators.model.Parameter;
 import fr.inrae.agroclim.indicators.model.data.DailyData;
@@ -93,35 +93,27 @@ public final class Quotient extends SimpleIndicator implements Detailable, Indic
     }
 
     @Override
-    public double computeSingleValue(final Resource<? extends DailyData> data)
-            throws TechnicalException, FunctionalException {
+    public double computeSingleValue(final Resource<? extends DailyData> data) throws IndicatorsException {
         if (dividend == null) {
-            throw new TechnicalException(
-                    "Dividend indicator should never be null! Check "
-                            + "this indicator " + getId());
+            throw new IndicatorsException(ComputationErrorType.QUOTIENT_DIVIDEND_NULL);
         }
         if (divisor == null) {
-            throw new TechnicalException(
-                    "Divisor indicator should never be null! Check "
-                            + "this indicator " + getId());
+            throw new IndicatorsException(ComputationErrorType.QUOTIENT_DIVISOR_NULL);
         }
         final double dividendValue;
         try {
             dividendValue = dividend.compute(data);
-        } catch (final NullPointerException e) {
-            throw new FunctionalException("Computed value of divident "
-                    + dividend.getId() + " is null", e);
+        } catch (final IndicatorsException e) {
+            throw new IndicatorsException(ComputationErrorType.QUOTIENT_DIVIDEND_EXCEPTION, e, dividend.getId());
         }
         final double divisorValue;
         try {
             divisorValue = divisor.compute(data);
-        } catch (final NullPointerException e) {
-            throw new FunctionalException("Computed value of divisor "
-                    + divisor.getId() + " is null", e);
+        } catch (final IndicatorsException e) {
+            throw new IndicatorsException(ComputationErrorType.QUOTIENT_DIVISOR_EXCEPTION, e, divisor.getId());
         }
         if (divisorValue == 0.0) {
-            throw new FunctionalException("Cannot compute value for '" + getId() + "' as value of divisor '"
-                    + divisor.getId() + "' is 0.");
+            throw new IndicatorsException(ComputationErrorType.QUOTIENT_DIVISOR_ZERO, divisor.getId());
         }
         return dividendValue / divisorValue;
     }
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/SimpleIndicator.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/SimpleIndicator.java
index 5afa03a72d98cd4a03cfdf583c47bbb1be26cd69..f09ec130d720ee6cd9e0520e865bbfc7ba84792f 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/SimpleIndicator.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/SimpleIndicator.java
@@ -18,8 +18,7 @@ package fr.inrae.agroclim.indicators.model.indicator;
 
 import javax.xml.bind.annotation.XmlSeeAlso;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.EvaluationType;
 import fr.inrae.agroclim.indicators.model.data.DailyData;
 import fr.inrae.agroclim.indicators.model.data.Resource;
@@ -73,19 +72,10 @@ public abstract class SimpleIndicator extends Indicator {
      * @param data
      *            climatic resource used to compute indicator.
      * @return normalized result
-     * @throws fr.inrae.agroclim.indicators.exception.TechnicalException
-     * @throws fr.inrae.agroclim.indicators.exception.FunctionalException
      */
     @Override
-    public final Double compute(final Resource<? extends DailyData> data)
-            throws TechnicalException, FunctionalException {
-        double value;
-        try {
-            value  = computeSingleValue(data);
-        } catch (TechnicalException | FunctionalException e) {
-            LOGGER.warn("Indicator '{}' failed to compute single value: {}.", getId(), e.getMessage());
-            throw e;
-        }
+    public final Double compute(final Resource<? extends DailyData> data) throws IndicatorsException {
+        double value = computeSingleValue(data);
         setNotNormalizedValue(value);
         if (getNormalizationFunction() != null
                 && getType() != EvaluationType.WITHOUT_AGGREGATION) {
@@ -101,13 +91,10 @@ public abstract class SimpleIndicator extends Indicator {
      * @param data
      *            resource with daily data
      * @return value
-     * @throws TechnicalException
-     *             exception
-     * @throws FunctionalException
+     * @throws IndicatorsException
      *             exception
      */
-    public abstract double computeSingleValue(Resource<? extends DailyData> data)
-                    throws TechnicalException, FunctionalException;
+    public abstract double computeSingleValue(Resource<? extends DailyData> data) throws IndicatorsException;
 
     @Override
     public final void fireValueUpdated() {
diff --git a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Tamm.java b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Tamm.java
index d0904fbbbfeb178c281d9b1a0d77bfa57b5bb07a..a7bf8a88592f2fc3b816ed59b9e22edb397cdb12 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Tamm.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/model/indicator/Tamm.java
@@ -25,8 +25,7 @@ import java.util.Set;
 
 import javax.xml.bind.annotation.XmlType;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.Knowledge;
 import fr.inrae.agroclim.indicators.model.Parameter;
 import fr.inrae.agroclim.indicators.model.data.DailyData;
@@ -154,8 +153,7 @@ public final class Tamm extends SimpleIndicator implements Detailable {
     }
 
     @Override
-    public double computeSingleValue(final Resource<? extends DailyData> data)
-            throws TechnicalException, FunctionalException {
+    public double computeSingleValue(final Resource<? extends DailyData> data) throws IndicatorsException {
         return data.getData().stream() //
                 .map(this::computeDailyValue) //
                 .reduce((v1, v2) -> v1 + v2).orElseThrow();
diff --git a/src/main/java/fr/inrae/agroclim/indicators/util/ErrorTypeUtils.java b/src/main/java/fr/inrae/agroclim/indicators/util/ErrorTypeUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..443a9d909b447afe7df66848afdbd50d5da5f49b
--- /dev/null
+++ b/src/main/java/fr/inrae/agroclim/indicators/util/ErrorTypeUtils.java
@@ -0,0 +1,60 @@
+package fr.inrae.agroclim.indicators.util;
+
+import fr.inrae.agroclim.indicators.exception.ErrorType;
+import fr.inrae.agroclim.indicators.resources.I18n;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Helper methods to ensure all implementations of {@link ErrorType} are well defined.
+ *
+ * @author Olivier Maury
+ */
+public interface ErrorTypeUtils {
+
+    static Set<String> getDuplicatedCodes(final Class<? extends Enum<?>> clazz) {
+        final Set<String> duplicates = new HashSet<>();
+        final Set<String> codes = new HashSet<>();
+        final Enum<?>[] values = clazz.getEnumConstants();
+        for (final Enum<?> value : values) {
+            final ErrorType k = (ErrorType) value;
+            if (codes.contains(k.getSubCode())) {
+                duplicates.add(k.getSubCode());
+            }
+            codes.add(k.getSubCode());
+        }
+        return duplicates;
+    }
+
+    /**
+     * Get missing translations for the Enum in the bundle for the locales.
+     *
+     * @param clazz      enum class
+     * @param bundleName bundle name for the Properties file.
+     * @param locales    locales of translations
+     * @return missing translations
+     */
+    static Map<Locale, Map<Class<?>, List<String>>> getMissingTranslations(final Class<? extends Enum<?>> clazz,
+            final String bundleName, final List<Locale> locales) {
+        final Map<Locale, Map<Class<?>, List<String>>> missing = new HashMap<>();
+        for (final Locale locale : locales) {
+            final I18n i18n = new I18n(bundleName, locale);
+            final Enum<?>[] values = clazz.getEnumConstants();
+            for (final Enum<?> value : values) {
+                final ErrorType k = (ErrorType) value;
+                final String tr = i18n.get(k.getI18nKey());
+                if (tr.startsWith("!") && tr.endsWith("!")) {
+                    missing.computeIfAbsent(locale, l -> new HashMap<>());
+                    missing.get(locale).computeIfAbsent(clazz, l -> new ArrayList<>());
+                    missing.get(locale).get(clazz).add(k.getI18nKey());
+                }
+            }
+        }
+        return missing;
+    }
+}
diff --git a/src/main/java/fr/inrae/agroclim/indicators/xml/XMLUtil.java b/src/main/java/fr/inrae/agroclim/indicators/xml/XMLUtil.java
index c2a52340109b2a90f8b95c6b3a18e80c395a2991..8dacd948f82a1d60328f7cdc7971347814f76776 100644
--- a/src/main/java/fr/inrae/agroclim/indicators/xml/XMLUtil.java
+++ b/src/main/java/fr/inrae/agroclim/indicators/xml/XMLUtil.java
@@ -16,6 +16,7 @@
  */
 package fr.inrae.agroclim.indicators.xml;
 
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -38,12 +39,11 @@ import org.w3c.dom.ls.LSInput;
 import org.w3c.dom.ls.LSResourceResolver;
 import org.xml.sax.SAXException;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.type.XmlErrorType;
 import fr.inrae.agroclim.indicators.model.EvaluationSettings;
 import fr.inrae.agroclim.indicators.model.Knowledge;
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.util.HashMap;
 import java.util.Map;
 import lombok.Getter;
 import lombok.Setter;
@@ -220,10 +220,9 @@ public abstract class XMLUtil {
      * @return  Public ID of doctype → Resource for the doctype file.
      */
     public static Map<String, InputStream> getDtds() {
-        final Map<String, InputStream> map = new HashMap<>();
-        map.put(DTD_PUBLIC_ID_EVALUATION, getResourceAsStream("evaluation.dtd"));
-        map.put(DTD_PUBLIC_ID_KNOWLEDGE, getResourceAsStream("knowledge.dtd"));
-        return map;
+        return Map.of(//
+                DTD_PUBLIC_ID_EVALUATION, getResourceAsStream("evaluation.dtd"), //
+                DTD_PUBLIC_ID_KNOWLEDGE, getResourceAsStream("knowledge.dtd"));
     }
 
     /**
@@ -244,17 +243,16 @@ public abstract class XMLUtil {
      * @param clazz
      *            used classes
      * @return object for the XML file
-     * @throws TechnicalException
+     * @throws IndicatorsException
      *             exception from JAXBException
      */
-    public static Object load(final File xmlFile, final Class<?>... clazz)
-            throws TechnicalException {
+    public static Object load(final File xmlFile, final Class<?>... clazz) throws IndicatorsException {
         try (InputStream inputStream = new FileInputStream(xmlFile);) {
             return loadResource(inputStream, clazz);
         } catch (final FileNotFoundException ex) {
-            throw new TechnicalException("File not found " + xmlFile.getAbsolutePath(), ex);
+            throw new IndicatorsException(XmlErrorType.FILE_NOT_FOUND, ex, xmlFile.getAbsolutePath());
         } catch (final IOException ex) {
-            throw new TechnicalException("Unable to load ", ex);
+            throw new IndicatorsException(XmlErrorType.UNABLE_TO_LOAD, ex);
         }
     }
 
@@ -266,13 +264,13 @@ public abstract class XMLUtil {
      * @param clazz
      *            used classes
      * @return object for the XML file
-     * @throws TechnicalException
+     * @throws IndicatorsException
      *             exception from JAXBException
      */
     public static Object loadResource(final InputStream inputStream, final Class<?>... clazz)
-            throws TechnicalException {
+            throws IndicatorsException {
         if (inputStream == null) {
-            throw new TechnicalException("InputStream should not be null!");
+            throw new IndicatorsException(XmlErrorType.FILE_NOT_FOUND, "InputStream should not be null!");
         }
         try {
             UnmarshallerBuilder builder = new UnmarshallerBuilder();
@@ -287,7 +285,7 @@ public abstract class XMLUtil {
             final PrintWriter pw = new PrintWriter(buffer);
             ex.printStackTrace(pw);
             LOGGER.fatal("Unable to deserialize : {}", buffer);
-            throw new TechnicalException("Unable to load", ex);
+            throw new IndicatorsException(XmlErrorType.UNABLE_TO_LOAD, ex);
         }
     }
 
@@ -301,15 +299,16 @@ public abstract class XMLUtil {
      *            output stream
      * @param clazz
      *            used classes
-     * @throws TechnicalException
+     * @throws IndicatorsException
      *             exception from JAXBException
      */
     public static void serialize(final Object o, final OutputStream outputStream, final Class<?>... clazz)
-                    throws TechnicalException {
+                    throws IndicatorsException {
         try (OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);) {
             serialize(o, writer, clazz);
         } catch (final IOException ex) {
-            throw new TechnicalException("Unable to create OutputStreamWriter : " + o, ex);
+            throw new IndicatorsException(XmlErrorType.UNABLE_TO_SERIALIZE, ex,
+                    "Unable to create OutputStreamWriter : " + o);
         }
     }
 
@@ -322,16 +321,17 @@ public abstract class XMLUtil {
      *            file path
      * @param clazz
      *            used classes
-     * @throws TechnicalException
+     * @throws IndicatorsException
      *             exception from JAXBException
      */
     public static void serialize(final Object o, final String fileName, final Class<?>... clazz)
-            throws TechnicalException {
+            throws IndicatorsException {
         try (FileOutputStream stream = new FileOutputStream(fileName);) {
             serialize(o, new OutputStreamWriter(stream, StandardCharsets.UTF_8), clazz);
         } catch (final IOException ex) {
             LOGGER.catching(ex);
-            throw new TechnicalException("Unable to create FileOutputStream : " + o, ex);
+            throw new IndicatorsException(XmlErrorType.UNABLE_TO_SERIALIZE, ex,
+                    "Unable to create FileOutputStream : " + o);
         }
     }
 
@@ -344,11 +344,11 @@ public abstract class XMLUtil {
      *            writer
      * @param clazz
      *            used classes
-     * @throws TechnicalException
+     * @throws IndicatorsException
      *             exception from JAXBException
      */
     private static void serialize(final Object o, final Writer writer, final Class<?>... clazz)
-            throws TechnicalException {
+            throws IndicatorsException {
         try {
             final MarshallerBuilder builder = new MarshallerBuilder();
             if (o instanceof EvaluationSettings) {
@@ -365,7 +365,7 @@ public abstract class XMLUtil {
             final PrintWriter pw = new PrintWriter(buffer);
             ex.printStackTrace(pw);
             LOGGER.fatal("Unable to serialize : " + buffer.toString(), ex);
-            throw new TechnicalException("Unable to serialize : " + o, ex);
+            throw new IndicatorsException(XmlErrorType.UNABLE_TO_SERIALIZE, ex);
         }
     }
 
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 88e38656bb52d6896824fadf56ee7b4cdc1e445c..d3f3036b354244f232180e1337a3aa8e34966435 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -30,6 +30,7 @@ module fr.inrae.agroclim.indicators {
     requires transitive java.desktop;
     requires org.apache.logging.log4j;
     requires org.apache.logging.log4j.core;
+    requires org.json;
 
     exports fr.inrae.agroclim.indicators.exception;
     exports fr.inrae.agroclim.indicators.model;
diff --git a/src/main/resources/fr/inrae/agroclim/indicators/resources/messages.properties b/src/main/resources/fr/inrae/agroclim/indicators/resources/messages.properties
index b9d6735b59050ef2239c00aa5d933fbbac1e0f55..441aca5ab2269f4c20e4028acfcb49ca65f42153 100644
--- a/src/main/resources/fr/inrae/agroclim/indicators/resources/messages.properties
+++ b/src/main/resources/fr/inrae/agroclim/indicators/resources/messages.properties
@@ -15,31 +15,62 @@
 # along with Indicators. If not, see <https://www.gnu.org/licenses/>.
 #
 #Errors
+error.category.computation=Evaluation computation
+error.category.resources=Evaluation resources
+error.category.xml=XML parsing
 error.climate.jdbc.query=Error while query execution "{0}"!
 error.climate.dates=For the year {0}, available dates are from {1} to {2}.
-error.climate.missing=No climatic daily data for phase {0}-{1} (days {2} -> {3}) in {4}.
 error.climate.no.data=No data were retrieved.
 error.climate.wrong.headers=Wrong number of headers! {0} headers are in the file: {1}, but {2} headers are defined: {3}.
+error.computation.wrong.definition=The indicator "{0}" is not well defined: {1}.
+error.computation.wrong.definition.criteria.isnt.nocriteria.simplecriteria=criteria is neither NoCriteria nor SimpleCriteria!
 error.evaluation.resource=Error for resources!
 error.evaluation.resource.climatic=Error for climatic data set in resources!
-error.evaluation.resource.climatic.empty=No climatic data set in resources!
-error.evaluation.resource.climatic.size.wrong=The number of climatic data does not match whole years!
-error.evaluation.resource.climatic.years=Error for years in climatic data set in resources!
-error.evaluation.resource.climatic.years.empty=No year in climatic data set in resources!
-error.evaluation.resource.climatic.years.missing=Climatic data set in resources miss for years {0}!
-error.evaluation.resource.cropdevelopmentyears.null=The property ResourceManager.cropDevelopmentYears must not be null!
-error.evaluation.resource.pheno=Error for phenological data set in resources!
-error.evaluation.resource.pheno.empty=No phenological data set in resources!
-error.evaluation.resource.pheno.years=Error for years in phenological data set in resources!
-error.evaluation.resource.pheno.years.empty=No year in phenological data set in resources!
-error.evaluation.resource.pheno.years.missing=Phenological data set in resources miss for years {0}!
-error.evaluation.resource.soil=Error for soil data!
-error.evaluation.resource.soil.size.wrong=The number of soil data does not match whole years!
-error.evaluation.resource.variables=Error for the property ResourceManager.variables!
-error.evaluation.resource.variables.empty=The property ResourceManager.variables must not be empty!
-error.evaluation.resource.variables.missing=The property ResourceManager.variables must not be null!
+error.computation.formula=Error while executing expression "{0}".
+error.computation.formula.aggregation.null=Aggregation must not be null.
+error.computation.formula.expression.blank=Expression must not be empty.
+error.computation.formula.expression.null=Expression must not be null.
+error.computation.formula.expression.parenthesis=Invalid expression "{0}": missing parenthesis.
+error.computation.formula.expression.parsing=Invalid expression "{0}": parsing error.
+error.computation.formula.function.unknown=Invalid expression "{0}": the function "{1}" is unknown or ambiguous. If the function exists check arguments.
+error.computation.formula.variable.undefined=Invalid expression "{0}": the variable "{1}" is not defined.
+error.computation.input=Indicator computation failed due to an invalid input.
+error.computation.input.composite.computation=An indicator in the CompositeIndicator "{0}" failed to compute.
+error.computation.input.data.null=Daily data must not be null.
+error.computation.input.quotient.dividend.exception=Computation of the dividend indicator "{0}" in Quotient failed.
+error.computation.input.quotient.divisor.exception=Computation of the divisor indicator "{0}" in Quotient failed.
+error.computation.input.quotient.divisor.zero=The result of the divisor indicator "{0}" is zero.
+error.computation.input.variable.value.null=The value for the variable "{0}" must not be null at "{1}".
+error.computation.wrong.definition.criteria.null=The "criteria" property must not be null.
+error.computation.wrong.definition.quotient.dividend.null=Dividend indicator must not be null.
+error.computation.wrong.definition.quotient.divisor.null=Divisor indicator must not be null.
+error.computation.wrong.definition.threshold.null=The threshold must not be null.
+error.computation.wrong.definition.variable.name.null=The variable name must not be null.
 error.day.duplicate={0}: line {1}: this is the same date as previous line "{2}".
 error.day.missing={0}: line {1}: a day is missing before "{2}".
+error.resources.climate=Error for climate data
+error.resources=Error for resources
+error.resources.cropdevelopment.years=The number of development years for the crop is not set.
+error.resources.soil=Error for soil data!
+error.resources.soil.size.wrong=The number of soil data does not match whole years!
+error.resources.variables=Error for the property ResourceManager.variables!
+error.resources.variables.empty=The property ResourceManager.variables must not be empty!
+error.resources.variables.missing=The property ResourceManager.variables must not be null!
+error.resources.climate.empty.for.phase=No climatic daily data for phase {0}-{1} (from {2} to {3}) in {4}. Available data between {5} and {6}.
+error.resources.climate.empty=No climatic data set in resources!
+error.resources.climate.size.wrong=The number of climatic data does not match whole years!
+error.resources.climate.years.empty=No year in climatic data set in resources!
+error.resources.climate.years=Error for years in climatic data set in resources!
+error.resources.climate.years.missing=Climatic data set in resources miss for years {0}!
+error.resources.cropdevelopmentyears.null=The property ResourceManager.cropDevelopmentYears must not be null!
+error.resources.pheno.empty=No phenological data set in resources!
+error.resources.pheno=Error for phenological data set in resources!
+error.resources.pheno.years.empty=No year in phenological data set in resources!
+error.resources.pheno.years=Error for years in phenological data set in resources!
+error.resources.pheno.years.missing=Phenological data set in resources miss for years {0}!
+error.xml.file.not.found=The XML file "{0}" was not found.
+error.xml.unable.to.load=XML loading failed.
+error.xml.unable.to.serialize=XML serializing failed.
 error.day.null={0}: line {1}: day is required.
 error.day.succession={0}: line {1}: the day "{2}" is ealier than the day of the previous line "{3}".
 error.date.notread=The start and/or end date could not be read
@@ -62,6 +93,8 @@ warning.tmax.inferiorto.tmin={0}: line {1}: minimal temperature must be inferior
 #eg. CLIMATE: line 0: Climatic variable is missing
 warning.missing={0}: line {1}: {2} is missing
 warning.soilcalculator.4stages=Phenological stages are provided on {0} stages, but SoilCalculator needs 4 stages!
+warning.soilcalculator.4stages[none]=No phenological stage provided whereas soil water balance needs 4 stages!
+warning.soilcalculator.4stages[one]=One phenological stage provided whereas soil water balance needs 4 stages!
 warning.soilloader.missing=No configuration to compute soil water balance or water reserve which are needed for some indicators.
 
 markdown.description.daily=List of available indicators at daily timescale
@@ -69,6 +102,13 @@ markdown.description.hourly=List of available indicators at hourly timescale
 markdown.title.daily=Indicators at daily timescale
 markdown.title.hourly=Indicators at hourly timescale
 markdown.keywords="indicator","agroclimatic","ecoclimatic"
+markdown.error.category=Category
+markdown.error.description=List of error types: codes and descriptions.
+markdown.error.fullcode=Error code
+markdown.error.keywords="library","error codes"
+markdown.error.message=Error message
+markdown.error.name=Error name
+markdown.error.title=Error types
 markdown.indicators=Indicators
 markdown.indicators.daily={0} daily indicators
 markdown.indicators.hourly={0} hourly indicators
@@ -93,14 +133,6 @@ normalization.Sigmoid=Sigmoid
 EvaluationType.WITH_AGGREGATION.name=with aggregation
 EvaluationType.WITHOUT_AGGREGATION.name=without aggregation
 
-JEXLFormula.error.execution=Error while executing expression "{0}".
-JEXLFormula.error.expression.null=Expression must not be null.
-JEXLFormula.error.expression.empty=Expression must not be empty.
-JEXLFormula.error.expression.parenthesis=Invalid expression "{0}": missing parenthesis.
-JEXLFormula.error.expression.parsing=Invalid expression "{0}": parsing error.
-JEXLFormula.error.variable.undefined=Invalid expression "{0}": the variable "{1}" is not defined.
-JEXLFormula.error.function.unknown=Invalid expression "{0}": the function "{1}" is unknown or ambiguous. If the function exists check arguments.
-
 MathMethod.avg.description = Returns the average of values passed as function parameter.
 MathMethod.exp.description = Returns Euler's number e raised to the power of a double value. Special cases:\n\n\
 If the argument is NaN, the result is NaN.\n\
@@ -140,4 +172,4 @@ Variable.RAIN.description=Rain precipitation [mm].
 Variable.RH.description=Relative humidity [%].
 Variable.SOILWATERCONTENT.description=Soil water content [% mass].
 Variable.WATER_RESERVE.description=Soil water reserve [mm].
-Variable.WIND.description=Wind speed [m/s].
\ No newline at end of file
+Variable.WIND.description=Wind speed [m/s].
diff --git a/src/main/resources/fr/inrae/agroclim/indicators/resources/messages_fr.properties b/src/main/resources/fr/inrae/agroclim/indicators/resources/messages_fr.properties
index 46bb1c77c87bc526e2b2f626b584411baa63fae0..afbe1374145513199faafeadbdd029255a7b1659 100644
--- a/src/main/resources/fr/inrae/agroclim/indicators/resources/messages_fr.properties
+++ b/src/main/resources/fr/inrae/agroclim/indicators/resources/messages_fr.properties
@@ -20,28 +20,56 @@ warning.soilcalculator.4stages[none]=Aucun stade ph\u00e9nologique n\u2019est fo
 warning.soilcalculator.4stages[one]=Un stade ph\u00e9nologique est fourni, mais le calcul du bilan hydrique en n\u00e9cessite 4\u00a0!
 warning.soilloader.missing=La configuration pour le calcul du bilan hydrique manque alors que des indicateurs portent sur la teneur en eau du sol ou la r\u00e9serve utile.
 warning.tmax.inferiorto.tmin={0}\u00a0: ligne {1}\u00a0: la temp\u00e9rature minimale doit \u00eatre inf\u00e9rieure \u00e0 la temp\u00e9rature maximale.
+error.category.computation=Calcul de l\u2019\u00e9valuation
+error.category.resources=Ressources de l\u2019\u00e9valuation
+error.category.xml=Lecture du fichier XML
 error.climate.dates=Pour l\u2019ann\u00e9e {0}, les dates lues vont du {1} au {2}.
-error.climate.missing=Aucune donn\u00e9e climatique pour la phase {0}-{1} (jours {2} -> {3}) en {4}.
 error.climate.no.data=Aucune donn\u00e9e n\u2019a \u00e9t\u00e9 r\u00e9cup\u00e9r\u00e9e.
-error.climate.wrong.headers=Mauvais nombre d\u2019ent\u00eates\u00a0! {0} ent\u00eates sont dans le fichier : {1}, mais {2} ent\u00eates sont d\u00e9finis : {3}.
-error.evaluation.resource=Erreur pour les ressources\u00a0!
+error.climate.wrong.headers=Mauvais nombre d\u2019ent\u00eates\u00a0! {0} ent\u00eates sont dans le fichier\u00a0: {1}, mais {2} ent\u00eates sont d\u00e9finis\u00a0: {3}.
+error.computation.formula.aggregation.null=L\u2019agr\u00e9gation ne peut \u00eatre nulle.
+error.computation.formula=Erreur lors de l\u2019ex\u00e9cution de l\u2019expression \u00ab\u00a0{0}\u00a0\u00bb.
+error.computation.formula.expression.blank=L\u2019expression ne peut \u00eatre vide.
+error.computation.formula.expression.null=L\u2019expression ne peut \u00eatre nulle.
+error.computation.formula.expression.parenthesis=Expression non valide \u00ab\u00a0{0}\u00a0\u00bb\u00a0: des parenth\u00e8ses manquent.
+error.computation.formula.expression.parsing=Expression non valide \u00ab\u00a0{0}\u00a0\u00bb\u00a0: erreur d\u2019analyse.
+error.computation.formula.function.unknown=Expression non valide \u00ab\u00a0{0}\u00a0\u00bb\u00a0: la fonction \u00ab\u00a0{1}\u00a0\u00bb est inconnue ou ambig\u00fce. Si la fonction existe, v\u00e9rifiez les arguments.
+error.computation.formula.variable.undefined=Expression non valide \u00ab\u00a0{0}\u00a0\u00bb\u00a0: la variable \u00ab\u00a0{1}\u00a0\u00bb n\u2019est pas d\u00e9finie.
+error.computation.input=Le calcul de l\u2019indicateur a \u00e9chou\u00e9 \u00e0 cause d\u2019une entr\u00e9e invalide.
+error.computation.input.composite.computation=Le calcul d\u2019un indicateur composant \u00ab\u00a0{0}\u00a0\u00bb a \u00e9chou\u00e9.
+error.computation.input.data.null=Les donn\u00e9es journali\u00e8res ne doivent pas \u00eatre nulles.
+error.computation.input.quotient.dividend.exception=Le calcul de l\u2019indicateur num\u00e9rateur \u00ab\u00a0{0}\u00a0\u00bb dans Quotient a \u00e9chou\u00e9.
+error.computation.input.quotient.divisor.exception=Le calcul de l\u2019indicateur d\u00e9nominateur \u00ab\u00a0{0}\u00a0\u00bb dans Quotient a \u00e9chou\u00e9.
+error.computation.input.quotient.divisor.zero=Le r\u00e9sultat du calcul de l\u2019indicateur d\u00e9nominateur \u00ab\u00a0{0}\u00a0\u00bb est z\u00e9ro.
+error.computation.input.variable.value.null=La valeur de la variable \u00ab\u00a0{0}\u00a0\u00bb ne doit pas \u00eatre nulle pour le jour \u00ab\u00a0{1}\u00a0\u00bb.
+error.computation.wrong.definition.criteria.isnt.nocriteria.simplecriteria=La propri\u00e9t\u00e9 \u00ab\u00a0criteria\u00a0\u00bb n\u2019est ni NoCriteria ni SimpleCriteria\u00a0!
+error.computation.wrong.definition.criteria.null=La propri\u00e9t\u00e9 \u00ab\u00a0criteria\u00a0\u00bb ne doit pas \u00eatre nulle.
+error.computation.wrong.definition=L\u2019indicateur \u00ab\u00a0{0}\u00a0\u00bb n\u2019est pas bien d\u00e9fini\u00a0: {1}.
+error.computation.wrong.definition.quotient.dividend.null=L\u2019indicateur num\u00e9rateur ne doit pas \u00eatre nul.
+error.computation.wrong.definition.quotient.divisor.null=L\u2019indicateur d\u00e9nominateur ne doit pas \u00eatre nul.
+error.computation.wrong.definition.threshold.null=Le seuil ne doit pas \u00eatre null.
+error.computation.wrong.definition.variable.name.null=Le nom de la variable ne doit pas \u00eatre nul.
 error.evaluation.resource.climatic=Erreur pour les donn\u00e9es climatiques d\u00e9finies dans les ressources\u00a0!
-error.evaluation.resource.climatic.empty=Aucune donn\u00e9e climatique d\u00e9finie dans les ressources\u00a0!
-error.evaluation.resource.climatic.size.wrong=Le nombre de donn\u00e9es climatiques ne correspond pas \u00e0 des ann\u00e9es enti\u00e8res\u00a0!
-error.evaluation.resource.climatic.years.empty=Erreur pour les ann\u00e9es dans les donn\u00e9es climatiques d\u00e9finies des ressources\u00a0!
-error.evaluation.resource.climatic.years.empty=Aucune ann\u00e9e dans les donn\u00e9es climatiques d\u00e9finies des ressources\u00a0!
-error.evaluation.resource.climatic.years.missing=Les donn\u00e9es climatiques d\u00e9finies dans les ressources manquent pour les ann\u00e9es {0}\u00a0!
-error.evaluation.resource.cropdevelopmentyears.null=La propri\u00e9t\u00e9 ResourceManager.cropDevelopmentYears ne doit pas \u00eatre nulle\u00a0!
-error.evaluation.resource.pheno=Erreur pour les donn\u00e9es ph\u00e9nologiques d\u00e9finies dans les ressources\u00a0!
-error.evaluation.resource.pheno.empty=Aucune donn\u00e9e ph\u00e9nologique d\u00e9finie dans les ressources\u00a0!
-error.evaluation.resource.pheno.years=Erreur pour les ann\u00e9es dans les donn\u00e9es ph\u00e9nologiques des ressources\u00a0!
-error.evaluation.resource.pheno.years.empty=Aucune ann\u00e9e dans les donn\u00e9es ph\u00e9nologiques des ressources\u00a0!
-error.evaluation.resource.pheno.years.missing=Les donn\u00e9es ph\u00e9nologiques d\u00e9finies dans les ressources manquent pour les ann\u00e9es {0}\u00a0!
-error.evaluation.resource.soil=Erreur pour l donn\u00e9es de sol\u00a0!
-error.evaluation.resource.soil.size.wrong=Le nombre de donn\u00e9es de sol ne correspond pas \u00e0 des ann\u00e9es enti\u00e8res\u00a0!
-error.evaluation.resource.variables=Erreur pour la propri\u00e9t\u00e9 ResourceManager.variables\u00a0!
-error.evaluation.resource.variables.empty=La propri\u00e9t\u00e9 ResourceManager.variables ne doit pas \u00eatre vide\u00a0!
-error.evaluation.resource.variables.missing=La propri\u00e9t\u00e9 ResourceManager.variables ne doit pas \u00eatre nulle\u00a0!
+error.evaluation.resource=Erreur pour les ressources\u00a0!
+error.resources.climate.empty=Aucune donn\u00e9e climatique d\u00e9finie dans les ressources\u00a0!
+error.resources.climate.empty.for.phase=Aucune donn\u00e9e climatique pour la phase {0}-{1} (de {2} \u00e0 {3}) en {4}. Donn\u00e9es disponibles entre {5} et {6}.
+error.resources.climate=Erreurs pour les donn\u00e9es climatiques
+error.resources.climate.size.wrong=Le nombre de donn\u00e9es climatiques ne correspond pas \u00e0 des ann\u00e9es enti\u00e8res\u00a0!
+error.resources.climate.years.empty=Aucune ann\u00e9e dans les donn\u00e9es climatiques d\u00e9finies des ressources\u00a0!
+error.resources.climate.years=Erreur pour les ann\u00e9es dans les donn\u00e9es climatiques d\u00e9finies des ressources\u00a0!
+error.resources.climate.years.missing=Les donn\u00e9es climatiques d\u00e9finies dans les ressources manquent pour les ann\u00e9es {0}\u00a0!
+error.resources.cropdevelopment.years=Le nombre d\u2019ann\u00e9es de d\u00e9veloppement de la culture n\u2019est pas d\u00e9fini.
+error.resources.cropdevelopmentyears.null=La propri\u00e9t\u00e9 ResourceManager.cropDevelopmentYears ne doit pas \u00eatre nulle\u00a0!
+error.resources=Erreurs pour les ressources
+error.resources.pheno.empty=Aucune donn\u00e9e ph\u00e9nologique d\u00e9finie dans les ressources\u00a0!
+error.resources.pheno=Erreur pour les donn\u00e9es ph\u00e9nologiques d\u00e9finies dans les ressources\u00a0!
+error.resources.pheno.years.empty=Aucune ann\u00e9e dans les donn\u00e9es ph\u00e9nologiques des ressources\u00a0!
+error.resources.pheno.years=Erreur pour les ann\u00e9es dans les donn\u00e9es ph\u00e9nologiques des ressources\u00a0!
+error.resources.pheno.years.missing=Les donn\u00e9es ph\u00e9nologiques d\u00e9finies dans les ressources manquent pour les ann\u00e9es {0}\u00a0!
+error.resources.soil=Erreurs pour les donn\u00e9es sol
+error.resources.soil.size.wrong=Le nombre de donn\u00e9es de sol ne correspond pas \u00e0 des ann\u00e9es enti\u00e8res\u00a0!
+error.resources.variables.empty=La propri\u00e9t\u00e9 ResourceManager.variables ne doit pas \u00eatre vide\u00a0!
+error.resources.variables=Erreur pour la propri\u00e9t\u00e9 ResourceManager.variables\u00a0!
+error.resources.variables.missing=La propri\u00e9t\u00e9 ResourceManager.variables ne doit pas \u00eatre nulle\u00a0!
 error.rh.outofrange={0}\u00a0: ligne {1}\u00a0: l\u2019humidit\u00e9 relative (%) doit \u00eatre comprise dans l\u2019intervalle 0-100.
 error.title=Erreur
 error.year.null={0}\u00a0: ligne {1}\u00a0: l\u2019ann\u00e9e est obligatoire.
@@ -50,7 +78,7 @@ error.day.duplicate={0}\u00a0: ligne {1}\u00a0: la date est la m\u00eame dans la
 error.day.missing={0}\u00a0: ligne {1}\u00a0: un jour manque avant \u00ab\u00a0{2}\u00a0\u00bb.
 error.day.null={0}\u00a0: ligne {1}\u00a0: le jour est obligatoire.
 error.day.succession={0}\u00a0: ligne {1}\u00a0: le jour \u00ab\u00a0{2}\u00a0\u00bb est ant\u00e9rieur au jour de la ligne pr\u00e9c\u00e9dente \u00ab\u00a0{3}\u00a0\u00bb.
-error.date.notread=La date de d\u00e9but et/ou de fin n'a pas pu \u00eatre lue
+error.date.notread=La date de d\u00e9but et/ou de fin n\u2019a pas pu \u00eatre lue
 error.minimal.stages={0}\u00a0: ligne {1}\u00a0: le fichier ph\u00e9nologique doit contenir au moins deux stades.
 error.climate.jdbc.query=Erreur lors de l\u2019ex\u00e9cution de la requ\u00eate \u00ab\u00a0{0}\u00a0\u00bb\u00a0!
 error.endstage.superiorto.startstage={0}\u00a0: ligne {1}\u00a0: le stade actuel ne peut \u00eatre ant\u00e9rieur au stade suivant.
@@ -67,6 +95,13 @@ markdown.description.hourly=Liste des indicateurs disponibles au pas de temps ho
 markdown.title.daily=Indicateurs au pas de temps journalier
 markdown.title.hourly=Indicateurs au pas de temps horaire
 markdown.keywords="indicateur","agroclimatique","\u00e9coclimatique"
+markdown.error.category=Cat\u00e9gorie
+markdown.error.description=Liste des erreurs\u00a0: codes et descriptions.
+markdown.error.fullcode=Code d\u2019erreur
+markdown.error.keywords="biblioth\u00e8que","code d\u2019erreur"
+markdown.error.message=Message d\u2019erreur
+markdown.error.name=Nom de l\u2019erreur
+markdown.error.title=Codes d\u2019erreurs
 markdown.indicators=Indicateurs
 markdown.indicators.daily={0} indicateurs journaliers
 markdown.indicators.hourly={0} indicateurs horaires
@@ -88,37 +123,29 @@ normalization.MultiLinear=Affine par morceaux
 normalization.Normal=Normale
 normalization.Sigmoid=Sigmo\u00efde
 
-JEXLFormula.error.execution=Erreur lors de l\u2019ex\u00e9cution de l\u2019expression \u00ab {0} \u00bb.
-JEXLFormula.error.expression.null=L\u2019expression ne peut \u00eatre nulle.
-JEXLFormula.error.expression.empty=L\u2019expression ne peut \u00eatre vide.
-JEXLFormula.error.expression.parenthesis=Expression non valide \u00ab {0} \u00bb : des parenth\u00e8ses manquent.
-JEXLFormula.error.expression.parsing=Expression non valide \u00ab {0} \u00bb : erreur d\u2019analyse.
-JEXLFormula.error.variable.undefined=Expression non valide \u00ab {0} \u00bb : la variable \u00ab {1} \u00bb n\u2019est pas d\u00e9finie.
-JEXLFormula.error.function.unknown=Expression non valide \u00ab {0} \u00bb : la fonction \u00ab {1} \u00bb est inconnue ou ambig\u00fce. Si la fonction existe, v\u00e9rifiez les arguments.
-
 EvaluationType.WITH_AGGREGATION.name=avec agr\u00e9gation
 EvaluationType.WITHOUT_AGGREGATION.name=sans agr\u00e9gation
 
 MathMethod.avg.description = Renvoie la moyenne des valeurs pass\u00e9es en param\u00e8tre de la fonction.
-MathMethod.exp.description = Renvoie le nombre Euler e \u00e9lev\u00e9 \u00e0 la puissance d'une valeur double. Cas sp\u00e9ciaux :\n\n\
-Si l'argument est NaN, le r\u00e9sultat est NaN.\n\
-Si l'argument in l'infini positif, le r\u00e9sultat est l'infini positif.\n\
-Si l'argument in l'infini n\u00e9gatif, le r\u00e9sultat est z\u00e9ro positif.\n\
+MathMethod.exp.description = Renvoie le nombre Euler e \u00e9lev\u00e9 \u00e0 la puissance d\u2019une valeur double. Cas sp\u00e9ciaux\u00a0:\n\n\
+Si l\u2019argument est NaN, le r\u00e9sultat est NaN.\n\
+Si l\u2019argument in l\u2019infini positif, le r\u00e9sultat est l\u2019infini positif.\n\
+Si l\u2019argument in l\u2019infini n\u00e9gatif, le r\u00e9sultat est z\u00e9ro positif.\n\
 The computed result must be within 1 ulp of the exact result. Results must be semi-monotonic.
 MathMethod.log.description=Returns the natural logarithm (base e) of a double value. Special cases:\n\n\
-Si l'argument est NaN ou n\u00e9gatif, le r\u00e9sultat est NaN.\n\
-Si l'argument in l'infini positif, le r\u00e9sultat est l'infini positif.\n\
-Si l'argument est z\u00e9ro (positif ou n\u00e9gatif), le r\u00e9sultat est l'infini n\u00e9gatif.\n\n
+Si l\u2019argument est NaN ou n\u00e9gatif, le r\u00e9sultat est NaN.\n\
+Si l\u2019argument in l\u2019infini positif, le r\u00e9sultat est l\u2019infini positif.\n\
+Si l\u2019argument est z\u00e9ro (positif ou n\u00e9gatif), le r\u00e9sultat est l\u2019infini n\u00e9gatif.\n\n
 The computed result must be within 1 ulp of the exact result. Results must be semi-monotonic.
 MathMethod.max.description = Renvoie la plus grande des valeurs.\n\n\
-C'est-\u00e0-dire que le r\u00e9sultat est l'argument le plus proche de l'infini positif.\n\
+C\u2019est-\u00e0-dire que le r\u00e9sultat est l\u2019argument le plus proche de l\u2019infini positif.\n\
 Si les arguments ont la m\u00eame valeur, le r\u00e9sultat est cette m\u00eame valeur.\n\
-Si l'une des valeurs est NaN, le r\u00e9sultat est NaN.\n\
+Si l\u2019une des valeurs est NaN, le r\u00e9sultat est NaN.\n\
 La fonction prend un ou plusieurs arguments en entr\u00e9e.
 MathMethod.min.description = Retourner la plus petite des valeurs.\n\n\
-C'est-\u00e0-dire que le r\u00e9sultat est la valeur la plus proche de l'infini n\u00e9gatif.\n\
+C\u2019est-\u00e0-dire que le r\u00e9sultat est la valeur la plus proche de l\u2019infini n\u00e9gatif.\n\
 Si les arguments ont la m\u00eame valeur, le r\u00e9sultat est cette m\u00eame valeur.\n\
-Si l'une des valeurs est NaN, le r\u00e9sultat est NaN.\n\
+Si l\u2019une des valeurs est NaN, le r\u00e9sultat est NaN.\n\
 La fonction prend un ou plusieurs arguments en entr\u00e9e.
 PhenologicalModelType.curve.name=curvilin\u00e9aire
 PhenologicalModelType.curve_grapevine.name=curvilin\u00e9aire vigne
@@ -139,3 +166,6 @@ Variable.RH.description=Humidit\u00e9 relative [%].
 Variable.SOILWATERCONTENT.description=Teneur en eau du sol [% massique].
 Variable.WATER_RESERVE.description=R\u00e9serve en eau du sol [mm].
 Variable.WIND.description=Vitesse du vent [m/s].
+error.xml.file.not.found=Le fichier XML \u00ab\u00a0{0}\u00a0\u00bb n\u2019a pas \u00e9t\u00e9 trouv\u00e9.
+error.xml.unable.to.load=Le chargement du fichier XML a \u00e9chou\u00e9.
+error.xml.unable.to.serialize=La s\u00e9rialisation du fichier XML a \u00e9chou\u00e9.
diff --git a/src/site/en/markdown/.gitignore b/src/site/en/markdown/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..62c71ef089a556130fbd5f50c9d5de17ddfcd073
--- /dev/null
+++ b/src/site/en/markdown/.gitignore
@@ -0,0 +1,3 @@
+errors-en.md
+indicators-daily-en.md
+indicators-hourly-en.md
diff --git a/src/site/en/markdown/index.md.vm b/src/site/en/markdown/index.md.vm
new file mode 100644
index 0000000000000000000000000000000000000000..262e036e24757f1a1e7d2ee7713858de7230c5cb
--- /dev/null
+++ b/src/site/en/markdown/index.md.vm
@@ -0,0 +1,59 @@
+#set($h1 = '#')
+#set($h2 = '##')
+#set($h3 = '###')
+#set($h4 = '####')
+<head>
+    <title>Technical documenation for de ${project.name}</title>
+</head>
+
+$h1 Technical documentation for ${project.name}
+
+Indicator library, usable in eco-climatic mode (with an embeddded phenological model) as well as agro-climatic.
+This library is used by the desktop software [GETARI](https://agroclim.inrae.fr/getari/en/) and the workflow SEASON embeeded into [SICLIMA](https://agroclim.inrae.fr/siclima/).
+
+$h2 Project links:
+
+- [Project info](project-info.html): information about code management, issues and bugs managnements, and continuous integration server.
+- [Dependencies](dependencies.html).
+- [Project license](license.html): from the tag `<licenses>` in the file `pom.xml`.
+
+$h2 Code structuration
+
+Source code is divided as:
+
+- `pom.xml`: Maven file
+- `src/main/` : source code
+- `src/test/` : source code for tests
+- `src/site/` : documentation, see below
+
+$h3 Documentation
+
+Documentation is writtent in the folder `/src/site/markdown/`.
+Site structure is defined in `/src/site/site_en.xml`.
+Plain text formats are prefered to allow SCM.
+This document is writtent in Markdown format.
+
+UML diagrams are written in [PlantUML](http://plantuml.com/) format
+(extension `.puml` in `/src/site/resources/images/`).
+The prefixes for the diagrams:
+
+- `act-` : activity diagrams
+- `cas-` : use case diagrams
+- `cls-` : class diagrams
+- `cmp-` : component diagrams
+- `seq-` : sequence diagrams
+
+The technical documentation is generated by Maven.
+The Markdown files showing indicators and error codes are generated by the class `GenerateMarkdown`.
+
+```
+mvn exec:java -Dexec.mainClass=fr.inrae.agroclim.indicators.GenerateMarkdown -Dexec.args='${basedir}/src/site/markdown -'
+mvn site
+```
+
+Jetty can be used for real time rendering,
+so refreshing the browser shows the changes: use `mvn site:run` and visit the link,
+usually http://localhost:8080/.
+
+If needed, generate the UML diagrams using
+`mvn com.github.jeluard:plantuml-maven-plugin:generate`.
diff --git a/src/site/markdown/.gitignore b/src/site/markdown/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..e533f96d0c52d5cd564ef08c1a37dca6c5abdd17
--- /dev/null
+++ b/src/site/markdown/.gitignore
@@ -0,0 +1,5 @@
+errors-*.md
+indicators-daily*
+indicators-hourly*
+parameters-daily.csv
+parameters-hourly.csv
diff --git a/src/site/markdown/index.md.vm b/src/site/markdown/index.md.vm
index 535955ae187bb4cd349c8542df394f8e345618b6..071b957d8c71230b56e13ed102f1e4a14a64a680 100644
--- a/src/site/markdown/index.md.vm
+++ b/src/site/markdown/index.md.vm
@@ -8,7 +8,7 @@
 
 $h1 Documentation technique de ${project.name}
 
-Bibliothèque d'indicateurs, utilisable autant en mode éco-climatique (avec modèle phénologique intégré) qu'enen mode agro-climatique.
+Bibliothèque d'indicateurs, utilisable autant en mode éco-climatique (avec modèle phénologique intégré) qu'en mode agro-climatique.
 Cette bibliothèque est utilisée dans le logiciel de bureau [GETARI](https://agroclim.inrae.fr/getari/) et la chaîne de traitement SEASON intégrée dans [SICLIMA](https://agroclim.inrae.fr/siclima/).
 
 $h2 Liens du projet :
@@ -43,7 +43,13 @@ Voici les préfixes pour les diagrammes :
 - `cmp-` : diagrammes de composants
 - `seq-` : diagrammes de séquence
 
-La documentation technique est générée par Maven avec la commande `mvn site`.
+La documentation technique est générée par Maven.
+Les fichiers Markdown listant les indicateurs et les codes d'erreur sont générés par la classe `GenerateMarkdown`.
+
+```
+mvn exec:java -Dexec.mainClass=fr.inrae.agroclim.indicators.GenerateMarkdown -Dexec.args='${basedir}/src/site/markdown -'
+mvn site
+```
 
 Jetty peut être utilisé pour le rendu en temps réel,
 c'est-à-dire qu'il suffit de rafraîchir le navigateur pour afficher
@@ -52,9 +58,3 @@ généralement http://localhost:8080/.
 
 Au besoin, regénérer les diagrammes UML avec la commande
 `mvn com.github.jeluard:plantuml-maven-plugin:generate`.
-
-Les fichiers Markdown listant les indicateurs sont générés par la classe `Knowlegde` que l'on peut exécuter avec Maven :
-`mvn exec:java -Dexec.mainClass="fr.inrae.agroclim.indicators.model.Knowledge"`.
-----
-
-`$Id$`
diff --git a/src/site/site.xml b/src/site/site.xml
index 3059eae54e2229eb3509bd6aa9bb23fc4e3a5248..f0bb9bd0dfff773e4c5022ccf08492af78842082 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -26,6 +26,10 @@
     <body>
         <menu name="Documentation" inherit="top">
             <item name="Accueil" href="index.html" />
+            <item name="Indicateurs horaires" href="indicators-hourly-fr.html" />
+            <item name="Indicateurs journaliers" href="indicators-daily-fr.html" />
+            <item name="Codes d'erreurs" href="errors-fr.html" />
+            <item name="Documentation in English" href="en/index.html" />
         </menu>
         <menu ref="modules" inherit="top" />
         <menu ref="reports" inherit="top" />
diff --git a/src/site/site_en.xml b/src/site/site_en.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ce0af405b26c66c3d3dca0d764b1506857f61e5d
--- /dev/null
+++ b/src/site/site_en.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE xml>
+<!-- Last changed : $Date$ -->
+<!-- @author $Author$ -->
+<!-- @version $Revision$ -->
+<project name="${project.name}">
+    <skin>
+        <groupId>org.apache.maven.skins</groupId>
+        <artifactId>maven-fluido-skin</artifactId>
+        <version>1.9</version>
+    </skin>
+
+    <custom>
+        <fluidoSkin>
+            <sideBarEnabled>true</sideBarEnabled>
+            <sourceLineNumbersEnabled>true</sourceLineNumbersEnabled>
+        </fluidoSkin>
+    </custom>
+
+    <bannerLeft>
+        <name>Documentation for ${project.name}</name>
+    </bannerLeft>
+
+    <version position="bottom" />
+
+    <body>
+        <menu name="Documentation" inherit="top">
+            <item name="Home" href="index.html" />
+            <item name="Hourly indicators" href="indicators-hourly-en.html" />
+            <item name="Daily indicators" href="indicators-daily-en.html" />
+            <item name="Error codes" href="errors-en.html" />
+            <item name="Documentation en français" href="../index.html" />
+        </menu>
+        <menu ref="modules" inherit="top" />
+        <menu ref="reports" inherit="top" />
+        <menu ref="parent" inherit="top" />
+    </body>
+</project>
\ No newline at end of file
diff --git a/src/test/java/fr/inrae/agroclim/indicators/exception/ErrorMessageTest.java b/src/test/java/fr/inrae/agroclim/indicators/exception/ErrorMessageTest.java
index 911352458f8de5d42d6b581edb8948d2cfdc611f..a632495152331ea294b21f5387fb4e6d7bdd2829 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/exception/ErrorMessageTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/exception/ErrorMessageTest.java
@@ -17,6 +17,8 @@
 package fr.inrae.agroclim.indicators.exception;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 
 import java.io.Serializable;
 import java.util.Arrays;
@@ -52,7 +54,17 @@ public class ErrorMessageTest {
 
         @Override
         public ErrorCategory getCategory() {
-            return () -> "TEST00";
+            return new ErrorCategory() {
+                @Override
+                public String getCode() {
+                    return "TEST00";
+                }
+
+                @Override
+                public String getName() {
+                    return "CATEGORY";
+                }
+            };
         }
 
         @Override
@@ -85,7 +97,6 @@ public class ErrorMessageTest {
      */
     @Test
     public void getMessageWithArguments() {
-
         final ErrorI18nKey key = ErrorI18nKey.KEY;
         final String category = "climate";
         final Integer line = 11;
@@ -99,6 +110,9 @@ public class ErrorMessageTest {
         expected = defaultResources.format(key.getI18nKey(), category, line);
         found = error.getMessage();
         assertEquals(expected, found);
+
+        assertNotNull(error.toJSON());
+        assertFalse(error.toJSON().isBlank());
     }
 
     /**
diff --git a/src/test/java/fr/inrae/agroclim/indicators/exception/ErrorTypeTest.java b/src/test/java/fr/inrae/agroclim/indicators/exception/ErrorTypeTest.java
index 695c625be721a6efe2dbf90fc6ce39a3c0d2520f..dfed45e2b15b55ca90bc2ee976515cd68f53e4d1 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/exception/ErrorTypeTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/exception/ErrorTypeTest.java
@@ -1,11 +1,10 @@
 package fr.inrae.agroclim.indicators.exception;
 
+import fr.inrae.agroclim.indicators.exception.type.ComputationErrorType;
+import fr.inrae.agroclim.indicators.exception.type.ResourceErrorType;
+import fr.inrae.agroclim.indicators.exception.type.XmlErrorType;
 import static org.junit.Assert.assertTrue;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -15,16 +14,12 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
-import fr.inrae.agroclim.indicators.model.data.ResourceManager;
-import fr.inrae.agroclim.indicators.resources.I18n;
+import fr.inrae.agroclim.indicators.util.ErrorTypeUtils;
 
 /**
  * Ensure all implementations of {@link ErrorType} are well defined.
  *
- * Last changed : $Date$
- *
- * @author $Author$
- * @version $Revision$
+ * @author Olivier Maury
  */
 @RunWith(Parameterized.class)
 public class ErrorTypeTest {
@@ -38,7 +33,7 @@ public class ErrorTypeTest {
      */
     @Parameterized.Parameters
     public static List<Class<? extends Enum<?>>> data() {
-        return Arrays.asList(ResourceManager.ErrorI18nKey.class);
+        return List.of(ComputationErrorType.class, ResourceErrorType.class, XmlErrorType.class);
     }
 
     /**
@@ -57,34 +52,14 @@ public class ErrorTypeTest {
 
     @Test
     public void i18n() {
-        final Map<Locale, Map<Class<?>, List<String>>> missing = new HashMap<>();
-        for (final Locale locale : Arrays.asList(Locale.ENGLISH, Locale.FRENCH)) {
-            final I18n i18n = new I18n(BUNDLE_NAME, locale);
-            final Enum<?>[] values = clazz.getEnumConstants();
-            for (final Enum<?> value : values) {
-                final ErrorType k = (ErrorType) value;
-                final String tr = i18n.get(k.getI18nKey());
-                if (tr.startsWith("!") && tr.endsWith("!")) {
-                    missing.computeIfAbsent(locale, l -> new HashMap<>());
-                    missing.get(locale).computeIfAbsent(clazz, l -> new ArrayList<>());
-                    missing.get(locale).get(clazz).add(k.getI18nKey());
-                }
-            }
-        }
+        final Map<Locale, Map<Class<?>, List<String>>> missing = ErrorTypeUtils.getMissingTranslations(clazz,
+                BUNDLE_NAME, List.of(Locale.ENGLISH, Locale.FRENCH));
         assertTrue(missing.toString(), missing.isEmpty());
     }
 
     @Test
     public void subCodeAreUnique() {
-        final Set<String> codes = new HashSet<>();
-        final Set<String> duplicates = new HashSet<>();
-        final Enum<?>[] values = clazz.getEnumConstants();
-        for (final Enum<?> value : values) {
-            final ErrorType k = (ErrorType) value;
-            if (codes.contains(k.getSubCode())) {
-                duplicates.add(k.getSubCode());
-            }
-        }
+        final Set<String> duplicates = ErrorTypeUtils.getDuplicatedCodes(clazz);
         assertTrue(duplicates.isEmpty());
     }
 }
diff --git a/src/test/java/fr/inrae/agroclim/indicators/exception/IndicatorsErrorCategoryTest.java b/src/test/java/fr/inrae/agroclim/indicators/exception/IndicatorsErrorCategoryTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..25ea12db4f5d9b25b7e010c13af8eb01525d805d
--- /dev/null
+++ b/src/test/java/fr/inrae/agroclim/indicators/exception/IndicatorsErrorCategoryTest.java
@@ -0,0 +1,60 @@
+package fr.inrae.agroclim.indicators.exception;
+
+import fr.inrae.agroclim.indicators.resources.I18n;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.Test;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * Ensure all translations of {@link IndicatorsErrorCategory} are well defined.
+ *
+ * @author Olivier Maury
+ */
+@RunWith(Parameterized.class)
+public class IndicatorsErrorCategoryTest {
+
+    /**
+     * @return classes to test.
+     */
+    @Parameterized.Parameters
+    public static List<IndicatorsErrorCategory> data() {
+        return Arrays.asList(IndicatorsErrorCategory.values());
+    }
+
+    /**
+     * Category to test.
+     */
+    private final IndicatorsErrorCategory category;
+
+    /**
+     * Constructor.
+     *
+     * @param cat category to test
+     */
+    public IndicatorsErrorCategoryTest(final IndicatorsErrorCategory cat) {
+        this.category = cat;
+    }
+
+    private void missingTranslation(Locale locale) {
+        final String bundleName = "fr.inrae.agroclim.indicators.resources.messages";
+        final I18n i18n = new I18n(bundleName, locale);
+        final String tr = category.getCategory(i18n);
+        final boolean actual = tr.startsWith("!") && tr.endsWith("!");
+        final String msg = tr + " is not translated in " + locale;
+        assertFalse(msg, actual);
+    }
+
+    @Test
+    public void missingTranslationEnglish() {
+        missingTranslation(Locale.ENGLISH);
+    }
+
+    @Test
+    public void missingTranslationFrench() {
+        missingTranslation(Locale.FRENCH);
+    }
+}
diff --git a/src/test/java/fr/inrae/agroclim/indicators/exception/IndicatorsExceptionTest.java b/src/test/java/fr/inrae/agroclim/indicators/exception/IndicatorsExceptionTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..63988bc01b3f67d33635a2d3effac98c76f3c677
--- /dev/null
+++ b/src/test/java/fr/inrae/agroclim/indicators/exception/IndicatorsExceptionTest.java
@@ -0,0 +1,23 @@
+package fr.inrae.agroclim.indicators.exception;
+
+import fr.inrae.agroclim.indicators.exception.type.ComputationErrorType;
+import java.util.Locale;
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+/**
+ * Test translations and formatting of exception message.
+ *
+ * @author Olivier Maury
+ */
+public class IndicatorsExceptionTest {
+
+    @Test
+    public void getLocalizedMessage() {
+        Locale.setDefault(Locale.ENGLISH);
+        var instance = new IndicatorsException(ComputationErrorType.FORMULA, "m * c * c");
+        var actual = instance.getLocalizedMessage();
+        var expected = "Error while executing expression \"m * c * c\".";
+        assertEquals(expected, actual);
+    }
+}
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/CulturalPracticesTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/CulturalPracticesTest.java
index b869201a2265874d4a62e196b4c966c8ea83fd59..55d2ceddef22f4b73f3b0bef5df56175b38de284 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/CulturalPracticesTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/CulturalPracticesTest.java
@@ -19,8 +19,7 @@
 
 package fr.inrae.agroclim.indicators.model;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
 import java.io.File;
 import lombok.extern.log4j.Log4j2;
@@ -76,7 +75,7 @@ public class CulturalPracticesTest extends DataTestHelper {
             long start = System.currentTimeMillis();
             evaluation.compute();
             LOGGER.info("Evaluation.compute() last {} seconds", (System.currentTimeMillis() - start) / 1000.);
-        } catch (final TechnicalException | FunctionalException e) {
+        } catch (final IndicatorsException e) {
             LOGGER.catching(e);
             error = e.getClass() + " : " + e.getLocalizedMessage();
         }
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationHourlyTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationHourlyTest.java
index 10eb9d618337b29ce09ac2159e89dc4c3f94d582..f90f64f02a78d61b89621df94754f98da23efe15 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationHourlyTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationHourlyTest.java
@@ -12,8 +12,7 @@ import java.util.Map;
 import org.junit.Before;
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
 import fr.inrae.agroclim.indicators.model.result.EvaluationResult;
 import fr.inrae.agroclim.indicators.model.result.IndicatorResult;
@@ -57,7 +56,7 @@ public class EvaluationHourlyTest extends DataTestHelper {
         try {
             evaluation.initializeResources();
             evaluation.compute();
-        } catch (final TechnicalException | FunctionalException e) {
+        } catch (final IndicatorsException e) {
             error = e.getClass() + " : " + e.getLocalizedMessage();
         }
         assertNull(error, error);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationRobertTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationRobertTest.java
index 16aae0edb2dbeda4a38b6416774879d45afe1a1b..e1a505c301a57c6900f3b14ea1d7b8e4f3cbb963 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationRobertTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationRobertTest.java
@@ -18,8 +18,7 @@
  */
 package fr.inrae.agroclim.indicators.model;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
 import fr.inrae.agroclim.indicators.model.data.climate.ClimaticResource;
 import fr.inrae.agroclim.indicators.model.result.EvaluationResult;
@@ -82,7 +81,7 @@ public class EvaluationRobertTest extends DataTestHelper {
         try {
             evaluation.initializeResources();
             evaluation.compute();
-        } catch (final TechnicalException | FunctionalException e) {
+        } catch (final IndicatorsException e) {
             error = e.getClass() + " : " + e.getLocalizedMessage();
         }
         assertNull(error, error);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationSettingsTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationSettingsTest.java
index 06872d5d692929d6dd516f5efdadf48a4e07418d..d5849da63a6b18e32039bfb2437e9048fd919e4f 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationSettingsTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationSettingsTest.java
@@ -16,6 +16,7 @@
  */
 package fr.inrae.agroclim.indicators.model;
 
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import static org.junit.Assert.assertTrue;
 import org.junit.Test;
 
@@ -30,7 +31,7 @@ import org.junit.Test;
 public final class EvaluationSettingsTest {
 
     @Test
-    public void initializeKnowledge() {
+    public void initializeKnowledge() throws IndicatorsException {
         EvaluationSettings settings = new EvaluationSettings();
         settings.initializeKnowledge();
         assertTrue("Knowledge should not be null!",
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java
index a1d8b3e9b3a4e11255d0c8f5febf8b09fcfffcfd..a7bf99dde53422c0617e118b6146a4f9cca33bfb 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationTest.java
@@ -40,8 +40,7 @@ import java.util.stream.Collectors;
 import org.junit.Before;
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
 import fr.inrae.agroclim.indicators.model.data.ResourceManager;
 import fr.inrae.agroclim.indicators.model.data.Variable;
@@ -68,7 +67,7 @@ public final class EvaluationTest extends DataTestHelper {
      * Evaluation from good XML file.
      */
     private Evaluation evaluation;
-    
+
     /**
      * Evaluation from good XML file and with a climatic file contains NA data.
      */
@@ -123,12 +122,12 @@ public final class EvaluationTest extends DataTestHelper {
         try {
             evaluation.initializeResources();
             evaluation.compute();
-        } catch (final TechnicalException | FunctionalException e) {
+        } catch (final IndicatorsException e) {
             error = e.getClass() + " : " + e.getLocalizedMessage();
         }
         assertNull(error, error);
     }
-    
+
     @Test
     public void computableWithNoData() {
     	final File xmlFile = getEvaluationWithNoDataTestFile();
@@ -139,11 +138,11 @@ public final class EvaluationTest extends DataTestHelper {
     	evaluationWithNoData.initializeResources();
         try {
         	evaluationWithNoData.compute();
-        } catch (final TechnicalException | FunctionalException e) {
+        } catch (final IndicatorsException e) {
             error = e.getClass() + " : " + e.getLocalizedMessage();
         }
         assertNull(error, error);
-        
+
     }
 
     /**
@@ -190,7 +189,7 @@ public final class EvaluationTest extends DataTestHelper {
             final int nbOfPhases = evaluation.getIndicators().size();
             final int nbOfYears = evaluation.getClimaticResource().getYears().size();
             assertEquals(nbOfPhases * nbOfYears, phases.size());
-        } catch (final TechnicalException | FunctionalException e) {
+        } catch (final IndicatorsException e) {
             error = e.getClass() + " : " + e.getLocalizedMessage();
         }
         assertNull(error, error);
@@ -260,7 +259,7 @@ public final class EvaluationTest extends DataTestHelper {
                     });
                 });
             });
-        } catch (final TechnicalException | FunctionalException e) {
+        } catch (final IndicatorsException e) {
             throw new RuntimeException("This should never occur.", e);
         }
         assertTrue(duplicates.toString(), duplicates.isEmpty());
@@ -424,7 +423,7 @@ public final class EvaluationTest extends DataTestHelper {
         try {
             evaluation.initializeResources();
             evaluation.compute();
-        } catch (final TechnicalException | FunctionalException e) {
+        } catch (final IndicatorsException e) {
             error = e.getClass() + " : " + e.getLocalizedMessage();
         }
         assertNull(error, error);
@@ -440,7 +439,7 @@ public final class EvaluationTest extends DataTestHelper {
         evaluation.setParametersValues(parameters);
         try {
             evaluation.compute();
-        } catch (TechnicalException | FunctionalException e) {
+        } catch (IndicatorsException e) {
             error = e.getClass() + " : " + e.getLocalizedMessage();
         }
         assertNull(error, error);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationWithoutAggregationTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationWithoutAggregationTest.java
index 989ea677dbe5eb5fbbe5640b9f8116c91ed1f41b..6bc188249d87bbd0d0e6a57bd195d3171cb5b7f0 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationWithoutAggregationTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvaluationWithoutAggregationTest.java
@@ -18,8 +18,7 @@
  */
 package fr.inrae.agroclim.indicators.model;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
 import fr.inrae.agroclim.indicators.model.indicator.CompositeIndicator;
 import fr.inrae.agroclim.indicators.model.indicator.Indicator;
@@ -76,13 +75,10 @@ public class EvaluationWithoutAggregationTest extends DataTestHelper {
 
     /**
      * Check if evaluation computes.
-     * @throws fr.inrae.agroclim.indicators.exception.TechnicalException while
-     * computing
-     * @throws fr.inrae.agroclim.indicators.exception.FunctionalException while
-     * computing
+     * @throws IndicatorsException while computing
      */
     @Test(expected = Test.None.class)
-    public void compute() throws TechnicalException, FunctionalException {
+    public void compute() throws IndicatorsException {
     	assertNotNull("Evaluation must not be null!", evaluation);
         evaluation.initializeResources();
         evaluation.compute();
@@ -101,12 +97,11 @@ public class EvaluationWithoutAggregationTest extends DataTestHelper {
     }
     /**
      * Test save() a file.
-     * @throws fr.inrae.agroclim.indicators.exception.TechnicalException while
-     * serializing or loading
+     * @throws IndicatorsException while serializing or loading
      * @throws java.io.IOException while creating tmp file or writing
      */
     @Test(expected = Test.None.class)
-    public void save() throws TechnicalException, IOException {
+    public void save() throws IndicatorsException, IOException {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         XMLUtil.serialize(evaluation.getSettings(), baos,
                 EvaluationSettings.CLASSES_FOR_JAXB);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/EvalutationCustomHeadersTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/EvalutationCustomHeadersTest.java
index f7bd55545e4f58342a9ffc9b295e105760995a31..325eaa031346ed31a1d993401426edf817054b03 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/EvalutationCustomHeadersTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/EvalutationCustomHeadersTest.java
@@ -18,8 +18,7 @@
  */
 package fr.inrae.agroclim.indicators.model;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
 import fr.inrae.agroclim.indicators.model.result.EvaluationResult;
 import java.io.File;
@@ -65,7 +64,7 @@ public class EvalutationCustomHeadersTest extends DataTestHelper {
         try {
             evaluation.initializeResources();
             evaluation.compute();
-        } catch (final TechnicalException | FunctionalException e) {
+        } catch (final IndicatorsException e) {
             error = e.getClass() + " : " + e.getLocalizedMessage();
         }
         assertNull(error, error);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/JEXLFormulaTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/JEXLFormulaTest.java
index b80a7ffcd438b314679d65d31d9e93ca61c02e5c..908876e5ea527fd5fc1259c4a8d894d4d4db86ed 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/JEXLFormulaTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/JEXLFormulaTest.java
@@ -18,7 +18,7 @@
  */
 package fr.inrae.agroclim.indicators.model;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.data.Variable;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -55,7 +55,7 @@ public class JEXLFormulaTest {
     }
 
     @Test
-    public void useFormulaCriteria() throws FunctionalException {
+    public void useFormulaCriteria() throws IndicatorsException {
         final JEXLFormula formula = new JEXLFormula();
         formula.setExpression("formulaCriteria:between(2, 1, 3)");
         var actual = formula.evaluate(new HashMap<>(), Boolean.class);
@@ -63,7 +63,7 @@ public class JEXLFormulaTest {
     }
 
     @Test
-    public void useMathMethod() throws FunctionalException {
+    public void useMathMethod() throws IndicatorsException {
         final JEXLFormula formula = new JEXLFormula();
         formula.setExpression("math:min(2, 1, 3)");
         var actual = formula.evaluate(new HashMap<>(), Double.class);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/KnowledgeDailyTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/KnowledgeDailyTest.java
index 1a623d73ccdad7863fe8e1dc205762c56d013153..7ccdb768ad19eba86e790a9497a438736bc1de5e 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/KnowledgeDailyTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/KnowledgeDailyTest.java
@@ -16,10 +16,12 @@
  */
 package fr.inrae.agroclim.indicators.model;
 
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.util.Arrays;
 import java.util.HashMap;
@@ -32,7 +34,6 @@ import java.util.stream.Collectors;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
 import fr.inrae.agroclim.indicators.model.data.Variable;
 import fr.inrae.agroclim.indicators.model.indicator.CompositeIndicator;
 import fr.inrae.agroclim.indicators.model.indicator.Indicator;
@@ -57,8 +58,8 @@ public final class KnowledgeDailyTest {
     public static void loadXml() {
         try {
             knowledge = Knowledge.load(TimeScale.DAILY);
-        } catch (final TechnicalException ex) {
-            assertTrue("Loading XML file from Knowledge.load() should work!", false);
+        } catch (final IndicatorsException ex) {
+            fail("Loading XML file from Knowledge.load() should work!");
         }
     }
 
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/KnowledgeHourlyTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/KnowledgeHourlyTest.java
index 101f19be36ec794fe35dee4c2f195f205e38ebd5..e4e0935e6efadbb988721579765bd09c89bad550 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/KnowledgeHourlyTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/KnowledgeHourlyTest.java
@@ -18,7 +18,7 @@
  */
 package fr.inrae.agroclim.indicators.model;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.criteria.Criteria;
 import fr.inrae.agroclim.indicators.model.criteria.FormulaCriteria;
 import fr.inrae.agroclim.indicators.model.indicator.CompositeIndicator;
@@ -51,7 +51,7 @@ public class KnowledgeHourlyTest {
     public static void loadXml() {
         try {
             knowledge = Knowledge.load(TimeScale.HOURLY);
-        } catch (final TechnicalException ex) {
+        } catch (final IndicatorsException ex) {
             assertTrue("Loading XML file from Knowledge.load() should work!", false);
         }
     }
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/KnowledgeTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/KnowledgeTest.java
index 92ca75b2fd72686bbb59511e67c102ec2989e3de..651393b6d5bbcc9ce016f4484478aef5d907c3d9 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/KnowledgeTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/KnowledgeTest.java
@@ -16,10 +16,12 @@
  */
 package fr.inrae.agroclim.indicators.model;
 
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -33,13 +35,13 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
 import fr.inrae.agroclim.indicators.model.data.Variable;
 import fr.inrae.agroclim.indicators.model.indicator.CompositeIndicator;
 import fr.inrae.agroclim.indicators.model.indicator.Indicator;
 import fr.inrae.agroclim.indicators.model.indicator.IndicatorCategory;
 import fr.inrae.agroclim.indicators.model.indicator.NumberOfWaves;
 import fr.inrae.agroclim.indicators.model.indicator.Quotient;
+import java.util.Objects;
 
 /**
  * Test the class vs the XML file.
@@ -71,9 +73,8 @@ public final class KnowledgeTest {
     public KnowledgeTest(final TimeScale timeScale) {
         try {
             knowledge = Knowledge.load(timeScale);
-        } catch (final TechnicalException ex) {
-            assertTrue("Loading XML file from Knowledge.load() should work!",
-                    false);
+        } catch (final IndicatorsException ex) {
+            fail("Loading XML file from Knowledge.load() should work!");
         }
     }
 
@@ -351,10 +352,10 @@ public final class KnowledgeTest {
             final String id = ind.getId();
             final Set<Variable> vars = ind.getVariables();
             assertNotNull(id + ".variables must not be a null set.", vars);
-            assertTrue(id + ".variables must contain at least one variable.",
-                    !vars.isEmpty());
-            assertTrue(id + ".variables must not contains null.",
-                    !vars.contains(null));
+            assertTrue(id + ".variables must contain at least one variable.", !vars.isEmpty());
+            // with unmodifiable set, contains(null) throws NullPointerException
+            final boolean contains = vars.stream().anyMatch(Objects::isNull);
+            assertFalse(id + ".variables must not contains null. variables=" + vars, contains);
         });
 
     }
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/MMarjouTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/MMarjouTest.java
index 34ef61c437aa140dffc5d529f74459764fe13842..75ca9303d6585584804333c81b739eefc39e8032 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/MMarjouTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/MMarjouTest.java
@@ -16,8 +16,7 @@
  */
 package fr.inrae.agroclim.indicators.model;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
 import fr.inrae.agroclim.indicators.model.data.phenology.AnnualStageData;
 import fr.inrae.agroclim.indicators.model.result.EvaluationResult;
@@ -80,7 +79,7 @@ public class MMarjouTest extends DataTestHelper {
             long start = System.currentTimeMillis();
             evaluation.compute();
             LOGGER.info("Evaluation.compute() last {} seconds", (System.currentTimeMillis() - start) / 1000.);
-        } catch (final TechnicalException | FunctionalException e) {
+        } catch (final IndicatorsException e) {
             error = e.getClass() + " : " + e.getLocalizedMessage();
         }
         assertNull(error, error);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/RaidayMeantTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/RaidayMeantTest.java
index 1579b2f2c8dac75a2bc223a14adbf98ecbe51f99..1c71ef2582d8cb401390ea9648f2c35988936d48 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/RaidayMeantTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/RaidayMeantTest.java
@@ -23,7 +23,6 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
-import java.io.IOException;
 import java.nio.file.Paths;
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
@@ -37,8 +36,7 @@ import java.util.stream.IntStream;
 import org.junit.Before;
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.criteria.RelationalOperator;
 import fr.inrae.agroclim.indicators.model.criteria.SimpleCriteria;
 import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
@@ -57,6 +55,7 @@ import fr.inrae.agroclim.indicators.model.result.IndicatorResult;
 import fr.inrae.agroclim.indicators.model.result.PhaseResult;
 import fr.inrae.agroclim.indicators.util.DateUtils;
 import fr.inrae.agroclim.indicators.xml.XMLUtil;
+import java.io.IOException;
 import java.util.TimeZone;
 import lombok.NonNull;
 import lombok.extern.log4j.Log4j2;
@@ -180,7 +179,7 @@ public class RaidayMeantTest extends DataTestHelper {
             settings = (EvaluationSettings) XMLUtil.load(xmlFile,
                     EvaluationSettings.CLASSES_FOR_JAXB);
             settings.setFilePath(xmlFile.getAbsolutePath());
-        } catch (final TechnicalException ex) {
+        } catch (final IndicatorsException ex) {
             LOGGER.error(ex.getLocalizedMessage());
             return;
         }
@@ -224,7 +223,7 @@ public class RaidayMeantTest extends DataTestHelper {
             evaluation.initializeResources();
             evaluation.compute();
             annualStageDatas = evaluation.getResourceManager().getPhenologicalResource().getData();
-        } catch (final TechnicalException | FunctionalException | IOException e) {
+        } catch (final IndicatorsException | IOException e) {
             error = e.getClass() + " : " + e.getLocalizedMessage();
         }
         assertNull(error, error);
@@ -253,7 +252,7 @@ public class RaidayMeantTest extends DataTestHelper {
         try {
             evaluation.initializeResources();
             evaluation.compute();
-        } catch (final TechnicalException | FunctionalException e) {
+        } catch (final IndicatorsException e) {
             error = e.getClass() + " : " + e.getLocalizedMessage();
         }
         assertNull(error, error);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/StageDeltaEvaluationTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/StageDeltaEvaluationTest.java
index e3743d5846dbd93b88c8a279de6b201e45a03f70..8e5b6573625b01320dc391873a8048acf605868e 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/StageDeltaEvaluationTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/StageDeltaEvaluationTest.java
@@ -27,8 +27,7 @@ import java.util.Map;
 import org.junit.Before;
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
 import fr.inrae.agroclim.indicators.xml.XMLUtil;
 import lombok.extern.log4j.Log4j2;
@@ -56,7 +55,7 @@ public class StageDeltaEvaluationTest extends DataTestHelper {
                     EvaluationSettings.CLASSES_FOR_JAXB);
             settings.initializeKnowledge();
             settings.setFilePath(xmlFile.getAbsolutePath());
-        } catch (final TechnicalException ex) {
+        } catch (final IndicatorsException ex) {
             LOGGER.error(ex.getLocalizedMessage());
             return;
         }
@@ -92,7 +91,7 @@ public class StageDeltaEvaluationTest extends DataTestHelper {
             final long start = System.currentTimeMillis();
             evaluation.compute();
             LOGGER.info("Evaluation.compute() last {} seconds", (System.currentTimeMillis() - start) / 1000.);
-        } catch (final TechnicalException | FunctionalException e) {
+        } catch (final IndicatorsException e) {
             error = e.getClass() + " : " + e.getLocalizedMessage();
         }
         assertNull(error, error);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/criteria/CompositeCriteriaTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/criteria/CompositeCriteriaTest.java
index 3b198104393a15d2dfa9af6d3e019291b43534bf..d37518aaad67e9e15963701f159a1e5692bcac17 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/criteria/CompositeCriteriaTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/criteria/CompositeCriteriaTest.java
@@ -16,6 +16,7 @@
  */
 package fr.inrae.agroclim.indicators.model.criteria;
 
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -24,7 +25,6 @@ import java.util.List;
 
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
 import fr.inrae.agroclim.indicators.model.data.climate.ClimaticDailyData;
 
 /**
@@ -88,11 +88,11 @@ public final class CompositeCriteriaTest {
     /**
      * Check sup0 AND inf10 criteria.
      *
-     * @throws TechnicalException
+     * @throws IndicatorsException
      *             exception while getting value.
      */
     @Test
-    public void and() throws TechnicalException {
+    public void and() throws IndicatorsException {
         criteria.setLogicalOperator(LogicalOperator.AND);
         assertFalse(criteria.getFormula(data0) + " must be false",
                 criteria.eval(data0));
@@ -105,11 +105,11 @@ public final class CompositeCriteriaTest {
     /**
      * Check inf10 criteria.
      *
-     * @throws TechnicalException
+     * @throws IndicatorsException
      *             exception while getting value.
      */
     @Test
-    public void inf10() throws TechnicalException {
+    public void inf10() throws IndicatorsException {
         assertTrue(inf10.getFormula(data0) + " must be true",
                 inf10.eval(data0));
         assertTrue(inf10.getFormula(data5) + " must be true",
@@ -121,11 +121,11 @@ public final class CompositeCriteriaTest {
     /**
      * Check sup0 OR inf10 criteria.
      *
-     * @throws TechnicalException
+     * @throws IndicatorsException
      *             exception while getting value.
      */
     @Test
-    public void or() throws TechnicalException {
+    public void or() throws IndicatorsException {
         criteria.setLogicalOperator(LogicalOperator.OR);
         assertTrue(criteria.getFormula(data0) + " must be true",
                 criteria.eval(data0));
@@ -138,11 +138,11 @@ public final class CompositeCriteriaTest {
     /**
      * Check sup0 criteria.
      *
-     * @throws TechnicalException
+     * @throws IndicatorsException
      *             exception while getting value.
      */
     @Test
-    public void sup0() throws TechnicalException {
+    public void sup0() throws IndicatorsException {
         assertFalse(
                 sup0.getFormula(data0) + " must be false. " + sup0.toString(),
                 sup0.eval(data0));
@@ -154,11 +154,11 @@ public final class CompositeCriteriaTest {
     /**
      * Check sup0 XOR inf10 criteria.
      *
-     * @throws TechnicalException
+     * @throws IndicatorsException
      *             exception while getting value.
      */
     @Test
-    public void xor() throws TechnicalException {
+    public void xor() throws IndicatorsException {
         criteria.setLogicalOperator(LogicalOperator.XOR);
         assertTrue(criteria.getFormula(data0) + " must be true",
                 criteria.eval(data0));
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/criteria/FormulaCriteriaTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/criteria/FormulaCriteriaTest.java
index 53fa19e2d81d32c6e79c9fc00057c991b9440753..deb63d0b9427736b2261d5af59a1a50a1988bfb8 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/criteria/FormulaCriteriaTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/criteria/FormulaCriteriaTest.java
@@ -18,6 +18,7 @@
  */
 package fr.inrae.agroclim.indicators.model.criteria;
 
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -32,7 +33,6 @@ import java.util.Set;
 
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
 import fr.inrae.agroclim.indicators.model.EvaluationSettings;
 import fr.inrae.agroclim.indicators.model.ExpressionParameter;
 import fr.inrae.agroclim.indicators.model.data.Variable;
@@ -73,14 +73,14 @@ public class FormulaCriteriaTest {
     }
 
     @Test
-    public void formulaFunction() throws TechnicalException {
+    public void formulaFunction() throws IndicatorsException {
         final FormulaCriteria crit = new FormulaCriteria();
         crit.setExpression("formulaCriteria:between(TMEAN, 10, 20)");
         assertTrue(crit.eval(data));
     }
 
     @Test
-    public void formulaWithParameter() throws TechnicalException {
+    public void formulaWithParameter() throws IndicatorsException {
         final FormulaCriteria crit = new FormulaCriteria();
         crit.setExpression("formulaCriteria:between(TMEAN, min, max)");
         final List<ExpressionParameter> expressionParameters = new ArrayList<>();
@@ -120,8 +120,8 @@ public class FormulaCriteriaTest {
         assertTrue(variables.contains(Variable.TMEAN));
     }
 
-    @Test(expected = TechnicalException.class)
-    public void wrongFunction() throws TechnicalException {
+    @Test(expected = IndicatorsException.class)
+    public void wrongFunction() throws IndicatorsException {
         final FormulaCriteria crit = new FormulaCriteria();
         crit.setExpression("that:notExists(TMEAN, 10, 20)");
         crit.eval(data);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/criteria/SimpleCriteriaTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/criteria/SimpleCriteriaTest.java
index ca9a0d883ffc8450fe8f48803cb06119fd5e3b91..774fefe1f5539e85577ba72f7de833a51a87c124 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/criteria/SimpleCriteriaTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/criteria/SimpleCriteriaTest.java
@@ -1,5 +1,6 @@
 package fr.inrae.agroclim.indicators.model.criteria;
 
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -13,7 +14,6 @@ import java.nio.charset.StandardCharsets;
 
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
 import fr.inrae.agroclim.indicators.model.data.Variable;
 import fr.inrae.agroclim.indicators.model.data.climate.ClimaticDailyData;
 import fr.inrae.agroclim.indicators.xml.XMLUtil;
@@ -48,7 +48,7 @@ public class SimpleCriteriaTest {
     }
 
     @Test
-    public void gt() throws TechnicalException {
+    public void gt() throws IndicatorsException {
         final double dek = 10.;
         final SimpleCriteria criteria = new SimpleCriteria();
         criteria.setOperator(RelationalOperator.GT);
@@ -59,7 +59,7 @@ public class SimpleCriteriaTest {
     }
 
     @Test
-    public void le() throws TechnicalException {
+    public void le() throws IndicatorsException {
         final double dek = 10.;
         final SimpleCriteria criteria = new SimpleCriteria();
         criteria.setOperator(RelationalOperator.LE);
@@ -70,7 +70,7 @@ public class SimpleCriteriaTest {
     }
 
     @Test
-    public void inferiorToThreshold() throws TechnicalException {
+    public void inferiorToThreshold() throws IndicatorsException {
         final double dek = 10.;
         final SimpleCriteria criteria = new SimpleCriteria();
         criteria.setInferiorToThreshold(true);
@@ -86,7 +86,7 @@ public class SimpleCriteriaTest {
     }
 
     @Test(expected = Test.None.class)
-    public void serializeInferiorToThreshold() throws TechnicalException, UnsupportedEncodingException {
+    public void serializeInferiorToThreshold() throws IndicatorsException, UnsupportedEncodingException {
         final Class<?>[] classes = new Class<?>[]{ Criteria.class, SimpleCriteria.class };
         final String expected = """
                                 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
@@ -111,7 +111,7 @@ public class SimpleCriteriaTest {
     }
 
     @Test(expected = Test.None.class)
-    public void unserializeGT() throws TechnicalException {
+    public void unserializeGT() throws IndicatorsException {
         final Class<?>[] classes = new Class<?>[]{ Criteria.class, SimpleCriteria.class };
         final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
                 + "<criteria xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"simpleCriteria\">"
@@ -131,7 +131,7 @@ public class SimpleCriteriaTest {
     }
 
     @Test(expected = Test.None.class)
-    public void unserializeInferiorToThreshold() throws TechnicalException {
+    public void unserializeInferiorToThreshold() throws IndicatorsException {
         final Class<?>[] classes = new Class<?>[]{ Criteria.class, SimpleCriteria.class };
         final String xml = """
                            <?xml version="1.0" encoding="UTF-8" ?><criteria xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="simpleCriteria">    <variable>th</variable>
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/data/DataTestHelper.java b/src/test/java/fr/inrae/agroclim/indicators/model/data/DataTestHelper.java
index ea7c486419b4f47f794d746f54d68cf74a163b4e..f2394894af84529a6fb14c01611e18f5549a7129 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/data/DataTestHelper.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/data/DataTestHelper.java
@@ -16,6 +16,7 @@
  */
 package fr.inrae.agroclim.indicators.model.data;
 
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -28,7 +29,6 @@ import java.util.Map;
 import java.util.Properties;
 import java.util.stream.Collectors;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
 import fr.inrae.agroclim.indicators.model.Evaluation;
 import fr.inrae.agroclim.indicators.model.EvaluationSettings;
 import fr.inrae.agroclim.indicators.model.data.climate.ClimateFileLoader;
@@ -269,7 +269,7 @@ public abstract class DataTestHelper {
             settings = (EvaluationSettings) XMLUtil.load(file, EvaluationSettings.CLASSES_FOR_JAXB);
             settings.initializeKnowledge();
             settings.setFilePath(file.getAbsolutePath());
-        } catch (final TechnicalException ex) {
+        } catch (final IndicatorsException ex) {
             LOGGER.error(ex.getLocalizedMessage());
             return null;
         }
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/data/ResourceManagerTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/data/ResourceManagerTest.java
index 6880d2ba053868cee21d0ba2cbee83ad8075ae70..fea3003051dd44bba111545db20280fe1024104a 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/data/ResourceManagerTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/data/ResourceManagerTest.java
@@ -31,17 +31,17 @@ import java.util.Set;
 import org.junit.Test;
 
 import fr.inrae.agroclim.indicators.exception.ErrorMessage;
-import fr.inrae.agroclim.indicators.model.data.ResourceManager.ErrorI18nKey;
+import fr.inrae.agroclim.indicators.exception.type.ResourceErrorType;
 import fr.inrae.agroclim.indicators.model.data.phenology.AnnualStageData;
 import fr.inrae.agroclim.indicators.resources.Messages;
 
 /**
  * Test the resource manager.
  *
- * Last changed : $Date$
+ * Last changed : $Date: 2023-03-16 17:36:45 +0100 (jeu. 16 mars 2023) $
  *
- * @author $Author$
- * @version $Revision$
+ * @author $Author: omaury $
+ * @version $Revision: 644 $
  */
 public final class ResourceManagerTest extends DataTestHelper {
 
@@ -53,11 +53,11 @@ public final class ResourceManagerTest extends DataTestHelper {
         final ResourceManager mgr = new ResourceManager();
 
         // without variables
-        Map<ErrorI18nKey, ErrorMessage> errors = mgr.getConsitencyErrors();
+        Map<ResourceErrorType, ErrorMessage> errors = mgr.getConsitencyErrors();
         assertNotNull("Empty ResourceManager must return consistency errors!",
                 errors);
-        final Set<ErrorI18nKey> expectedErrors = new HashSet<>();
-        expectedErrors.add(ErrorI18nKey.VARIABLES);
+        final Set<ResourceErrorType> expectedErrors = new HashSet<>();
+        expectedErrors.add(ResourceErrorType.VARIABLES);
         assertEquals("ResourceManager without variables must have errors!",
                 expectedErrors, errors.keySet());
         expectedErrors.clear();
@@ -67,8 +67,8 @@ public final class ResourceManagerTest extends DataTestHelper {
         variables.add(Variable.TMEAN);
         variables.add(Variable.SOILWATERCONTENT);
         mgr.setVariables(variables);
-        expectedErrors.addAll(Arrays.asList(ErrorI18nKey.CLIMATE,
-                ErrorI18nKey.PHENO));
+        expectedErrors.addAll(Arrays.asList(ResourceErrorType.CLIMATE,
+                ResourceErrorType.PHENO));
         errors = mgr.getConsitencyErrors();
         assertNotNull("Empty ResourceManager must return consistency errors!",
                 errors);
@@ -89,14 +89,14 @@ public final class ResourceManagerTest extends DataTestHelper {
                 "radiation", "rain", "tmean", "tmin", "tmax", "rh", "wind"};
         mgr.getClimaticResource().setData(getClimaticData(
                 "climate-missing-data.csv", ";", headers));
-        final Map<ErrorI18nKey, ErrorMessage> errors = mgr.getConsitencyErrors();
+        final Map<ResourceErrorType, ErrorMessage> errors = mgr.getConsitencyErrors();
         final String subject = "ResourceManager with missing days in climatic "
                 + "resource ";
         assertNotNull(subject + "must return consistency errors!", errors);
         assertTrue(subject + "must have errors on climatic resources!",
-                errors.keySet().contains(ErrorI18nKey.CLIMATE));
+                errors.keySet().contains(ResourceErrorType.CLIMATE));
         assertEquals(subject + "must have errors on climatic resources!",
-                errors.get(ErrorI18nKey.CLIMATE).getType().getI18nKey(), ErrorI18nKey.CLIMATE_SIZE_WRONG.getI18nKey());
+                errors.get(ResourceErrorType.CLIMATE).getType().getI18nKey(), ResourceErrorType.CLIMATE_SIZE_WRONG.getI18nKey());
     }
 
     /**
@@ -111,7 +111,7 @@ public final class ResourceManagerTest extends DataTestHelper {
         mgr.setVariables(variables);
         mgr.getClimaticResource().setData(getClimatic2015Data());
         mgr.getPhenologicalResource().setData(getPheno2015Data());
-        final Map<ErrorI18nKey, ErrorMessage> errors = mgr.getConsitencyErrors();
+        final Map<ResourceErrorType, ErrorMessage> errors = mgr.getConsitencyErrors();
         assertNull(errors);
     }
 
@@ -125,11 +125,11 @@ public final class ResourceManagerTest extends DataTestHelper {
         variables.add(Variable.TMEAN);
         mgr.setVariables(variables);
         mgr.getClimaticResource().setData(getClimatic2015Data());
-        final Map<ErrorI18nKey, ErrorMessage> errors = mgr.getConsitencyErrors();
+        final Map<ResourceErrorType, ErrorMessage> errors = mgr.getConsitencyErrors();
         final String subject = "ResourceManager with only climatic resource ";
         assertNotNull(subject + "must return consistency errors!", errors);
-        final Set<ErrorI18nKey> expectedErrors = new HashSet<>();
-        expectedErrors.add(ErrorI18nKey.PHENO);
+        final Set<ResourceErrorType> expectedErrors = new HashSet<>();
+        expectedErrors.add(ResourceErrorType.PHENO);
         assertEquals(subject + "must have errors on pheno resources",
                 expectedErrors, errors.keySet());
     }
@@ -148,10 +148,10 @@ public final class ResourceManagerTest extends DataTestHelper {
         final List<AnnualStageData> data = getPhenoSampleData();
         data.remove(data.size() - 1);
         mgr.getPhenologicalResource().setData(data);
-        final Map<ErrorI18nKey, ErrorMessage> errors = mgr.getConsitencyErrors();
+        final Map<ResourceErrorType, ErrorMessage> errors = mgr.getConsitencyErrors();
         assertNotNull(errors);
-        final Set<ErrorI18nKey> expectedErrors = new HashSet<>();
-        expectedErrors.add(ErrorI18nKey.CLIMATIC_YEARS);
+        final Set<ResourceErrorType> expectedErrors = new HashSet<>();
+        expectedErrors.add(ResourceErrorType.CLIMATE_YEARS);
         final String subject = "ResourceManager with only climatic data in 2015 "
                 + "must have errors on climatic resources";
         assertEquals(subject, expectedErrors, errors.keySet());
@@ -162,7 +162,7 @@ public final class ResourceManagerTest extends DataTestHelper {
      */
     @Test
     public void error18nKey() {
-        for (final ErrorI18nKey val : ErrorI18nKey.values()) {
+        for (final ResourceErrorType val : ResourceErrorType.values()) {
             if (val.getParent() != null) {
                 assertFalse(val.getI18nKey() + " must be translated!", Messages.get(val.getI18nKey()).startsWith("!"));
             }
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/data/climate/ClimateTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/data/climate/ClimateTest.java
index f9d1d7c9f2be60200d217c3b7bf2fd2b09052a21..f5b291a9e4601a3dd7361611875c4aed135798aa 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/data/climate/ClimateTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/data/climate/ClimateTest.java
@@ -16,6 +16,7 @@
  */
 package fr.inrae.agroclim.indicators.model.data.climate;
 
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -26,7 +27,6 @@ import java.util.Map;
 
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
 import fr.inrae.agroclim.indicators.model.EvaluationSettings;
 import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
 
@@ -62,8 +62,8 @@ public final class ClimateTest extends DataTestHelper {
             assertNotNull("Loaded ClimaticDailyData list must not be null", data);
             assertFalse("Loaded ClimaticDailyData list must not be empty",
                     data.isEmpty());
-        } catch (final TechnicalException ex) {
-            error = ex.getMessage() + " " + ex.getRootException().getMessage();
+        } catch (final IndicatorsException ex) {
+            error = ex.getMessage() + " " + ex.getCause().getMessage();
         }
         assertNull(error, error);
     }
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/data/phenology/PhenologyLoaderTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/data/phenology/PhenologyLoaderTest.java
index 949a930a11ef38fb59cf2b62485274c36f2ebac4..ee655a1476df77d1daaeb1abfc8d9661aad3a644 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/data/phenology/PhenologyLoaderTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/data/phenology/PhenologyLoaderTest.java
@@ -16,6 +16,7 @@
  */
 package fr.inrae.agroclim.indicators.model.data.phenology;
 
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
@@ -23,7 +24,6 @@ import java.util.List;
 
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
 import fr.inrae.agroclim.indicators.model.EvaluationSettings;
 import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
 import fr.inrae.agroclim.indicators.xml.XMLUtil;
@@ -59,8 +59,8 @@ public final class PhenologyLoaderTest extends DataTestHelper {
             List<AnnualStageData> data = e.getPhenologyLoader().load();
             assertTrue("loaded data must not be null", data != null);
             assertTrue("loaded data must not be empty", !data.isEmpty());
-        } catch (final TechnicalException ex) {
-            error = ex.getMessage() + " " + ex.getRootException().getMessage();
+        } catch (final IndicatorsException ex) {
+            error = ex.getMessage() + " " + ex.getCause().getMessage();
         }
         assertTrue(error, error == null);
     }
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/data/soil/SoilCalculatorTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/data/soil/SoilCalculatorTest.java
index dccd1bab3171043af78a97653797d75375e3b59e..47e88c4a300d8fa87aa88edb6f02f5633192cc4d 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/data/soil/SoilCalculatorTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/data/soil/SoilCalculatorTest.java
@@ -17,7 +17,7 @@
 package fr.inrae.agroclim.indicators.model.data.soil;
 
 import fr.inrae.agroclim.indicators.exception.ErrorMessage;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.type.ResourceErrorType;
 import fr.inrae.agroclim.indicators.model.Evaluation;
 import fr.inrae.agroclim.indicators.model.EvaluationSettings;
 import static org.junit.Assert.assertEquals;
@@ -51,10 +51,10 @@ import java.util.TimeZone;
 /**
  * Test Soil data calculator from ClimaticDailyData.
  *
- * Last changed : $Date$
+ * Last changed : $Date: 2023-05-30 15:49:13 +0200 (mar. 30 mai 2023) $
  *
- * @author $Author$
- * @version $Revision$
+ * @author $Author: omaury $
+ * @version $Revision: 656 $
  */
 public final class SoilCalculatorTest extends DataTestHelper {
 
@@ -273,7 +273,7 @@ public final class SoilCalculatorTest extends DataTestHelper {
         final int haltComparison = 582;
         final double tolerance = 0.01;
         final String diffMsg = """
-                               
+
                                At %d-%02d-%02d (%d), %s=%.3f != expected %.3f!""";
         List<String> computationErrors = new ArrayList<>();
         int i = 0;
@@ -348,11 +348,10 @@ public final class SoilCalculatorTest extends DataTestHelper {
     /**
      * Test integration into {@link Evaluation}.
      *
-     * @throws TechnicalException if knowledge does not load.
      * @throws IOException while loading phenology properties
      */
     @Test
-    public void loadInEvaluation() throws TechnicalException, IOException {
+    public void loadInEvaluation() throws IOException {
         final String baseName = "pheno_curve_grapevine_sw_4_stages-chardonnay";
         final Properties ini = getPhenologyProperties(baseName);
         // 1. load Evaluation
@@ -371,7 +370,7 @@ public final class SoilCalculatorTest extends DataTestHelper {
         settings.getSoilLoader().setCalculator(calc);
         // 4. initialize resources
         evaluation.initializeResources();
-        final Map<ResourceManager.ErrorI18nKey, ErrorMessage> errors;
+        final Map<ResourceErrorType, ErrorMessage> errors;
         errors = evaluation.getResourceManager().getConsitencyErrors();
         assertNull("No error must be found", errors);
         // Tests
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/function/aggregation/JEXLFunctionTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/function/aggregation/JEXLFunctionTest.java
index eedc6f65c8472a614e62e164af58cd56f8988ac1..5b226497c309506bc1e4375f851e923da31d8632 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/function/aggregation/JEXLFunctionTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/function/aggregation/JEXLFunctionTest.java
@@ -23,7 +23,7 @@ import java.util.HashMap;
 
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 
 /**
  * Test JEXLFunction.
@@ -52,12 +52,12 @@ public final class JEXLFunctionTest {
             final Double expected = a * MathMethod.min(b, 2.) - 10;
             final Double actual = func.aggregate(data);
             assertEquals(expected, actual);
-        } catch (final FunctionalException ex) {
-            error = ex.getMessage() + ex.getRootException();
+        } catch (final IndicatorsException ex) {
+            error = ex.getMessage();
         }
         assertNull(error);
     }
-    
+
     /**
      * Test expression with variable names starting with $.
      */
@@ -75,8 +75,8 @@ public final class JEXLFunctionTest {
             final Double expected = one * MathMethod.min(two, 2.) - 10;
             final Double actual = func.aggregate(data);
             assertEquals(expected, actual);
-        } catch (final FunctionalException ex) {
-            error = ex.getMessage() + ex.getRootException();
+        } catch (final IndicatorsException ex) {
+            error = ex.getMessage();
         }
         assertNull(error);
     }
@@ -99,8 +99,8 @@ public final class JEXLFunctionTest {
             final Double expected = one * MathMethod.min(two, 2) - 10;
             final Double actual = func.aggregate(data);
             assertEquals(expected, actual);
-        } catch (final FunctionalException ex) {
-            error = ex.getMessage() + ex.getRootException();
+        } catch (final IndicatorsException ex) {
+            error = ex.getMessage();
         }
         assertNull(error);
     }
@@ -119,8 +119,8 @@ public final class JEXLFunctionTest {
             final Double expected = 0.;
             assertEquals(expected, actual);
 
-        } catch (final FunctionalException ex) {
-            error = ex.getMessage() + ex.getRootException();
+        } catch (final IndicatorsException ex) {
+            error = ex.getMessage();
         }
         assertNull(error);
     }
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/AverageOfDiffTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/AverageOfDiffTest.java
index 790e6c5935f6ec413df17989251e08f355fc4b8f..7c73ef0c6a932648ce45256050939d9ecfa08bb8 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/AverageOfDiffTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/AverageOfDiffTest.java
@@ -18,8 +18,7 @@
  */
 package fr.inrae.agroclim.indicators.model.indicator;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
 import fr.inrae.agroclim.indicators.model.data.Variable;
 import fr.inrae.agroclim.indicators.model.data.climate.ClimaticResource;
@@ -69,7 +68,7 @@ public class AverageOfDiffTest extends DataTestHelper {
         String error = null;
         try {
             result = indicator.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (final IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         assertNull(error, error );
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/AverageTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/AverageTest.java
index 11db4ece7d1893fc654f99d151666b2d04e058cd..7433ba01907e2b1de35a3020dfb87b8be31f7991 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/AverageTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/AverageTest.java
@@ -32,8 +32,7 @@ import java.nio.file.Path;
 
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.Knowledge;
 import fr.inrae.agroclim.indicators.model.TimeScale;
 import fr.inrae.agroclim.indicators.model.criteria.NoCriteria;
@@ -80,7 +79,7 @@ public class AverageTest extends DataTestHelper {
         String error = null;
         try {
             result = indicator.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         assertEquals(null, error);
@@ -94,7 +93,7 @@ public class AverageTest extends DataTestHelper {
     }
 
     @Test
-    public void mint() throws TechnicalException, FunctionalException, CloneNotSupportedException {
+    public void mint() throws IndicatorsException, CloneNotSupportedException {
         final Knowledge knowledge = Knowledge.load(TimeScale.DAILY);
         final String indicatorId = "mint";
         final IndicatorCategory expectedCat = IndicatorCategory.INDICATORS;
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/ColdsumtminTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/ColdsumtminTest.java
index c8c02d5ac4496e91dfe0a1f35c7ace7aaa257ec2..49bbe20020d6b16b8850901ecc5e3695b53129ae 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/ColdsumtminTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/ColdsumtminTest.java
@@ -20,8 +20,7 @@ package fr.inrae.agroclim.indicators.model.indicator;
 
 import static junit.framework.TestCase.assertEquals;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.criteria.RelationalOperator;
 import fr.inrae.agroclim.indicators.model.criteria.SimpleCriteria;
 import org.junit.Test;
@@ -53,7 +52,7 @@ public class ColdsumtminTest extends DataTestHelper {
     }
 
     @Test
-    public void test() throws TechnicalException, FunctionalException {
+    public void test() throws IndicatorsException {
         final Sum indicator = new Sum();
         final SimpleCriteria criteria = new SimpleCriteria();
         criteria.setVariable(Variable.TMIN);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/DayOfYearTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/DayOfYearTest.java
index 7d769ac85e5a02e3e47dfa006f54228816c7f975..a9ef8c8c6f89139356a4f39becc2517b6338e8d2 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/DayOfYearTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/DayOfYearTest.java
@@ -21,8 +21,7 @@ import static org.junit.Assert.assertNull;
 
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.criteria.Criteria;
 import fr.inrae.agroclim.indicators.model.criteria.RelationalOperator;
 import fr.inrae.agroclim.indicators.model.criteria.SimpleCriteria;
@@ -76,7 +75,7 @@ public final class DayOfYearTest extends DataTestHelper {
         String error = null;
         try {
             result = indicator.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (final IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         assertNull(error, error);
@@ -96,7 +95,7 @@ public final class DayOfYearTest extends DataTestHelper {
         String error = null;
         try {
             result = indicator.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (final IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         assertNull(error, error);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/DiffOfSumTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/DiffOfSumTest.java
index b371057d7b85c342e8668c60bc7f90ec87fbae06..8291e26f10d3f779c276af6f70d7dbfebf756c58 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/DiffOfSumTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/DiffOfSumTest.java
@@ -5,8 +5,7 @@ import static org.junit.Assert.assertNull;
 
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.Knowledge;
 import fr.inrae.agroclim.indicators.model.criteria.SimpleCriteria;
 import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
@@ -62,7 +61,7 @@ public class DiffOfSumTest extends DataTestHelper {
         String error = null;
         try {
             result = indicator.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (final IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         assertNull(error, error);
@@ -72,9 +71,9 @@ public class DiffOfSumTest extends DataTestHelper {
 
     /**
      * sumwd is also defined in knowledge.xml
-     * @throws TechnicalException while reading the XML
+     * @throws IndicatorsException while reading the XML
      */
-    public void sumwdFromKnowledge() throws TechnicalException {
+    public void sumwdFromKnowledge() throws IndicatorsException {
         final Knowledge knowledge = Knowledge.load();
         final SimpleIndicator sumwd = (SimpleIndicator) knowledge.getIndicator("sumwd");
         final double expected = climaticData.getData().stream()
@@ -85,7 +84,7 @@ public class DiffOfSumTest extends DataTestHelper {
         String error = null;
         try {
             result = sumwd.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (final IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         assertNull(error, error);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/FormulaTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/FormulaTest.java
index 5b81634e5c7ac493c6b508395ba62255b7ced6e9..d132cdf4da937a5c45a781258a4de4cd2f567635 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/FormulaTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/FormulaTest.java
@@ -36,8 +36,7 @@ import com.fasterxml.jackson.databind.MappingIterator;
 import com.fasterxml.jackson.dataformat.csv.CsvMapper;
 import com.fasterxml.jackson.dataformat.csv.CsvSchema;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
 import fr.inrae.agroclim.indicators.model.data.climate.ClimaticDailyData;
 import fr.inrae.agroclim.indicators.model.data.climate.ClimaticResource;
@@ -121,7 +120,7 @@ public class FormulaTest extends DataTestHelper {
     }
 
     @Test
-    public void computeSingleValue() throws TechnicalException, FunctionalException {
+    public void computeSingleValue() throws IndicatorsException {
         assertNotNull(this.data);
         assertNotNull(this.data.getRh());
         assertNotNull(this.data.getTh());
@@ -132,7 +131,7 @@ public class FormulaTest extends DataTestHelper {
     }
 
     @Test
-    public void computeWithParameters() throws TechnicalException, FunctionalException {
+    public void computeWithParameters() throws IndicatorsException {
         assertNotNull(this.data);
         assertNotNull(this.data.getTh());
         final double paramA = 10.;
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/FrequencyTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/FrequencyTest.java
index 1b31ee4425e05e55b129ab61cf75de806721383f..2a0ea149e51adbbf1cc02f9a7f2a46b1173a43ae 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/FrequencyTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/FrequencyTest.java
@@ -18,8 +18,7 @@
  */
 package fr.inrae.agroclim.indicators.model.indicator;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.criteria.NoCriteria;
 import fr.inrae.agroclim.indicators.model.criteria.SimpleCriteria;
 import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
@@ -67,7 +66,7 @@ public class FrequencyTest extends DataTestHelper {
         String error = null;
         try {
             result = indicator.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (final IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         assertTrue(error, error == null);
@@ -76,7 +75,7 @@ public class FrequencyTest extends DataTestHelper {
         nbOfDays.setCriteria(new NoCriteria());
         try {
             result = indicator.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (final IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         assertTrue(error, error == null);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/ImplementationsTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/ImplementationsTest.java
index 69b9dca851083dc57123cc88253646f14a75f9ad..ebe244b2159c876d01ff582dd04d0f43927f88bb 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/ImplementationsTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/ImplementationsTest.java
@@ -16,7 +16,7 @@
  */
 package fr.inrae.agroclim.indicators.model.indicator;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.Knowledge;
 import java.util.ArrayList;
 import java.util.List;
@@ -61,7 +61,7 @@ public class ImplementationsTest {
                     indicators.add(ind);
                 });
             }
-        } catch (final TechnicalException ex) {
+        } catch (final IndicatorsException ex) {
             LOGGER.fatal("Loading knowledge should not fail!", ex);
         }
         return indicators;
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/IndicatorTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/IndicatorTest.java
index b70220dd0c54e098855455cf0f2044b8c18ac342..0ca49112e61df0d174120e129072c7baa6863b2a 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/IndicatorTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/IndicatorTest.java
@@ -19,7 +19,7 @@ package fr.inrae.agroclim.indicators.model.indicator;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.util.Map;
 import java.util.Set;
@@ -27,8 +27,7 @@ import java.util.Set;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.EvaluationType;
 import fr.inrae.agroclim.indicators.model.Knowledge;
 import fr.inrae.agroclim.indicators.model.Parameter;
@@ -54,8 +53,7 @@ public class IndicatorTest extends DataTestHelper {
         private static final long serialVersionUID = 1L;
 
         @Override
-        public Double compute(final Resource<? extends DailyData> data)
-                throws TechnicalException, FunctionalException {
+        public Double compute(final Resource<? extends DailyData> data) throws IndicatorsException {
             throw new UnsupportedOperationException("Not supported yet.");
         }
 
@@ -107,7 +105,7 @@ public class IndicatorTest extends DataTestHelper {
 		@Override
 		public void removeParameter(final Parameter param) {
 			throw new UnsupportedOperationException("Not supported yet.");
-			
+
 		}
 
     }
@@ -120,8 +118,8 @@ public class IndicatorTest extends DataTestHelper {
     public static void loadXml() {
         try {
             knowledge = Knowledge.load(TimeScale.DAILY);
-        } catch (final TechnicalException ex) {
-            assertTrue("Loading XML file from Knowledge.load() should work!", false);
+        } catch (final IndicatorsException ex) {
+            fail("Loading XML file from Knowledge.load() should work!");
         }
     }
     @Test
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/MaxWaveLengthTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/MaxWaveLengthTest.java
index 88867844cdd8bce4b02ba8119965e60bbe823873..1291270158d127d639bdfd30039cc49c6035d618 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/MaxWaveLengthTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/MaxWaveLengthTest.java
@@ -5,8 +5,7 @@ import static org.junit.Assert.assertTrue;
 
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.criteria.Criteria;
 import fr.inrae.agroclim.indicators.model.criteria.RelationalOperator;
 import fr.inrae.agroclim.indicators.model.criteria.SimpleCriteria;
@@ -53,7 +52,7 @@ public class MaxWaveLengthTest extends DataTestHelper {
         String error = null;
         try {
             result = instance.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (final IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         assertTrue(error, error == null);
@@ -77,7 +76,7 @@ public class MaxWaveLengthTest extends DataTestHelper {
         String error = null;
         try {
             result = instance.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (final IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         assertTrue(error, error == null);
@@ -86,17 +85,11 @@ public class MaxWaveLengthTest extends DataTestHelper {
 
     /**
      * Without criteria: raise exception.
+     * @throws IndicatorsException expected exception while compute
      */
-    @Test(expected = RuntimeException.class)
-    public void withoutCriteria() {
+    @Test(expected = IndicatorsException.class)
+    public void withoutCriteria() throws IndicatorsException {
         final MaxWaveLength instance = new MaxWaveLength();
-
-        String error = null;
-        try {
-            instance.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
-            error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
-        }
-        assertTrue(error, error == null);
+        instance.computeSingleValue(climaticData);
     }
 }
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/NumberOfDaysTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/NumberOfDaysTest.java
index 9b4dba30cd8721f0c662141f428bb60bf8361a05..4ad1a5d16cf7bdbde2b3043c5a65cb813d7e056b 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/NumberOfDaysTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/NumberOfDaysTest.java
@@ -21,8 +21,7 @@ import static org.junit.Assert.assertTrue;
 
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.criteria.ComparisonCriteria;
 import fr.inrae.agroclim.indicators.model.criteria.Criteria;
 import fr.inrae.agroclim.indicators.model.criteria.RelationalOperator;
@@ -73,7 +72,7 @@ public final class NumberOfDaysTest extends DataTestHelper {
         String error = null;
         try {
             result = instance.compute(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (final IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         assertTrue(error, error == null);
@@ -93,7 +92,7 @@ public final class NumberOfDaysTest extends DataTestHelper {
         String error = null;
         try {
             result = instance.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (final IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         assertTrue(error, error == null);
@@ -114,7 +113,7 @@ public final class NumberOfDaysTest extends DataTestHelper {
         String error = null;
         try {
             result = instance.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (final IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         assertTrue(error, error == null);
@@ -133,7 +132,7 @@ public final class NumberOfDaysTest extends DataTestHelper {
         String error = null;
         try {
             result = instance.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (final IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         assertTrue(error, error == null);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/NumberOfWavesTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/NumberOfWavesTest.java
index 368d348e3a638e913d22ccd38204faed0ffd206c..1e993f072e3f058b910e0ba4edd62e616ca92bb9 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/NumberOfWavesTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/NumberOfWavesTest.java
@@ -23,8 +23,7 @@ import static org.junit.Assert.assertTrue;
 
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.criteria.Criteria;
 import fr.inrae.agroclim.indicators.model.criteria.RelationalOperator;
 import fr.inrae.agroclim.indicators.model.criteria.SimpleCriteria;
@@ -75,7 +74,7 @@ public final class NumberOfWavesTest extends DataTestHelper {
         String error = null;
         try {
             result = instance.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (final IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         assertTrue(error, error == null);
@@ -84,17 +83,11 @@ public final class NumberOfWavesTest extends DataTestHelper {
 
     /**
      * Without criteria: raise exception.
+     * @throws IndicatorsException expected exception while compute
      */
-    @Test(expected = RuntimeException.class)
-    public void withoutCriteria() {
+    @Test(expected = IndicatorsException.class)
+    public void withoutCriteria() throws IndicatorsException {
         final NumberOfWaves instance = new NumberOfWaves();
-
-        String error = null;
-        try {
-            instance.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
-            error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
-        }
-        assertTrue(error, error == null);
+        instance.computeSingleValue(climaticData);
     }
 }
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/PhaseLengthTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/PhaseLengthTest.java
index 60c03cd335060f2a04c49540e69bfa1e804dab52..72dad37264ae2700494eae8e8f244f8d15a57228 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/PhaseLengthTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/PhaseLengthTest.java
@@ -18,8 +18,7 @@
  */
 package fr.inrae.agroclim.indicators.model.indicator;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
 import fr.inrae.agroclim.indicators.model.data.climate.ClimaticResource;
 import static junit.framework.TestCase.assertEquals;
@@ -62,7 +61,7 @@ public class PhaseLengthTest extends DataTestHelper {
         String error = null;
         try {
             result = indicator.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         assertTrue(error, error == null);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/PhotothermalQuotientTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/PhotothermalQuotientTest.java
index 2a6486599f70f4c850165e130e8b438eb2764a01..a38ca0fb7feb7488a3f7cab88220bd4fe9fb085b 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/PhotothermalQuotientTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/PhotothermalQuotientTest.java
@@ -23,8 +23,7 @@ import static org.junit.Assert.assertNotEquals;
 
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.criteria.RelationalOperator;
 import fr.inrae.agroclim.indicators.model.criteria.SimpleCriteria;
 import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
@@ -77,7 +76,7 @@ public class PhotothermalQuotientTest extends DataTestHelper {
     }
 
     @Test
-    public void computeSingleValue() throws TechnicalException, FunctionalException {
+    public void computeSingleValue() throws IndicatorsException {
         final Double radiationSum = climaticData.getData().stream().map(ClimaticDailyData::getRadiation)
                 .reduce(0d, Double::sum);
         final Double tmeanSum = climaticData.getData().stream().map(ClimaticDailyData::getTmean)
@@ -89,7 +88,7 @@ public class PhotothermalQuotientTest extends DataTestHelper {
     }
 
     @Test
-    public void withNormalization() throws TechnicalException, FunctionalException {
+    public void withNormalization() throws IndicatorsException {
         indicator.getDividend().setNormalizationFunction(new Sigmoid());
         indicator.getDivisor().setNormalizationFunction(new Sigmoid());
         final double actual = indicator.computeSingleValue(climaticData);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/QuotientTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/QuotientTest.java
index c7d1b50316ab984486ea14e2ac7680b0eb6f5d82..e2b255cd828765e5715eb035768a0f31e5e883fb 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/QuotientTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/QuotientTest.java
@@ -21,8 +21,7 @@ import static org.junit.Assert.assertTrue;
 
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.criteria.Criteria;
 import fr.inrae.agroclim.indicators.model.criteria.RelationalOperator;
 import fr.inrae.agroclim.indicators.model.criteria.SimpleCriteria;
@@ -77,7 +76,7 @@ public final class QuotientTest extends DataTestHelper {
         String error = null;
         try {
             result = indicator.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         assertTrue(error, error == null);
@@ -97,7 +96,7 @@ public final class QuotientTest extends DataTestHelper {
         String error = null;
         try {
             result = ind.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         double expected = 10d;
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/SumTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/SumTest.java
index 65465aa4917a6318df356ba7ce44544814cf38e1..31e2e51e4cf60f9f9c5134f700d06c25cd84d3eb 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/SumTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/SumTest.java
@@ -5,8 +5,7 @@ import static org.junit.Assert.assertNull;
 
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.criteria.NoCriteria;
 import fr.inrae.agroclim.indicators.model.criteria.SimpleCriteria;
 import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
@@ -53,7 +52,7 @@ public class SumTest extends DataTestHelper {
         String error = null;
         try {
             result = indicator.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (final IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         assertNull(error, error);
@@ -79,7 +78,7 @@ public class SumTest extends DataTestHelper {
         String error = null;
         try {
             result = indicator.computeSingleValue(climaticData);
-        } catch (TechnicalException | FunctionalException ex) {
+        } catch (final IndicatorsException ex) {
             error = ex.getClass().getCanonicalName() + " : " + ex.getMessage();
         }
         assertNull(error, error);
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/TammFormulaTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/TammFormulaTest.java
index afa28025c7a5b9949d8d885a9ed0731c2863e75f..846182ddf3b6f8122f64fe6cb2169ba6fd16af16 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/TammFormulaTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/TammFormulaTest.java
@@ -33,8 +33,6 @@ import com.fasterxml.jackson.databind.MappingIterator;
 import com.fasterxml.jackson.dataformat.csv.CsvMapper;
 import com.fasterxml.jackson.dataformat.csv.CsvSchema;
 
-import fr.inrae.agroclim.indicators.exception.FunctionalException;
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
 import fr.inrae.agroclim.indicators.model.data.DataTestHelper;
 import fr.inrae.agroclim.indicators.model.data.climate.ClimaticDailyData;
 import lombok.Data;
@@ -123,7 +121,7 @@ public class TammFormulaTest extends DataTestHelper {
     }
 
     @Test
-    public void computeDailyValue() throws TechnicalException, FunctionalException {
+    public void computeDailyValue() {
         assertNotNull(data);
         final Tamm indicator = new Tamm();
         final double actual = indicator.computeDailyValue(fromTestData(data));
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/listener/CompositeIndicatorTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/listener/CompositeIndicatorTest.java
index 7b875fecea8a77fe5f2c508f797b0a6fb47d4f5e..06efd48440c7433b9637a5d3bdc96c1a2bd5f026 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/listener/CompositeIndicatorTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/listener/CompositeIndicatorTest.java
@@ -18,7 +18,7 @@
  */
 package fr.inrae.agroclim.indicators.model.indicator.listener;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.Knowledge;
 import fr.inrae.agroclim.indicators.model.indicator.CompositeIndicator;
 import fr.inrae.agroclim.indicators.model.indicator.Indicator;
@@ -39,7 +39,7 @@ public class CompositeIndicatorTest {
     private boolean listenerFired = false;
 
     @Test
-    public void addThenRemove() throws TechnicalException {
+    public void addThenRemove() throws IndicatorsException {
         CompositeIndicator c = new CompositeIndicator();
         c.addFunctionListener((final CompositeIndicator i) -> {
             listenerFired = true;
diff --git a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/listener/PropertyChangeListenerTest.java b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/listener/PropertyChangeListenerTest.java
index 22b835bcd245d9c0f377a195fd3432be0b8f63ff..90899b49cec6cd9103d4a9fdba260781794e9f7e 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/model/indicator/listener/PropertyChangeListenerTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/model/indicator/listener/PropertyChangeListenerTest.java
@@ -18,7 +18,7 @@
  */
 package fr.inrae.agroclim.indicators.model.indicator.listener;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import fr.inrae.agroclim.indicators.model.Knowledge;
 import fr.inrae.agroclim.indicators.model.criteria.SimpleCriteria;
 import fr.inrae.agroclim.indicators.model.indicator.Indicator;
@@ -51,7 +51,7 @@ public class PropertyChangeListenerTest {
     private IndicatorEvent.Type eventType;
 
     @Test
-    public void cdaystmin() throws TechnicalException {
+    public void cdaystmin() throws IndicatorsException {
         listenerFired = false;
         Knowledge k = Knowledge.load();
 
diff --git a/src/test/java/fr/inrae/agroclim/indicators/xml/XMLUtilTest.java b/src/test/java/fr/inrae/agroclim/indicators/xml/XMLUtilTest.java
index a8597c495d2b37a1a4a46b0d6d5a44b27a0a1045..447f6769f5e56cead51554b6389ea09d0651fbb2 100644
--- a/src/test/java/fr/inrae/agroclim/indicators/xml/XMLUtilTest.java
+++ b/src/test/java/fr/inrae/agroclim/indicators/xml/XMLUtilTest.java
@@ -16,6 +16,7 @@
  */
 package fr.inrae.agroclim.indicators.xml;
 
+import fr.inrae.agroclim.indicators.exception.IndicatorsException;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -34,7 +35,6 @@ import java.util.List;
 
 import org.junit.Test;
 
-import fr.inrae.agroclim.indicators.exception.TechnicalException;
 import fr.inrae.agroclim.indicators.model.Evaluation;
 import fr.inrae.agroclim.indicators.model.EvaluationSettings;
 import fr.inrae.agroclim.indicators.model.Knowledge;
@@ -67,7 +67,7 @@ public final class XMLUtilTest extends DataTestHelper {
         try {
             final File xmlFile = getEvaluationTestFile();
             XMLUtil.load(xmlFile, EvaluationSettings.CLASSES_FOR_JAXB);
-        } catch (final TechnicalException e) {
+        } catch (final IndicatorsException e) {
             final StringWriter sw = new StringWriter();
             final PrintWriter pw = new PrintWriter(sw);
             e.printStackTrace(pw);
@@ -172,7 +172,7 @@ public final class XMLUtilTest extends DataTestHelper {
             assertEquals("SoilPhenologyCalculators must be equal",
                     settings.getSoilPhenologyCalculator(), saved.getSoilPhenologyCalculator());
             assertEquals(settings, saved);
-        } catch (final IOException | TechnicalException e) {
+        } catch (final IOException | IndicatorsException e) {
             final StringWriter sw = new StringWriter();
             final PrintWriter pw = new PrintWriter(sw);
             e.printStackTrace(pw);
@@ -192,9 +192,9 @@ public final class XMLUtilTest extends DataTestHelper {
             final File xmlFile = new File(System.getProperty("user.dir")
                     + File.separator + "test/xml/does-not-exist.xml");
             XMLUtil.load(xmlFile, EvaluationSettings.CLASSES_FOR_JAXB);
-        } catch (final TechnicalException e) {
+        } catch (final IndicatorsException e) {
             exception = true;
-            if (e.getRootException() instanceof FileNotFoundException) {
+            if (e.getCause() instanceof FileNotFoundException) {
                 fileNotFoundException = true;
             }
         }
@@ -212,12 +212,12 @@ public final class XMLUtilTest extends DataTestHelper {
             final File xmlFile = getFile("knowledge.xml");
             assertNotNull("knowledge.xml must be found!", xmlFile);
             XMLUtil.load(xmlFile, Knowledge.CLASSES_FOR_JAXB);
-        } catch (final TechnicalException e) {
+        } catch (final IndicatorsException e) {
             LOGGER.catching(e);
             final StringBuilder sb = new StringBuilder();
             sb.append(e.getClass().getCanonicalName()).append(": ");
             sb.append(e.toString());
-            Throwable cause = e.getRootException();
+            Throwable cause = e.getCause();
             while (cause != null) {
                 sb.append(" : ").append(cause.getClass().getCanonicalName())
                 .append(": ").append(cause.getMessage());
@@ -233,11 +233,10 @@ public final class XMLUtilTest extends DataTestHelper {
      * Test saving a new evaluation.
      *
      * @throws java.io.IOException while creating tmp file.
-     * @throws fr.inrae.agroclim.indicators.exception.TechnicalException while
-     * serializing
+     * @throws IndicatorsException while serializing
      */
     @Test
-    public void saveEvaluation() throws IOException, TechnicalException {
+    public void saveEvaluation() throws IOException, IndicatorsException {
         final String name = "évaluation";
         final File clim = getClimate1951File();
         final File phen = getPhenoSampleFile();