diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/enums/SMPPropertyTypeEnum.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/enums/SMPPropertyTypeEnum.java
index 714a7bf8b71e043778c8e8db630480237c91a726..2acbeb11ffad546e504368e3aa45e71584cee6d8 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/enums/SMPPropertyTypeEnum.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/enums/SMPPropertyTypeEnum.java
@@ -25,6 +25,7 @@ package eu.europa.ec.edelivery.smp.config.enums;
  */
 public enum SMPPropertyTypeEnum {
     STRING (".{0,2000}","Property value [%s] must be less than 2000 characters!"),
+    DATETIME (".{0,2000}","Property value [%s] must be less than 2000 characters!"),
     LIST_STRING(".{0,2000}","Property [%s] is not valid LIST_STRING type!"),
     MAP_STRING(".{0,2000}","Property [%s] is not valid MAP_STRING type!"),
     INTEGER("\\d{0,12}","Property [%s] is not valid Integer!"),
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/CredentialsAlertService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/CredentialsAlertService.java
index 82dbb170da9ca5ee490c9136e23f97bc11912910..88956c554f8704f701d8e5c9920ce86991364f36 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/CredentialsAlertService.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/CredentialsAlertService.java
@@ -49,7 +49,7 @@ import java.time.OffsetDateTime;
 import java.util.Date;
 
 import static eu.europa.ec.edelivery.smp.cron.CronTriggerConfig.TRIGGER_BEAN_CREDENTIAL_ALERTS;
-import static java.time.format.DateTimeFormatter.ISO_DATE_TIME;
+import static eu.europa.ec.edelivery.smp.utils.DateTimeUtils.formatOffsetDateTimeWithLocal;
 import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME;
 
 /**
@@ -239,18 +239,16 @@ public class CredentialsAlertService {
                                           String credentialId,
                                           OffsetDateTime expirationDate
     ) {
-
-        String serverName = HttpUtils.getServerAddress();
+        DBUser user = credential.getUser();
         // add alert properties
         alert.addProperty(CredentialsExpirationProperties.CREDENTIAL_TYPE.name(), credentialType.name());
         alert.addProperty(CredentialsExpirationProperties.CREDENTIAL_ID.name(), credentialId);
-        alert.addProperty(CredentialsExpirationProperties.EXPIRATION_DATETIME.name(), expirationDate);
-        alert.addProperty(CredentialsExpirationProperties.REPORTING_DATETIME.name(), alert.getReportingTime());
+        alert.addProperty(CredentialsExpirationProperties.EXPIRATION_DATETIME.name(), formatOffsetDateTimeWithLocal(expirationDate, user.getSmpLocale()));
+        alert.addProperty(CredentialsExpirationProperties.REPORTING_DATETIME.name(), formatOffsetDateTimeWithLocal(expirationDate, user.getSmpLocale()));
         alert.addProperty(CredentialsExpirationProperties.ALERT_LEVEL.name(), alert.getAlertLevel().name());
-        alert.addProperty(CredentialsExpirationProperties.SERVER_NAME.name(), serverName);
         alertDao.persistFlushDetach(alert);
         // submit alerts
-        submitAlertMail(alert, credential.getUser());
+        submitAlertMail(alert, user);
         // when alert about to expire - check if the next cron execution is expired
         // and set date sent tp null to ensure alert submission in next cron execution
         credentialDao.updateAlertSentForUserCredentials(credential,
@@ -267,15 +265,13 @@ public class CredentialsAlertService {
                                                   OffsetDateTime lastFailedLoginDate
     ) {
         LOG.info("Prepare alert for credentials [{}] ", credentialId);
-        String serverName = HttpUtils.getServerAddress();
         // add alert properties
         alert.addProperty(CredentialVerificationFailedProperties.CREDENTIAL_TYPE.name(), credentialType.name());
         alert.addProperty(CredentialVerificationFailedProperties.CREDENTIAL_ID.name(), credentialId);
         alert.addProperty(CredentialVerificationFailedProperties.FAILED_LOGIN_ATTEMPT.name(), failedLoginCount.toString());
-        alert.addProperty(CredentialVerificationFailedProperties.LAST_LOGIN_FAILURE_DATETIME.name(), lastFailedLoginDate);
-        alert.addProperty(CredentialVerificationFailedProperties.REPORTING_DATETIME.name(), alert.getReportingTime());
+        alert.addProperty(CredentialVerificationFailedProperties.LAST_LOGIN_FAILURE_DATETIME.name(), formatOffsetDateTimeWithLocal(lastFailedLoginDate, user.getSmpLocale()));
+        alert.addProperty(CredentialVerificationFailedProperties.REPORTING_DATETIME.name(), formatOffsetDateTimeWithLocal(alert.getReportingTime(), user.getSmpLocale()));
         alert.addProperty(CredentialVerificationFailedProperties.ALERT_LEVEL.name(), alert.getAlertLevel().name());
-        alert.addProperty(CredentialVerificationFailedProperties.SERVER_NAME.name(), serverName);
         alertDao.persistFlushDetach(alert);
         // submit alerts
         submitAlertMail(alert, user);
@@ -289,16 +285,15 @@ public class CredentialsAlertService {
                                          OffsetDateTime lastFailedLoginDate,
                                          OffsetDateTime suspendedUtil) {
 
-        String serverName = HttpUtils.getServerAddress();
+
         // add alert properties
         alert.addProperty(CredentialSuspendedProperties.CREDENTIAL_TYPE.name(), credentialType.name());
         alert.addProperty(CredentialSuspendedProperties.CREDENTIAL_ID.name(), credentialId);
         alert.addProperty(CredentialSuspendedProperties.FAILED_LOGIN_ATTEMPT.name(), failedLoginCount.toString());
-        alert.addProperty(CredentialSuspendedProperties.LAST_LOGIN_FAILURE_DATETIME.name(), lastFailedLoginDate);
-        alert.addProperty(CredentialSuspendedProperties.SUSPENDED_UNTIL_DATETIME.name(), suspendedUtil);
-        alert.addProperty(CredentialSuspendedProperties.REPORTING_DATETIME.name(), alert.getReportingTime());
+        alert.addProperty(CredentialSuspendedProperties.LAST_LOGIN_FAILURE_DATETIME.name(), formatOffsetDateTimeWithLocal(lastFailedLoginDate, user.getSmpLocale()));
+        alert.addProperty(CredentialSuspendedProperties.SUSPENDED_UNTIL_DATETIME.name(), formatOffsetDateTimeWithLocal(suspendedUtil, user.getSmpLocale()));
+        alert.addProperty(CredentialSuspendedProperties.REPORTING_DATETIME.name(), formatOffsetDateTimeWithLocal(alert.getReportingTime(), user.getSmpLocale()));
         alert.addProperty(CredentialSuspendedProperties.ALERT_LEVEL.name(), alert.getAlertLevel().name());
-        alert.addProperty(CredentialSuspendedProperties.SERVER_NAME.name(), serverName);
         alertDao.persistFlushDetach(alert);
         // submit alerts
         submitAlertMail(alert, user);
@@ -404,7 +399,7 @@ public class CredentialsAlertService {
         // add alert properties
         alert.addProperty(CredentialsResetRequestProperties.CREDENTIAL_TYPE.name(), credentialType.name());
         alert.addProperty(CredentialsResetRequestProperties.CREDENTIAL_ID.name(), credentialId);
-        alert.addProperty(CredentialsResetRequestProperties.REPORTING_DATETIME.name(), alert.getReportingTime());
+        alert.addProperty(CredentialsResetRequestProperties.REPORTING_DATETIME.name(), formatOffsetDateTimeWithLocal(alert.getReportingTime(), user.getSmpLocale()));
         alert.addProperty(CredentialsResetRequestProperties.ALERT_LEVEL.name(), alert.getAlertLevel().name());
         alert.addProperty(CredentialsResetRequestProperties.RESET_URL.name(), resetUrlPath);
         alert.addProperty(CredentialsResetRequestProperties.SERVER_NAME.name(), serverName);
@@ -437,13 +432,11 @@ public class CredentialsAlertService {
                                        CredentialType credentialType,
                                        String credentialId) {
 
-        String serverName = HttpUtils.getServerAddress();
         // add alert properties
         alert.addProperty(CredentialsChangedProperties.CREDENTIAL_TYPE.name(), credentialType.name());
         alert.addProperty(CredentialsChangedProperties.CREDENTIAL_ID.name(), credentialId);
-        alert.addProperty(CredentialsChangedProperties.REPORTING_DATETIME.name(), alert.getReportingTime());
+        alert.addProperty(CredentialsChangedProperties.REPORTING_DATETIME.name(), formatOffsetDateTimeWithLocal(alert.getReportingTime(), user.getSmpLocale()));
         alert.addProperty(CredentialsChangedProperties.ALERT_LEVEL.name(), alert.getAlertLevel().name());
-        alert.addProperty(CredentialsChangedProperties.SERVER_NAME.name(), serverName);
         alertDao.persistFlushDetach(alert);
         // submit alerts
         submitAlertMail(alert, user);
@@ -463,6 +456,9 @@ public class CredentialsAlertService {
                                   AlertLevelEnum level,
                                   AlertTypeEnum alertType) {
 
+
+        String serverName = HttpUtils.getServerAddress();
+
         DBAlert alert = new DBAlert();
         alert.setMailSubject(mailSubject);
         alert.setMailTo(mailTo);
@@ -471,6 +467,7 @@ public class CredentialsAlertService {
         alert.setAlertType(alertType);
         alert.setAlertLevel(level);
         alert.setAlertStatus(AlertStatusEnum.PROCESS);
+        alert.addProperty(CredentialSuspendedProperties.SERVER_NAME.name(), serverName);
         return alert;
     }
 
@@ -487,13 +484,14 @@ public class CredentialsAlertService {
             return;
         }
 
-
         String mailFrom = configurationService.getAlertEmailFrom();
         MailDataModel props = new MailDataModel(user.getSmpLocale(), alert);
+
+        // add additional common properties to the model
         props.getModel().put(MailDataModel.CommonProperties.SMP_INSTANCE_NAME.name(),
                 configurationService.getSMPInstanceName());
         props.getModel().put(MailDataModel.CommonProperties.CURRENT_DATETIME.name(),
-                OffsetDateTime.now().format(ISO_DATE_TIME));
+                formatOffsetDateTimeWithLocal(OffsetDateTime.now(), user.getSmpLocale()));
 
         try {
             mailService.sendMail(props, mailFrom, alert.getMailTo());
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/mail/MailDataModel.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/mail/MailDataModel.java
index d0ee71f9403e259cf75cf0840b90e2862501e605..02d6ae8ab000e8a6b7702aceafc8fc064678175c 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/mail/MailDataModel.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/mail/MailDataModel.java
@@ -20,11 +20,17 @@ package eu.europa.ec.edelivery.smp.services.mail;
 
 import eu.europa.ec.edelivery.smp.data.model.DBAlert;
 import eu.europa.ec.edelivery.smp.data.ui.enums.AlertTypeEnum;
+import eu.europa.ec.edelivery.smp.utils.DateTimeUtils;
 import org.apache.commons.lang3.StringUtils;
 
+import java.time.OffsetDateTime;
 import java.util.HashMap;
 import java.util.Map;
 
+/**
+ * Mail data model for mail content. The class is used to create mail messages from templates and message
+ * translations. It contains common properties like current date time, SMP instance name, etc.
+ */
 public class MailDataModel {
     private static final String DEFAULT_LANGUAGE = "en";
     public enum CommonProperties {
@@ -33,21 +39,30 @@ public class MailDataModel {
     }
     private final String language;
     private final AlertTypeEnum alertType;
-    Map<String, Object> model = new HashMap<>();
-
+    Map<String, String> model = new HashMap<>();
 
     public MailDataModel(String language, AlertTypeEnum alertType, Map<String, Object> model) {
         this.language = language;
         this.alertType = alertType;
-        this.model.putAll(model);
+        model.forEach((key, value) -> this.model.put(key, valueToString(value)));
     }
 
     public MailDataModel(String language, final DBAlert alert) {
         this.language = language;
         this.alertType = alert.getAlertType();
-        alert.getProperties().forEach((key, prop) ->  this.model.put(key, prop.getValue()));
+        alert.getProperties().forEach((key, prop) ->  this.model.put(key,valueToString(prop.getValue())));
     }
 
+    private String valueToString(Object value) {
+        if (value instanceof OffsetDateTime) {
+            OffsetDateTime odt = (OffsetDateTime) value;
+            return DateTimeUtils.formatOffsetDateTimeWithLocal(odt, getLanguage(), null);
+        }
+        if (value == null) {
+            return "";
+        }
+        return String.valueOf(value);
+    }
     /**
      * Get language of the mail. If not set, default language "en" is used.
      * @return language of the mail or "en" if not set
@@ -60,7 +75,7 @@ public class MailDataModel {
         return alertType;
     }
 
-    public Map<String, Object> getModel() {
+    public Map<String, String> getModel() {
         return model;
     }
 }
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/mail/MailTemplateService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/mail/MailTemplateService.java
index 0abf9a4ac5061cfbf50782e6d31783c8db442abe..9858cc54d002ddeccb861116c9ad10bc8875993f 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/mail/MailTemplateService.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/mail/MailTemplateService.java
@@ -58,7 +58,7 @@ public class MailTemplateService {
     public String getMailHtmlContent(MailDataModel model) {
         InputStream templateIS = MailTemplateService.class.getResourceAsStream(MAIL_TEMPLATE);
         try {
-            Map<String, Object> modelData = new HashMap<>();
+            Map<String, String> modelData = new HashMap<>();
             modelData.put(MAIL_HEADER, getMailHeader(model));
             modelData.put(MAIL_FOOTER, getMailFooter(model));
             modelData.put(MAIL_TITLE, getMailTitle(model));
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/AbstractResourceHandler.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/AbstractResourceHandler.java
index 989b8eeda6fb96cd40620c473f8e08b0a593bf01..e5cf4f68b7995a6326cf922eeb5911aab81134af 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/AbstractResourceHandler.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/AbstractResourceHandler.java
@@ -116,7 +116,7 @@ public class AbstractResourceHandler {
             throw new SMPRuntimeException(ErrorCode.RESOURCE_DOCUMENT_MISSING, resource.getIdentifierValue(), resource.getIdentifierScheme());
         }
         // read and replace properties
-        Map<String, Object> docProp = resourceStorage.getResourceProperties(resource);
+        Map<String, String> docProp = resourceStorage.getResourceProperties(resource);
 
         try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
             StringNamedSubstitutor.resolve(new ByteArrayInputStream(content), docProp, baos, EXPECTED_RESOURCE_CHARSET);
@@ -144,7 +144,7 @@ public class AbstractResourceHandler {
                     resource.getIdentifierValue(), resource.getIdentifierScheme());
         }
 
-        Map<String, Object> docProp = resourceStorage.getSubresourceProperties(resource, subresource);
+        Map<String, String> docProp = resourceStorage.getSubresourceProperties(resource, subresource);
 
         try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
             StringNamedSubstitutor.resolve(new ByteArrayInputStream(content), docProp, baos, EXPECTED_RESOURCE_CHARSET);
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceStorage.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceStorage.java
index c3f65bdb9d0662eab88b90669c5e8732dc35ec82..6d762d33a3661e5cea765f87ecf3e5bf10a29027 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceStorage.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceStorage.java
@@ -102,14 +102,14 @@ public class ResourceStorage {
         return getDocumentContent(document, true);
     }
     @Transactional
-    public Map<String, Object> getResourceProperties(DBResource resource) {
+    public Map<String, String> getResourceProperties(DBResource resource) {
 
         DBDocument document = documentDao.getDocumentForResource(resource).orElseGet(null);
         if (document == null) {
             LOG.debug("Document not found for resource [{}]", resource);
             return Collections.emptyMap();
         }
-        Map<String, Object> documentProperties = getDocumentProperties(document, true);
+        Map<String, String> documentProperties = getDocumentProperties(document, true);
         // then overwrite with document properties
         documentProperties.put(TransientDocumentPropertyType.RESOURCE_IDENTIFIER_VALUE.getPropertyName(), resource.getIdentifierValue());
         if (resource.getIdentifierScheme() != null) {
@@ -119,7 +119,7 @@ public class ResourceStorage {
     }
 
     @Transactional
-    public Map<String, Object> getSubresourceProperties(DBResource resource, DBSubresource subresource) {
+    public Map<String, String> getSubresourceProperties(DBResource resource, DBSubresource subresource) {
 
         DBDocument document = documentDao.getDocumentForSubresource(subresource).orElseGet(null);
         if (document == null) {
@@ -127,7 +127,7 @@ public class ResourceStorage {
             return Collections.emptyMap();
         }
 
-        Map<String, Object> documentProperties = getDocumentProperties(document, true);
+        Map<String, String> documentProperties = getDocumentProperties(document, true);
         // add resource and subresource properties
         documentProperties.put(TransientDocumentPropertyType.RESOURCE_IDENTIFIER_VALUE.getPropertyName(), resource.getIdentifierValue());
         if (resource.getIdentifierScheme() != null) {
@@ -147,12 +147,12 @@ public class ResourceStorage {
      * @param document document
      * @return document properties the key, value map
      */
-    private Map<String, Object> getDocumentProperties(DBDocument document, boolean followReference) {
+    private Map<String, String> getDocumentProperties(DBDocument document, boolean followReference) {
         if (document == null) {
             return Collections.emptyMap();
         }
 
-        Map<String, Object> documentProperties = new HashMap<>();
+        Map<String, String> documentProperties = new HashMap<>();
         DBDocument referenceDocument = document.getReferenceDocument();
         if (followReference && referenceDocument != null) {
             if (Boolean.TRUE.equals(referenceDocument.getSharingEnabled())) {
@@ -164,7 +164,7 @@ public class ResourceStorage {
         //add/overwrite with document properties
         documentProperties.put(DOCUMENT_NAME.getPropertyName(), document.getName());
         documentProperties.put(DOCUMENT_MIMETYPE.getPropertyName(), document.getMimeType());
-        documentProperties.put(DOCUMENT_VERSION.getPropertyName(), document.getCurrentVersion());
+        documentProperties.put(DOCUMENT_VERSION.getPropertyName(), String.valueOf(document.getCurrentVersion()));
         document.getDocumentProperties().forEach(property ->
             documentProperties.put(property.getProperty(), property.getValue()));
         return documentProperties;
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/DateTimeUtils.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/DateTimeUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..d1f61aca719abb9db484388818474a39a56dc67b
--- /dev/null
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/DateTimeUtils.java
@@ -0,0 +1,73 @@
+/*-
+ * #START_LICENSE#
+ * smp-server-library
+ * %%
+ * Copyright (C) 2017 - 2024 European Commission | eDelivery | DomiSMP
+ * %%
+ * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European Commission - subsequent
+ * versions of the EUPL (the "Licence");
+ * You may not use this work except in compliance with the Licence.
+ * You may obtain a copy of the Licence at:
+ *
+ * [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the Licence is
+ * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licence for the specific language governing permissions and limitations under the Licence.
+ * #END_LICENSE#
+ */
+package eu.europa.ec.edelivery.smp.utils;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
+import java.util.Locale;
+
+/**
+ * Utility class for date operations.
+ *
+ * @author Joze RIHTARSIC
+ * @since 5.1
+ */
+public class DateTimeUtils {
+
+
+    private DateTimeUtils() {
+        // Utility class
+    }
+
+    /**
+     * Format OffsetDateTime with locale for given ZoneId. IF ZoneId is null, system default time zone is used.
+     *
+     * @param offsetDateTime OffsetDateTime to format
+     * @param code           Locale code (e.g. "en", "de", "it", "fr", "sl"). If null/empty, "en" is used
+     * @param zoneId         ZoneId. If null, system default time zone is used
+     * @return formatted OffsetDateTime string representation
+     */
+    public static String formatOffsetDateTimeWithLocal(OffsetDateTime offsetDateTime, String code, ZoneId zoneId) {
+
+        if (offsetDateTime == null) {
+            return null;
+        }
+        //
+        Locale locale = new Locale(StringUtils.isBlank(code) ? "en" : code);
+        // Convert to ZonedDateTime using system default time zone
+        ZonedDateTime zonedDateTime = offsetDateTime.atZoneSameInstant(zoneId == null ? ZoneId.systemDefault() : zoneId);
+        // DateTimeFormatter with locale
+        DateTimeFormatter formatter = DateTimeFormatter.
+                ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.LONG)
+                .withLocale(locale);
+
+        // Format OffsetDateTime
+        return zonedDateTime.format(formatter);
+    }
+
+    public static String formatOffsetDateTimeWithLocal(OffsetDateTime offsetDateTime, String code) {
+        return formatOffsetDateTimeWithLocal(offsetDateTime, code, ZoneId.systemDefault());
+    }
+
+}
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/HttpUtils.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/HttpUtils.java
index 07c2ba04ec3e7b1c0e122d816fe9127668cbef11..522f6d93d51e31a64944cbc31517f94965bd9ebe 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/HttpUtils.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/HttpUtils.java
@@ -23,6 +23,8 @@ import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory;
 import org.apache.commons.lang3.StringUtils;
 
 import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.security.AccessControlException;
 import java.util.Arrays;
 
 public class HttpUtils {
@@ -62,7 +64,7 @@ public class HttpUtils {
         String serverAddress;
         try {
             serverAddress = InetAddress.getLocalHost().getHostName();
-        } catch (Exception e) {
+        } catch (AccessControlException | UnknownHostException e) {
             LOG.error(e.getMessage(), e);
             serverAddress = StringUtils.EMPTY;
         }
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/StringNamedSubstitutor.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/StringNamedSubstitutor.java
index 1c87fb419787a10c83717fc54213665d28e0487c..c28ca5d2dc8c517b2a6dfbbb4944ad747a116114 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/StringNamedSubstitutor.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/StringNamedSubstitutor.java
@@ -53,7 +53,7 @@ public class StringNamedSubstitutor {
      * @param config the config to use
      * @return the resolved string
      */
-    public static String resolve(String string, Map<String, Object> config) {
+    public static String resolve(String string, Map<String, String> config) {
         String charset = Charset.defaultCharset().name();
         LOG.debug("Using default charset: [{}]", charset);
         return resolve(string, config, charset);
@@ -68,7 +68,7 @@ public class StringNamedSubstitutor {
      * @param charset the character of the input stream
      * @return the resolved string
      */
-    public static String resolve(String string, Map<String, Object> config, String charset) {
+    public static String resolve(String string, Map<String, String> config, String charset) {
         try {
             return resolve(new ByteArrayInputStream(string.getBytes()), config, charset);
         } catch (IOException e) {
@@ -86,7 +86,7 @@ public class StringNamedSubstitutor {
      * @return the resolved string
      * @throws IOException if an I/O error occurs
      */
-    public static String resolve(InputStream templateIS, Map<String, Object> config) throws IOException {
+    public static String resolve(InputStream templateIS, Map<String, String> config) throws IOException {
         String charset = Charset.defaultCharset().name();
         LOG.debug("Using default charset: [{}]", charset);
         return resolve(templateIS, config, charset);
@@ -101,7 +101,7 @@ public class StringNamedSubstitutor {
      * @param charset    the character of the input stream
      * @return the resolved string
      */
-    public static String resolve(InputStream templateIS, Map<String, Object> config, String charset) throws IOException {
+    public static String resolve(InputStream templateIS, Map<String, String> config, String charset) throws IOException {
 
         try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
             resolve(templateIS, config, byteArrayOutputStream, charset);
@@ -119,9 +119,9 @@ public class StringNamedSubstitutor {
      * @param charset      the charset to use
      * @throws IOException if an I/O error occurs
      */
-    public static void resolve(InputStream templateIS, Map<String, Object> config,
+    public static void resolve(InputStream templateIS, Map<String, String> config,
                                OutputStream outputStream, String charset) throws IOException {
-        Map<String, Object> lowerCaseMap = normalizeData(config);
+        Map<String, String> lowerCaseMap = normalizeData(config);
         try (BufferedReader template = new BufferedReader(new InputStreamReader(templateIS, charset));
              Writer writer = new OutputStreamWriter(outputStream, charset)) {
             int read;
@@ -137,11 +137,9 @@ public class StringNamedSubstitutor {
                     writer.write(START_NAME);
                 } else {
                     String key = lowerCase(name);
-                    Object objValue = lowerCaseMap.get(key);
-                    String value = objValue != null ? String.valueOf(lowerCaseMap.get(key)) : null;
-
-                    if (value != null) {
-                        writer.write(value);
+                    String objValue = lowerCaseMap.get(key);
+                    if (objValue != null) {
+                        writer.write(objValue);
                     } else {
                         writer.write(START_NAME);
                         writer.write(name);
@@ -158,8 +156,8 @@ public class StringNamedSubstitutor {
      * @param dataModel the data model
      * @return the normalized data model
      */
-    private static Map<String, Object> normalizeData(Map<String, Object> dataModel) {
-        Map<String, Object> lowerCaseMap = new HashMap<>();
+    private static Map<String, String> normalizeData(Map<String, String> dataModel) {
+        Map<String, String> lowerCaseMap = new HashMap<>();
         // Note: do not use stream with Collectors.toMap because it throws NPE if value is null
         dataModel.forEach((key, value) ->
                 lowerCaseMap.put(lowerCase(trim(key)), value));
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIDocumentServiceTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIDocumentServiceTest.java
index c078cbb5d9327a9270527c8f42092ef04ca3381b..d3b7083a9bf9d09814614a1e6dbf6e7ba568d149 100644
--- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIDocumentServiceTest.java
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIDocumentServiceTest.java
@@ -50,7 +50,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 
 @ContextConfiguration(classes = {UIDocumentService.class, ConversionTestConfig.class, ResourceHandlerService.class,
         OasisSMPResource10.class, OasisSMPResource10Handler.class, OasisSMPSubresource10.class, OasisSMPSubresource10Handler.class, Subresource10Validator.class,})
-public class UIDocumentServiceTest extends AbstractServiceIntegrationTest {
+class UIDocumentServiceTest extends AbstractServiceIntegrationTest {
 
     @Autowired
     protected UIDocumentService testInstance;
@@ -181,7 +181,7 @@ public class UIDocumentServiceTest extends AbstractServiceIntegrationTest {
         //when
         DocumentRO result = testInstance.saveSubresourceDocumentForResource(subresource.getId(), subresource.getResource().getId(), testDoc);
 
-        Map<String, Object> mapProperties = result.getProperties().stream().collect(Collectors.toMap(DocumentPropertyRO::getProperty, DocumentPropertyRO::getValue));
+        Map<String, String> mapProperties = result.getProperties().stream().collect(Collectors.toMap(DocumentPropertyRO::getProperty, DocumentPropertyRO::getValue));
         String resolved = StringNamedSubstitutor.resolve(result.getPayload(), mapProperties);
         // then
         Assertions.assertThat(resolved).doesNotContain(TransientDocumentPropertyType.RESOURCE_IDENTIFIER_SCHEME.getPropertyPlaceholder());
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/DateUtilsTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/DateUtilsTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..211acecbe0f9371a4de7e65203d1e5f54db49e47
--- /dev/null
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/DateUtilsTest.java
@@ -0,0 +1,28 @@
+package eu.europa.ec.edelivery.smp.utils;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class DateUtilsTest {
+
+    @ParameterizedTest
+    @CsvSource({
+            "2021-06-01T12:00:00Z, US,'2021-06-01 12:00:00 UTC'",
+            "2021-06-01T12:00:00Z, DE, '01.06.21, 12:00:00 UTC'",
+            "2021-06-01T12:00:00Z, IT, '01/06/21, 12:00:00 UTC'",
+            "2007-12-03T10:15:30+01:00, FR, '03/12/2007 09:15:30 UTC'",
+            "2021-06-01T12:00:00+02, SL, '1. 06. 21 10:00:00 UTC'",
+    })
+    void testFormatOffsetDateTimeAsLocalFormat(String offsetDateTimeStr, String locale, String expected) {
+        // given
+        OffsetDateTime offsetDateTime = OffsetDateTime.parse(offsetDateTimeStr);
+        ZoneId zoneId = ZoneId.of("UTC");
+        // Act and Assert
+        assertEquals(expected, DateTimeUtils.formatOffsetDateTimeWithLocal(offsetDateTime, locale, zoneId));
+    }
+}
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/StringNamedSubstitutorTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/StringNamedSubstitutorTest.java
index 956fb612eb8447c6fbddf4e8e84b3b3937295456..2267db16b73a3bda01fc5dc67d67800be232ed5e 100644
--- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/StringNamedSubstitutorTest.java
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/StringNamedSubstitutorTest.java
@@ -59,7 +59,7 @@ class StringNamedSubstitutorTest {
                     "'The quick red fox jumps over the ${DOG_MODE} dog'",
     })
     void resolve(String testString, String values, String expected) {
-        Map<String, Object> mapVal = Stream.of(values.split("\\s*;\\s*"))
+        Map<String, String> mapVal = Stream.of(values.split("\\s*;\\s*"))
                 .map(s -> s.split("\\s*=\\s*"))
                 .collect(HashMap::new, (m, v) -> m.put(v[0], v[1]), Map::putAll);
 
@@ -69,7 +69,7 @@ class StringNamedSubstitutorTest {
 
     @Test
     void testTransientResolutionForResourceWithUTF8() throws UnsupportedEncodingException {
-        Map<String, Object> mapProperties = new HashMap<>();
+        Map<String, String> mapProperties = new HashMap<>();
         mapProperties.put(TransientDocumentPropertyType.RESOURCE_IDENTIFIER_VALUE.getPropertyName(), "value");
         mapProperties.put(TransientDocumentPropertyType.RESOURCE_IDENTIFIER_SCHEME.getPropertyName(), "scheme");
         String serviceGroupWithUt8 = URLDecoder.decode(SERVICE_GROUP_WITH_UTF8, "UTF-8");
@@ -87,7 +87,7 @@ class StringNamedSubstitutorTest {
 
     @Test
     void testTransientResolutionWithUTF8String() throws UnsupportedEncodingException {
-        Map<String, Object> mapProperties = new HashMap<>();
+        Map<String, String> mapProperties = new HashMap<>();
         mapProperties.put(TransientDocumentPropertyType.RESOURCE_IDENTIFIER_VALUE.getPropertyName(), "value");
         mapProperties.put(TransientDocumentPropertyType.RESOURCE_IDENTIFIER_SCHEME.getPropertyName(), "scheme");
         String serviceGroupWithUt8 = URLDecoder.decode(TEST_UTF8_STRING, "UTF-8");
diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCasUserService.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCasUserService.java
index 5b22b2f9f0cd53ba74410237b17c9e224c397b38..f8e04881a4153e3a7aa11facf5916a6641c04b35 100644
--- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCasUserService.java
+++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCasUserService.java
@@ -8,9 +8,9 @@
  * versions of the EUPL (the "Licence");
  * You may not use this work except in compliance with the Licence.
  * You may obtain a copy of the Licence at:
- * 
+ *
  * [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software distributed under the Licence is
  * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the Licence for the specific language governing permissions and limitations under the Licence.
@@ -39,6 +39,7 @@ import org.springframework.stereotype.Component;
 
 import java.util.Collections;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 
 /**
@@ -50,82 +51,91 @@ import java.util.Map;
  */
 @Component
 public class SMPCasUserService implements AuthenticationUserDetailsService<CasAssertionAuthenticationToken> {
-	enum MappingData {
-		EMAIL("${email}"),
-		FULL_NAME("${firstName} ${lastName}");
-
-		private final String defaultValue;
-
-		MappingData(String defaultValue) {
-			this.defaultValue = defaultValue;
-		}
-
-		public String getDefaultValue() {
-			return defaultValue;
-		}
-	}
-
-	private static final Logger LOG = LoggerFactory.getLogger(SMPCasUserService.class);
-
-	private final UIUserService uiUserService;
-	private final ConfigurationService configurationService;
-
-	@Autowired
-	public SMPCasUserService(UIUserService uiUserService,
-							 ConfigurationService configurationService) {
-		this.uiUserService = uiUserService;
-		this.configurationService = configurationService;
-	}
-
-	/**
-	 * @param token The pre-authenticated authentication token from the cas SMPCas20ServiceTicketValidator
-	 * @return UserDetails for the given authentication token, never null.
-	 * @throws UsernameNotFoundException if no user details can be found for the given authentication token
-	 */
-	@Override
-	public UserDetails loadUserDetails(CasAssertionAuthenticationToken token) throws UsernameNotFoundException {
-		
-		AttributePrincipal principal = token.getAssertion().getPrincipal();
-		// the cas id must match with username
-
-		String username = principal.getName();
-		LOG.info("Got CAS user with principal name: [{}]", username);
+    enum MappingData {
+        EMAIL("${email}"),
+        FULL_NAME("${firstName} ${lastName}");
+
+        private final String defaultValue;
+
+        MappingData(String defaultValue) {
+            this.defaultValue = defaultValue;
+        }
+
+        public String getDefaultValue() {
+            return defaultValue;
+        }
+    }
+
+    private static final Logger LOG = LoggerFactory.getLogger(SMPCasUserService.class);
+
+    private final UIUserService uiUserService;
+    private final ConfigurationService configurationService;
+
+    @Autowired
+    public SMPCasUserService(UIUserService uiUserService,
+                             ConfigurationService configurationService) {
+        this.uiUserService = uiUserService;
+        this.configurationService = configurationService;
+    }
+
+    /**
+     * @param token The pre-authenticated authentication token from the cas SMPCas20ServiceTicketValidator
+     * @return UserDetails for the given authentication token, never null.
+     * @throws UsernameNotFoundException if no user details can be found for the given authentication token
+     */
+    @Override
+    public UserDetails loadUserDetails(CasAssertionAuthenticationToken token) throws UsernameNotFoundException {
+
+        AttributePrincipal principal = token.getAssertion().getPrincipal();
+        // the cas id must match with username
+
+        String username = principal.getName();
+        LOG.info("Got CAS user with principal name: [{}]", username);
         DBUser dbuser;
-		try {
-			dbuser = uiUserService.findUserByUsername(username);
-		} catch (SMPRuntimeException ex) {
-			if (configurationService.isCasAutomaticRegistrationEnabledForUserAuthentication()) {
-				dbuser = registerNewCasUser(principal);
-			}else {
-				throw new UsernameNotFoundException("User with the username ["+username+"] is not registered in SMP", ex);
-			}
-		}
-
-		SMPAuthority authority = SMPAuthority.getAuthorityByApplicationRole(dbuser.getApplicationRole());
-		// generate secret for the session
-		SMPUserDetails smpUserDetails = new SMPUserDetails(dbuser, SecurityUtils.generatePrivateSymmetricKey(true), Collections.singletonList(authority));
-		smpUserDetails.setCasAuthenticated(true);
-		LOG.info("Return authenticated user details for username: [{}]", username);
-		return smpUserDetails;
-	}
-
-	public DBUser registerNewCasUser(AttributePrincipal principal){
-		Map<String, Object> attributes = principal.getAttributes();
-		Map<String, String> attributesMap = configurationService.getCasAutomaticRegistrationDataMapping();
-
-		DBUser dbUser = new DBUser();
-		// update user data by admin
-		dbUser.setUsername(principal.getName());
-		dbUser.setApplicationRole(ApplicationRoleType.USER);
-		dbUser.setActive(!configurationService.isCasAutomaticRegistrationConfirmation());
-		dbUser.setEmailAddress(getValueFromCasPrincipal(MappingData.EMAIL, attributes, attributesMap));
-		dbUser.setFullName(getValueFromCasPrincipal(MappingData.FULL_NAME, attributes, attributesMap));
-		uiUserService.createDBUser(dbUser);
-		return dbUser;
-	}
-
-	protected String getValueFromCasPrincipal(MappingData data, Map<String, Object> attributes, Map<String,String> mapping){
-		String template = mapping.getOrDefault(data.name(), data.getDefaultValue());
-		return StringNamedSubstitutor.resolve(template, attributes);
-	}
+        try {
+            dbuser = uiUserService.findUserByUsername(username);
+        } catch (SMPRuntimeException ex) {
+            if (configurationService.isCasAutomaticRegistrationEnabledForUserAuthentication()) {
+                dbuser = registerNewCasUser(principal);
+            } else {
+                throw new UsernameNotFoundException("User with the username [" + username + "] is not registered in SMP", ex);
+            }
+        }
+
+        SMPAuthority authority = SMPAuthority.getAuthorityByApplicationRole(dbuser.getApplicationRole());
+        // generate secret for the session
+        SMPUserDetails smpUserDetails = new SMPUserDetails(dbuser, SecurityUtils.generatePrivateSymmetricKey(true), Collections.singletonList(authority));
+        smpUserDetails.setCasAuthenticated(true);
+        LOG.info("Return authenticated user details for username: [{}]", username);
+        return smpUserDetails;
+    }
+
+    public DBUser registerNewCasUser(AttributePrincipal principal) {
+        // convert user attributes to map of string
+        Map<String, Object> princAttr = principal.getAttributes();
+        Map<String, String> attributes = princAttr == null ? Collections.emptyMap() :
+                princAttr.entrySet()
+                        .stream()
+                        .collect(Collectors.toMap(
+                                Map.Entry::getKey,
+                                entry -> String.valueOf(entry.getValue())
+                        ));
+
+        Map<String, String> attributesMap = configurationService.getCasAutomaticRegistrationDataMapping();
+
+        DBUser dbUser = new DBUser();
+        // update user data by admin
+        dbUser.setUsername(principal.getName());
+        dbUser.setApplicationRole(ApplicationRoleType.USER);
+        dbUser.setActive(!configurationService.isCasAutomaticRegistrationConfirmation());
+        dbUser.setEmailAddress(getValueFromCasPrincipal(MappingData.EMAIL, attributes, attributesMap));
+        dbUser.setFullName(getValueFromCasPrincipal(MappingData.FULL_NAME, attributes, attributesMap));
+        uiUserService.createDBUser(dbUser);
+        return dbUser;
+    }
+
+    protected String getValueFromCasPrincipal(MappingData data, Map<String, String> attributes, Map<String, String> mapping) {
+        String template = mapping.getOrDefault(data.name(), data.getDefaultValue());
+        return StringNamedSubstitutor.resolve(template, attributes);
+    }
 }