diff --git a/docs/simplesamlphp-upgrade-notes-2.0.md b/docs/simplesamlphp-upgrade-notes-2.0.md
index f83e997826e72b4eeffe60f9d466603e20387d0d..1e9be1b1d5cb04153d3bdbecd2b12cdca6f13e0a 100644
--- a/docs/simplesamlphp-upgrade-notes-2.0.md
+++ b/docs/simplesamlphp-upgrade-notes-2.0.md
@@ -96,3 +96,17 @@ processing filters or interface with the SimpleSAMLphp development API.
     - lib/SimpleSAML/Store/Memcache.php has been renamed to lib/SimpleSAML/Store/MemcacheStore.php
     - lib/SimpleSAML/Store/Redis.php has been renamed to lib/SimpleSAML/Store/RedisStore.php
 
+- The following methods have had their signature changed:
+  - Configuration::getValue
+  - Configuration::getBoolean
+  - Configuration::getString
+  - Configuration::getInteger
+  - Configuration::getIntegerRange
+  - Configuration::getValueValidate
+  - Configuration::getArray
+  - Configuration::getArrayize
+  - Configuration::getArrayizeString
+  - Configuration::getConfigItem
+  - Configuration::getLocalizedString
+
+  All of these methods no longer accept a default as their last parameter. Use their getOptional* counterparts instead.
diff --git a/lib/SimpleSAML/Auth/ProcessingChain.php b/lib/SimpleSAML/Auth/ProcessingChain.php
index eeba0d6b8f6a3b3d1889b64a19491b9c73db3ca2..1de3dbb43b5d922171e939d7bdc94a16972c32dd 100644
--- a/lib/SimpleSAML/Auth/ProcessingChain.php
+++ b/lib/SimpleSAML/Auth/ProcessingChain.php
@@ -61,7 +61,7 @@ class ProcessingChain
     public function __construct(array $idpMetadata, array $spMetadata, string $mode = 'idp')
     {
         $config = Configuration::getInstance();
-        $configauthproc = $config->getArray('authproc.' . $mode, null);
+        $configauthproc = $config->getOptionalArray('authproc.' . $mode, null);
 
         if (!empty($configauthproc)) {
             $configfilters = self::parseFilterList($configauthproc);
diff --git a/lib/SimpleSAML/Auth/Simple.php b/lib/SimpleSAML/Auth/Simple.php
index ca806d3759652af6fc2caa80233349612daa9efa..350901fbe87f079ef7c359c2bf586d4095bfd157 100644
--- a/lib/SimpleSAML/Auth/Simple.php
+++ b/lib/SimpleSAML/Auth/Simple.php
@@ -46,8 +46,7 @@ class Simple
             $config = Configuration::getInstance();
         }
         $this->authSource = $authSource;
-        /** @psalm-var \SimpleSAML\Configuration $this->app_config */
-        $this->app_config = $config->getConfigItem('application');
+        $this->app_config = $config->getOptionalConfigItem('application', []);
 
         if ($session === null) {
             $session = Session::getSessionFromRequest();
@@ -385,7 +384,7 @@ class Simple
             $port = '';
         }
 
-        $base = trim($this->app_config->getString(
+        $base = trim($this->app_config->getOptionalString(
             'baseURL',
             $scheme . '://' . $host . $port
         ), '/');
diff --git a/lib/SimpleSAML/Auth/Source.php b/lib/SimpleSAML/Auth/Source.php
index 79fec9e021c594b0b0d8e72f0575c45de7be22ad..1b54babb9f0e2e9be2d20defd36a48d674c99a0c 100644
--- a/lib/SimpleSAML/Auth/Source.php
+++ b/lib/SimpleSAML/Auth/Source.php
@@ -344,7 +344,7 @@ abstract class Source
         // for now - load and parse config file
         $config = Configuration::getConfig('authsources.php');
 
-        $authConfig = $config->getArray($authId, null);
+        $authConfig = $config->getOptionalArray($authId, null);
         if ($authConfig === null) {
             if ($type !== null) {
                 throw new Error\Exception(
diff --git a/lib/SimpleSAML/Auth/State.php b/lib/SimpleSAML/Auth/State.php
index b43f48f1b1b7242adb1e824aeab6e04e84c03642..69544cf8f099bafce2b7d3f8fcb24633a0acef03 100644
--- a/lib/SimpleSAML/Auth/State.php
+++ b/lib/SimpleSAML/Auth/State.php
@@ -181,7 +181,7 @@ class State
     {
         if (self::$stateTimeout === null) {
             $globalConfig = Configuration::getInstance();
-            self::$stateTimeout = $globalConfig->getInteger('session.state.timeout', 60 * 60);
+            self::$stateTimeout = $globalConfig->getOptionalInteger('session.state.timeout', 60 * 60);
         }
 
         return self::$stateTimeout;
diff --git a/lib/SimpleSAML/Configuration.php b/lib/SimpleSAML/Configuration.php
index 2f07bd17e370ff13951a02dce6aaf367d27f3739..54a6d9edbd0002e4735a6c58cda9942476e1eb6f 100644
--- a/lib/SimpleSAML/Configuration.php
+++ b/lib/SimpleSAML/Configuration.php
@@ -6,6 +6,7 @@ namespace SimpleSAML;
 
 use SAML2\Constants;
 use SimpleSAML\Assert\Assert;
+use SimpleSAML\Assert\AssertionFailedException;
 use SimpleSAML\Error;
 use SimpleSAML\Utils;
 
@@ -348,29 +349,41 @@ class Configuration implements Utils\ClearableState
     /**
      * Retrieve a configuration option set in config.php.
      *
-     * @param string $name Name of the configuration option.
-     * @param mixed  $default Default value of the configuration option. This parameter will default to null if not
-     *                        specified. This can be set to \SimpleSAML\Configuration::REQUIRED_OPTION, which will
-     *                        cause an exception to be thrown if the option isn't found.
+     * @param string $name  Name of the configuration option.
+     * @return mixed        The configuration option with name $name.
      *
-     * @return mixed The configuration option with name $name, or $default if the option was not found.
+     * @throws \SimpleSAML\Assert\AssertionFailedException If the required option cannot be retrieved.
+     */
+    public function getValue(string $name)
+    {
+        Assert::true(
+            $this->hasValue($name),
+            sprintf('%s: Could not retrieve the required option %s.', $this->location, var_export($name, true)),
+        );
+
+        return $this->configuration[$name];
+    }
+
+
+    /**
+     * Retrieve an optional configuration option set in config.php.
+     *
+     * @param string $name     Name of the configuration option.
+     * @param mixed  $default  Default value of the configuration option.
+                               This parameter will default to null if not specified.
+     *
+     * @return mixed           The configuration option with name $name, or $default if the option was not found.
      *
-     * @throws \Exception If the required option cannot be retrieved.
+     * @throws \SimpleSAML\Assert\AssertionFailedException If the required option cannot be retrieved.
      */
-    public function getValue(string $name, $default = null)
+    public function getOptionalValue(string $name, $default)
     {
         // return the default value if the option is unset
-        if (!array_key_exists($name, $this->configuration)) {
-            if ($default === self::REQUIRED_OPTION) {
-                throw new \Exception(
-                    $this->location . ': Could not retrieve the required option ' .
-                    var_export($name, true)
-                );
-            }
+        if (!$this->hasValue($name)) {
             return $default;
         }
 
-        return $this->configuration[$name];
+        return $this->configuration[$name] ?? $default;
     }
 
 
@@ -416,7 +429,7 @@ class Configuration implements Utils\ClearableState
      */
     public function getBasePath(): string
     {
-        $baseURL = $this->getString('baseurlpath', 'simplesaml/');
+        $baseURL = $this->getOptionalString('baseurlpath', 'simplesaml/');
 
         if (preg_match('#^https?://[^/]*(?:/(.+/?)?)?$#', $baseURL, $matches)) {
             // we have a full url, we need to strip the path
@@ -441,7 +454,7 @@ class Configuration implements Utils\ClearableState
             $c['baseurlpath'] = $httpUtils->guessBasePath();
             throw new Error\CriticalConfigurationError(
                 'Incorrect format for option \'baseurlpath\'. Value is: "' .
-                $this->getString('baseurlpath', 'simplesaml/') . '". Valid format is in the form' .
+                $this->getOptionalString('baseurlpath', 'simplesaml/') . '". Valid format is in the form' .
                 ' [(http|https)://(hostname|fqdn)[:port]]/[path/to/simplesaml/].',
                 $this->filename,
                 $c
@@ -515,7 +528,7 @@ class Configuration implements Utils\ClearableState
     public function getBaseDir(): string
     {
         // check if a directory is configured in the configuration file
-        $dir = $this->getString('basedir', null);
+        $dir = $this->getOptionalString('basedir', null);
         if ($dir !== null) {
             // add trailing slash if it is missing
             if (substr($dir, -1) !== DIRECTORY_SEPARATOR) {
@@ -547,34 +560,52 @@ class Configuration implements Utils\ClearableState
     /**
      * This function retrieves a boolean configuration option.
      *
-     * An exception will be thrown if this option isn't a boolean, or if this option isn't found, and no default value
-     * is given.
+     * An exception will be thrown if this option isn't a boolean, or if this option isn't found.
      *
-     * @param string $name The name of the option.
-     * @param mixed  $default A default value which will be returned if the option isn't found. The option will be
-     *                  required if this parameter isn't given. The default value can be any value, including
-     *                  null.
+     * @param string   $name The name of the option.
+     * @return boolean       The option with the given name.
      *
-     * @return boolean|mixed The option with the given name, or $default if the option isn't found and $default is
-     *     specified.
-     *
-     * @throws \Exception If the option is not boolean.
+     * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not boolean.
      */
-    public function getBoolean(string $name, $default = self::REQUIRED_OPTION)
+    public function getBoolean(string $name): bool
     {
-        $ret = $this->getValue($name, $default);
+        $ret = $this->getValue($name);
 
-        if ($ret === $default) {
-            // the option wasn't found, or it matches the default value. In any case, return this value
-            return $ret;
-        }
+        Assert::boolean(
+            $ret,
+            sprintf('%s: The option %s is not a valid boolean value.', $this->location, var_export($name, true)),
+        );
+
+        return $ret;
+    }
 
-        if (!is_bool($ret)) {
-            throw new \Exception(
-                $this->location . ': The option ' . var_export($name, true) .
-                ' is not a valid boolean value.'
-            );
-        }
+
+    /**
+     * This function retrieves a boolean configuration option.
+     *
+     * An exception will be thrown if this option isn't a boolean.
+     *
+     * @param string    $name     The name of the option.
+     * @param bool|null $default  A default value which will be returned if the option isn't found.
+     *                            The default value can be null or a boolean.
+     *
+     * @return bool|null          The option with the given name, or $default.
+     * @psalm-return              ($default is null ? null : bool)
+     *
+     * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not boolean.
+     */
+    public function getOptionalBoolean(string $name, ?bool $default): ?bool
+    {
+        $ret = $this->getOptionalValue($name, $default);
+
+        Assert::nullOrBoolean(
+            $ret,
+            sprintf(
+                '%s: The option %s is not a valid boolean value or null.',
+                $this->location,
+                var_export($name, true)
+            ),
+        );
 
         return $ret;
     }
@@ -583,34 +614,52 @@ class Configuration implements Utils\ClearableState
     /**
      * This function retrieves a string configuration option.
      *
-     * An exception will be thrown if this option isn't a string, or if this option isn't found, and no default value
-     * is given.
+     * An exception will be thrown if this option isn't a string, or if this option isn't found.
      *
-     * @param string $name The name of the option.
-     * @param mixed  $default A default value which will be returned if the option isn't found. The option will be
-     *                  required if this parameter isn't given. The default value can be any value, including
-     *                  null.
+     * @param string $name  The name of the option.
+     * @return string       The option with the given name.
      *
-     * @return string|mixed The option with the given name, or $default if the option isn't found and $default is
-     *     specified.
-     *
-     * @throws \Exception If the option is not a string.
+     * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not a string.
      */
-    public function getString(string $name, $default = self::REQUIRED_OPTION)
+    public function getString(string $name): string
     {
-        $ret = $this->getValue($name, $default);
+        $ret = $this->getValue($name);
 
-        if ($ret === $default) {
-            // the option wasn't found, or it matches the default value. In any case, return this value
-            return $ret;
-        }
+        Assert::string(
+            $ret,
+            sprintf('%s: The option %s is not a valid string value.', $this->location, var_export($name, true)),
+        );
+
+        return $ret;
+    }
 
-        if (!is_string($ret)) {
-            throw new \Exception(
-                $this->location . ': The option ' . var_export($name, true) .
-                ' is not a valid string value.'
-            );
-        }
+
+    /**
+     * This function retrieves an optional string configuration option.
+     *
+     * An exception will be thrown if this option isn't a string.
+     *
+     * @param string       $name     The name of the option.
+     * @param string|null  $default  A default value which will be returned if the option isn't found.
+     *                               The default value can be null or a string.
+     *
+     * @return string|null The option with the given name, or $default if the option isn't found.
+     * @psalm-return       ($default is null ? null : string)
+     *
+     * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not a string.
+     */
+    public function getOptionalString(string $name, ?string $default): ?string
+    {
+        $ret = $this->getOptionalValue($name, $default);
+
+        Assert::nullOrString(
+            $ret,
+            sprintf(
+                '%s: The option %s is not a valid string value or null.',
+                $this->location,
+                var_export($name, true)
+            ),
+        );
 
         return $ret;
     }
@@ -619,34 +668,52 @@ class Configuration implements Utils\ClearableState
     /**
      * This function retrieves an integer configuration option.
      *
-     * An exception will be thrown if this option isn't an integer, or if this option isn't found, and no default value
-     * is given.
-     *
-     * @param string $name The name of the option.
-     * @param mixed  $default A default value which will be returned if the option isn't found. The option will be
-     *                  required if this parameter isn't given. The default value can be any value, including
-     *                  null.
+     * An exception will be thrown if this option isn't an integer, or if this option isn't found.
      *
-     * @return int|mixed The option with the given name, or $default if the option isn't found and $default is
-     * specified.
+     * @param string $name  The name of the option.
+     * @return int          The option with the given name.
      *
-     * @throws \Exception If the option is not an integer.
+     * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an integer.
      */
-    public function getInteger(string $name, $default = self::REQUIRED_OPTION)
+    public function getInteger(string $name): int
     {
-        $ret = $this->getValue($name, $default);
+        $ret = $this->getValue($name);
 
-        if ($ret === $default) {
-            // the option wasn't found, or it matches the default value. In any case, return this value
-            return $ret;
-        }
+        Assert::integer(
+            $ret,
+            sprintf('%s: The option %s is not a valid integer value.', $this->location, var_export($name, true)),
+        );
+
+        return $ret;
+    }
 
-        if (!is_int($ret)) {
-            throw new \Exception(
-                $this->location . ': The option ' . var_export($name, true) .
-                ' is not a valid integer value.'
-            );
-        }
+
+    /**
+     * This function retrieves an optional integer configuration option.
+     *
+     * An exception will be thrown if this option isn't an integer.
+     *
+     * @param string $name     The name of the option.
+     * @param mixed  $default  A default value which will be returned if the option isn't found.
+     *                         The default value can be null or an integer.
+     *
+     * @return int|null The option with the given name, or $default if the option isn't found.
+     * @psalm-return           ($default is null ? null : int)
+     *
+     * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an integer.
+     */
+    public function getOptionalInteger(string $name, ?int $default): ?int
+    {
+        $ret = $this->getOptionalValue($name, $default);
+
+        Assert::nullOrInteger(
+            $ret,
+            sprintf(
+                '%s: The option %s is not a valid integer value or null.',
+                $this->location,
+                var_export($name, true)
+            ),
+        );
 
         return $ret;
     }
@@ -663,31 +730,63 @@ class Configuration implements Utils\ClearableState
      * @param string $name The name of the option.
      * @param int    $minimum The smallest value which is allowed.
      * @param int    $maximum The largest value which is allowed.
-     * @param mixed  $default A default value which will be returned if the option isn't found. The option will be
-     *                  required if this parameter isn't given. The default value can be any value, including
-     *                  null.
      *
-     * @return int|mixed The option with the given name, or $default if the option isn't found and $default is
-     *     specified.
+     * @return int The option with the given name.
      *
-     * @throws \Exception If the option is not in the range specified.
+     * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not in the range specified.
      */
-    public function getIntegerRange(string $name, int $minimum, int $maximum, $default = self::REQUIRED_OPTION)
+    public function getIntegerRange(string $name, int $minimum, int $maximum): int
     {
-        $ret = $this->getInteger($name, $default);
+        $ret = $this->getInteger($name);
+
+        Assert::range(
+            $ret,
+            $minimum,
+            $maximum,
+            sprintf(
+                '%s: Value of option %s is out of range. Value is %%s, allowed range is [%%2$s - %%3$s]',
+                $this->location,
+                var_export($name, true),
+            ),
+        );
 
-        if ($ret === $default) {
-            // the option wasn't found, or it matches the default value. In any case, return this value
-            return $ret;
-        }
+        return $ret;
+    }
 
-        if ($ret < $minimum || $ret > $maximum) {
-            throw new \Exception(
-                $this->location . ': Value of option ' . var_export($name, true) .
-                ' is out of range. Value is ' . $ret . ', allowed range is ['
-                . $minimum . ' - ' . $maximum . ']'
-            );
-        }
+
+    /**
+     * This function retrieves an optional integer configuration option where the value must be in the specified range.
+     *
+     * An exception will be thrown if:
+     * - the option isn't an integer
+     * - the value is outside of the allowed range
+     *
+     * @param string    $name    The name of the option.
+     * @param int       $minimum The smallest value which is allowed.
+     * @param int       $maximum The largest value which is allowed.
+     * @param int|null  $default A default value which will be returned if the option isn't found.
+     *                             The default value can be null or an integer.
+     *
+     * @return int|null The option with the given name, or $default if the option isn't found and $default is
+     *     specified.
+     * @psalm-return    ($default is null ? null : int)
+     *
+     * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not in the range specified.
+     */
+    public function getOptionalIntegerRange(string $name, int $minimum, int $maximum, ?int $default): ?int
+    {
+        $ret = $this->getOptionalInteger($name, $default);
+
+        Assert::nullOrRange(
+            $ret,
+            $minimum,
+            $maximum,
+            sprintf(
+                '%s: Value of option %s is out of range. Value is %%s, allowed range is [%%2$s - %%3$s] or null.',
+                $this->location,
+                var_export($name, true),
+            ),
+        );
 
         return $ret;
     }
@@ -699,42 +798,63 @@ class Configuration implements Utils\ClearableState
      * This will check that the configuration option matches one of the given values. The match will use
      * strict comparison. An exception will be thrown if it does not match.
      *
-     * The option can be mandatory or optional. If no default value is given, it will be considered to be
-     * mandatory, and an exception will be thrown if it isn't provided. If a default value is given, it
-     * is considered to be optional, and the default value is returned. The default value is automatically
-     * included in the list of allowed values.
+     * The option is mandatory and an exception will be thrown if it isn't provided.
      *
-     * @param string $name The name of the option.
-     * @param array  $allowedValues The values the option is allowed to take, as an array.
-     * @param mixed  $default The default value which will be returned if the option isn't found. If this parameter
-     *                  isn't given, the option will be considered to be mandatory. The default value can be
-     *                  any value, including null.
+     * @param string $name           The name of the option.
+     * @param array  $allowedValues  The values the option is allowed to take, as an array.
      *
-     * @return mixed The option with the given name, or $default if the option isn't found and $default is given.
+     * @return mixed The option with the given name.
      *
-     * @throws \Exception If the option does not have any of the allowed values.
+     * @throws \SimpleSAML\Assert\AssertionFailedException If the option does not have any of the allowed values.
      */
-    public function getValueValidate(string $name, array $allowedValues, $default = self::REQUIRED_OPTION)
+    public function getValueValidate(string $name, array $allowedValues)
     {
-        $ret = $this->getValue($name, $default);
-        if ($ret === $default) {
-            // the option wasn't found, or it matches the default value. In any case, return this value
-            return $ret;
-        }
+        $ret = $this->getValue($name);
+
+        Assert::oneOf(
+            $ret,
+            $allowedValues,
+            sprintf(
+                '%s: Invalid value given for option %s. It should have one of: %%2$s; but got: %%s.',
+                $this->location,
+                var_export($name, true),
+            ),
+        );
 
-        if (!in_array($ret, $allowedValues, true)) {
-            $strValues = [];
-            foreach ($allowedValues as $av) {
-                $strValues[] = var_export($av, true);
-            }
-            $strValues = implode(', ', $strValues);
+        return $ret;
+    }
 
-            throw new \Exception(
-                $this->location . ': Invalid value given for the option ' .
-                var_export($name, true) . '. It should have one of the following values: ' .
-                $strValues . '; but it had the following value: ' . var_export($ret, true)
-            );
-        }
+
+    /**
+     * Retrieve an optional configuration option with one of the given values.
+     *
+     * This will check that the configuration option matches one of the given values. The match will use
+     * strict comparison. An exception will be thrown if it does not match.
+     *
+     * The option is optional. The default value is automatically included in the list of allowed values.
+     *
+     * @param string $name           The name of the option.
+     * @param array  $allowedValues  The values the option is allowed to take, as an array.
+     * @param mixed  $default        The default value which will be returned if the option isn't found.
+     *                               The default value can be any value, including null.
+     *
+     * @return mixed The option with the given name, or $default if the option isn't found and $default is given.
+     *
+     * @throws \SimpleSAML\Assert\AssertionFailedException If the option does not have any of the allowed values.
+     */
+    public function getOptionalValueValidate(string $name, array $allowedValues, $default)
+    {
+        $ret = $this->getOptionalValue($name, $default);
+
+        Assert::nullOrOneOf(
+            $ret,
+            $allowedValues,
+            sprintf(
+                '%s: Invalid value given for option %s. It should have one of: %%2$s or null; but got: %%s.',
+                $this->location,
+                var_export($name, true),
+            ),
+        );
 
         return $ret;
     }
@@ -743,31 +863,49 @@ class Configuration implements Utils\ClearableState
     /**
      * This function retrieves an array configuration option.
      *
-     * An exception will be thrown if this option isn't an array, or if this option isn't found, and no
-     * default value is given.
+     * An exception will be thrown if this option isn't an array, or if this option isn't found.
      *
      * @param string $name The name of the option.
-     * @param mixed  $default A default value which will be returned if the option isn't found. The option will be
-     *                       required if this parameter isn't given. The default value can be any value, including
-     *                       null.
+     * @return array The option with the given name.
      *
-     * @return array|mixed The option with the given name, or $default if the option isn't found and $default is
+     * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an array.
+     */
+    public function getArray(string $name): array
+    {
+        $ret = $this->getValue($name);
+
+        Assert::isArray(
+            $ret,
+            sprintf('%s: The option %s is not an array.', $this->location, var_export($name, true)),
+        );
+
+        return $ret;
+    }
+
+
+    /**
+     * This function retrieves an optional array configuration option.
+     *
+     * An exception will be thrown if this option isn't an array, or if this option isn't found.
+     *
+     * @param string      $name     The name of the option.
+     * @param array|null  $default  A default value which will be returned if the option isn't found.
+     *                                The default value can be null or an array.
+     *
+     * @return array|null The option with the given name, or $default if the option isn't found and $default is
      * specified.
+     * @psalm-return      ($default is null ? null : array)
      *
-     * @throws \Exception If the option is not an array.
+     * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an array.
      */
-    public function getArray(string $name, $default = self::REQUIRED_OPTION)
+    public function getOptionalArray(string $name, ?array $default): ?array
     {
-        $ret = $this->getValue($name, $default);
-
-        if ($ret === $default) {
-            // the option wasn't found, or it matches the default value. In any case, return this value
-            return $ret;
-        }
+        $ret = $this->getOptionalValue($name, $default);
 
-        if (!is_array($ret)) {
-            throw new \Exception($this->location . ': The option ' . var_export($name, true) . ' is not an array.');
-        }
+        Assert::nullOrIsArray(
+            $ret,
+            sprintf('%s: The option %s is not an array or null.', $this->location, var_export($name, true)),
+        );
 
         return $ret;
     }
@@ -779,21 +917,37 @@ class Configuration implements Utils\ClearableState
      * If the configuration option isn't an array, it will be converted to an array.
      *
      * @param string $name The name of the option.
-     * @param mixed  $default A default value which will be returned if the option isn't found. The option will be
-     *                       required if this parameter isn't given. The default value can be any value, including
-     *                       null.
      *
-     * @return mixed The option with the given name, or $default if the option isn't found and $default is specified.
+     * @return array The option with the given name.
      */
-    public function getArrayize(string $name, $default = self::REQUIRED_OPTION)
+    public function getArrayize(string $name): array
     {
-        $ret = $this->getValue($name, $default);
+        $ret = $this->getValue($name);
 
-        if ($ret === $default) {
-            // the option wasn't found, or it matches the default value. In any case, return this value
-            return $ret;
+        if (!is_array($ret)) {
+            $ret = [$ret];
         }
 
+        return $ret;
+    }
+
+
+    /**
+     * This function retrieves an optional array configuration option.
+     *
+     * If the configuration option isn't an array, it will be converted to an array.
+     *
+     * @param string      $name The name of the option.
+     * @param array|null  $default A default value which will be returned if the option isn't found.
+     *                       The default value can be null or an array.
+     *
+     * @return array|null The option with the given name.
+     * @psalm-return      ($default is null ? null : array)
+     */
+    public function getOptionalArrayize(string $name, $default): ?array
+    {
+        $ret = $this->getOptionalValue($name, $default);
+
         if (!is_array($ret)) {
             $ret = [$ret];
         }
@@ -808,31 +962,53 @@ class Configuration implements Utils\ClearableState
      * If the configuration option is a string, it will be converted to an array with a single string
      *
      * @param string $name The name of the option.
-     * @param mixed  $default A default value which will be returned if the option isn't found. The option will be
-     *                       required if this parameter isn't given. The default value can be any value, including
-     *                       null.
-     *
-     * @return mixed The option with the given name, or $default if the option isn't found and $default is specified.
+     * @return string[] The option with the given name.
      *
-     * @throws \Exception If the option is not a string or an array of strings.
+     * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not a string or an array of strings.
      */
-    public function getArrayizeString(string $name, $default = self::REQUIRED_OPTION)
+    public function getArrayizeString(string $name): array
     {
-        $ret = $this->getArrayize($name, $default);
+        $ret = $this->getArrayize($name);
+
+        Assert::allString(
+            $ret,
+            sprintf(
+                '%s: The option %s must be a string or an array of strings.',
+                $this->location,
+                var_export($name, true),
+            ),
+        );
 
-        if ($ret === $default) {
-            // the option wasn't found, or it matches the default value. In any case, return this value
-            return $ret;
-        }
+        return $ret;
+    }
 
-        foreach ($ret as $value) {
-            if (!is_string($value)) {
-                throw new \Exception(
-                    $this->location . ': The option ' . var_export($name, true) .
-                    ' must be a string or an array of strings.'
-                );
-            }
-        }
+
+    /**
+     * This function retrieves an optional configuration option with a string or an array of strings.
+     *
+     * If the configuration option is a string, it will be converted to an array with a single string
+     *
+     * @param string         $name The name of the option.
+     * @param string[]|null  $default A default value which will be returned if the option isn't found.
+     *                         The default value can be null or an array of strings.
+     *
+     * @return string[]|null The option with the given name, or $default if the option isn't found and $default is specified.
+     * @psalm-return         ($default is null ? null : array)
+     *
+     * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not a string or an array of strings.
+     */
+    public function getOptionalArrayizeString(string $name, ?array $default): ?array
+    {
+        $ret = $this->getOptionalArrayize($name, $default);
+
+        Assert::nullOrAllString(
+            $ret,
+            sprintf(
+                '%s: The option %s must be null, a string or an array of strings.',
+                $this->location,
+                var_export($name, true),
+            ),
+        );
 
         return $ret;
     }
@@ -841,41 +1017,49 @@ class Configuration implements Utils\ClearableState
     /**
      * Retrieve an array as a \SimpleSAML\Configuration object.
      *
-     * This function will load the value of an option into a \SimpleSAML\Configuration object. The option must contain
-     * an array.
+     * This function will load the value of an option into a \SimpleSAML\Configuration object.
+     *   The option must contain an array.
      *
-     * An exception will be thrown if this option isn't an array, or if this option isn't found, and no default value
-     * is given.
+     * An exception will be thrown if this option isn't an array, or if this option isn't found.
      *
      * @param string $name The name of the option.
+     * @return \SimpleSAML\Configuration The option with the given name,
+     *
+     * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an array.
+     */
+    public function getConfigItem(string $name): Configuration
+    {
+        $ret = $this->getArray($name);
+
+        return self::loadFromArray($ret, $this->location . '[' . var_export($name, true) . ']');
+    }
+
+
+
+    /**
+     * Retrieve an optional array as a \SimpleSAML\Configuration object.
+     *
+     * This function will load the optional value of an option into a \SimpleSAML\Configuration object.
+     *   The option must contain an array.
+     *
+     * An exception will be thrown if this option isn't an array, or if this option isn't found.
+     *
+     * @param string     $name The name of the option.
      * @param array|null $default A default value which will be used if the option isn't found. An empty Configuration
-     *                        object will be returned if this parameter isn't given and the option doesn't exist.
-     *                        This function will only return null if $default is set to null and the option
-     *                        doesn't exist.
+     *                     object will be returned if this parameter isn't given and the option doesn't exist.
+     *                     This function will only return null if $default is set to null and the option doesn't exist.
      *
      * @return \SimpleSAML\Configuration|null The option with the given name,
-     *   or $default if the option isn't found and $default is specified.
+     *   or $default, converted into a Configuration object.
+     * @psalm-return     ($default is null ? null : \SimpleSAML\Configuration)
      *
-     * @throws \Exception If the option is not an array.
+     * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an array.
      */
-    public function getConfigItem(string $name, $default = []): ?Configuration
+    public function getOptionalConfigItem(string $name, ?array $default): ?Configuration
     {
-        $ret = $this->getValue($name, $default);
+        $ret = $this->getOptionalArray($name, $default);
 
-        if ($ret === null) {
-            // the option wasn't found, or it is explicitly null
-            // do not instantiate a new Configuration instance, but just return null
-            return null;
-        }
-
-        if (!is_array($ret)) {
-            throw new \Exception(
-                $this->location . ': The option ' . var_export($name, true) .
-                ' is not an array.'
-            );
-        }
-
-        return self::loadFromArray($ret, $this->location . '[' . var_export($name, true) . ']');
+        return ($ret === null) ? null : self::loadFromArray($ret, $this->location . '[' . var_export($name, true) . ']');
     }
 
 
@@ -971,7 +1155,7 @@ class Configuration implements Utils\ClearableState
                     'Location' => $ep,
                     'Binding'  => $this->getDefaultBinding($endpointType),
                 ];
-                $responseLocation = $this->getString($endpointType . 'Response', null);
+                $responseLocation = $this->getOptionalString($endpointType . 'Response', null);
                 if ($responseLocation !== null) {
                     $ep['ResponseLocation'] = $responseLocation;
                 }
@@ -1082,44 +1266,57 @@ class Configuration implements Utils\ClearableState
      * The default language returned is always 'en'.
      *
      * @param string $name The name of the option.
-     * @param mixed  $default The default value. If no default is given, and the option isn't found, an exception will
-     *     be thrown.
+     * @param array  $default The default value.
      *
-     * @return mixed Associative array with language => string pairs, or the provided default value.
+     * @return array Associative array with language => string pairs.
      *
-     * @throws \Exception If the translation is not an array or a string, or its index or value are not strings.
+     * @throws \SimpleSAML\Assert\AssertionFailedException
+     *   If the translation is not an array or a string, or its index or value are not strings.
      */
-    public function getLocalizedString(string $name, $default = self::REQUIRED_OPTION)
+    public function getLocalizedString(string $name): array
     {
-        $ret = $this->getValue($name, $default);
-        if ($ret === $default) {
-            // the option wasn't found, or it matches the default value. In any case, return this value
-            return $ret;
-        }
-
-        $loc = $this->location . '[' . var_export($name, true) . ']';
+        $ret = $this->getValue($name);
 
         if (is_string($ret)) {
             $ret = ['en' => $ret];
         }
 
-        if (!is_array($ret)) {
-            throw new \Exception($loc . ': Must be an array or a string.');
-        }
+        Assert::isArray($ret, sprintf('%s: Must be an array or a string.', $this->location));
 
         foreach ($ret as $k => $v) {
-            if (!is_string($k)) {
-                throw new \Exception($loc . ': Invalid language code: ' . var_export($k, true));
-            }
-            if (!is_string($v)) {
-                throw new \Exception($loc . '[' . var_export($v, true) . ']: Must be a string.');
-            }
+            Assert::string($k, sprintf('%s: Invalid language code: %s', $this->location, var_export($k, true)));
+            Assert::string($v, sprintf('%s[%s]: Must be a string.', $this->location, var_export($v, true)));
         }
 
         return $ret;
     }
 
 
+    /**
+     * Retrieve an optional string which may be localized into many languages.
+     *
+     * The default language returned is always 'en'.
+     *
+     * @param string $name The name of the option.
+     * @param mixed  $default The default value.
+     *
+     * @return array|null Associative array with language => string pairs, or the provided default value.
+     * @psalm-return ($default is null ? null : array)
+     *
+     * @throws \SimpleSAML\Assert\AssertionFailedException
+     *   If the translation is not an array or a string, or its index or value are not strings.
+     */
+    public function getOptionalLocalizedString(string $name, ?array $default): ?array
+    {
+        if (!$this->hasValue($name)) {
+            // the option wasn't found, or it matches the default value. In any case, return this value
+            return $default;
+        }
+
+        return $this->getLocalizedString($name);
+    }
+
+
     /**
      * Get public key from metadata.
      *
@@ -1153,7 +1350,7 @@ class Configuration implements Utils\ClearableState
         } elseif ($this->hasValue($prefix . 'certData')) {
             $certData = $this->getString($prefix . 'certData');
             $certData = preg_replace('/\s+/', '', $certData);
-            $keyName = $this->getString($prefix . 'key_name', null);
+            $keyName = $this->getOptionalString($prefix . 'key_name', null);
             return [
                 [
                     'name'            => $keyName,
@@ -1184,7 +1381,7 @@ class Configuration implements Utils\ClearableState
                 );
             }
             $certData = preg_replace('/\s+/', '', $matches[1]);
-            $keyName = $this->getString($prefix . 'key_name', null);
+            $keyName = $this->getOptionalString($prefix . 'key_name', null);
 
             return [
                 [
diff --git a/lib/SimpleSAML/Database.php b/lib/SimpleSAML/Database.php
index 01bd51eea20a2d12033d550b1882c27d04bad2f0..239b88eb69a7074edc45703ce0f770cc6c08cbed 100644
--- a/lib/SimpleSAML/Database.php
+++ b/lib/SimpleSAML/Database.php
@@ -84,28 +84,28 @@ class Database
      */
     private function __construct(Configuration $config)
     {
-        $driverOptions = $config->getArray('database.driver_options', []);
-        if ($config->getBoolean('database.persistent', true)) {
+        $driverOptions = $config->getOptionalArray('database.driver_options', []);
+        if ($config->getOptionalBoolean('database.persistent', true)) {
             $driverOptions[PDO::ATTR_PERSISTENT] = true;
         }
 
         // connect to the primary
         $this->dbPrimary = $this->connect(
             $config->getString('database.dsn'),
-            $config->getString('database.username', null),
-            $config->getString('database.password', null),
+            $config->getOptionalString('database.username', null),
+            $config->getOptionalString('database.password', null),
             $driverOptions
         );
 
         // TODO: deprecated: the "database.slave" terminology is preserved here for backwards compatibility.
-        if ($config->getArray('database.slaves', null) !== null) {
+        if ($config->getOptionalArray('database.slaves', null) !== null) {
             Logger::warning(
                 'The "database.slaves" config option is deprecated. ' .
                 'Please update your configuration to use "database.secondaries".'
             );
         }
         // connect to any configured secondaries, preserving legacy config option
-        $secondaries = $config->getArray('database.secondaries', $config->getArray('database.slaves', []));
+        $secondaries = $config->getOptionalArray('database.secondaries', $config->getOptionalArray('database.slaves', []));
         foreach ($secondaries as $secondary) {
             array_push(
                 $this->dbSecondaries,
@@ -117,7 +117,7 @@ class Database
                 )
             );
         }
-        $this->tablePrefix = $config->getString('database.prefix', '');
+        $this->tablePrefix = $config->getOptionalString('database.prefix', '');
     }
 
 
@@ -133,13 +133,14 @@ class Database
         $assembledConfig = [
             'primary' => [
                 'database.dsn'        => $config->getString('database.dsn'),
-                'database.username'   => $config->getString('database.username', null),
-                'database.password'   => $config->getString('database.password', null),
-                'database.prefix'     => $config->getString('database.prefix', ''),
-                'database.persistent' => $config->getBoolean('database.persistent', true),
+                'database.username'   => $config->getOptionalString('database.username', null),
+                'database.password'   => $config->getOptionalString('database.password', null),
+                'database.prefix'     => $config->getOptionalString('database.prefix', ''),
+                'database.persistent' => $config->getOptionalBoolean('database.persistent', true),
             ],
+
             // TODO: deprecated: the "database.slave" terminology is preserved here for backwards compatibility.
-            'secondaries' => $config->getArray('database.secondaries', $config->getArray('database.slaves', [])),
+            'secondaries' => $config->getOptionalArray('database.secondaries', $config->getOptionalArray('database.slaves', [])),
         ];
 
         return sha1(serialize($assembledConfig));
diff --git a/lib/SimpleSAML/Error/Error.php b/lib/SimpleSAML/Error/Error.php
index f5a4b2d284e9cfc2424a980e01a8586346b7100a..bce003aa70e2f1088811f8bbe738759c7f563679 100644
--- a/lib/SimpleSAML/Error/Error.php
+++ b/lib/SimpleSAML/Error/Error.php
@@ -230,7 +230,7 @@ class Error extends Exception
         $config = Configuration::getInstance();
 
         $data = [];
-        $data['showerrors'] = $config->getBoolean('showerrors', true);
+        $data['showerrors'] = $config->getOptionalBoolean('showerrors', true);
         $data['error'] = $errorData;
         $data['errorCode'] = $this->errorCode;
         $data['parameters'] = $this->parameters;
@@ -242,8 +242,8 @@ class Error extends Exception
 
         // check if there is a valid technical contact email address
         if (
-            $config->getBoolean('errorreporting', true)
-            && $config->getString('technicalcontact_email', 'na@example.org') !== 'na@example.org'
+            $config->getOptionalBoolean('errorreporting', true)
+            && $config->getOptionalString('technicalcontact_email', 'na@example.org') !== 'na@example.org'
         ) {
             // enable error reporting
             $httpUtils = new Utils\HTTP();
@@ -262,7 +262,7 @@ class Error extends Exception
             }
         }
 
-        $show_function = $config->getArray('errors.show_function', null);
+        $show_function = $config->getOptionalArray('errors.show_function', null);
         if (isset($show_function)) {
             Assert::isCallable($show_function);
             $this->setHTTPCode();
diff --git a/lib/SimpleSAML/Error/Exception.php b/lib/SimpleSAML/Error/Exception.php
index 40c5569941c7974d7372af2217598b0588e3ec8c..e75fbbab37600cf2d030ad04fd173dc150f6e8e5 100644
--- a/lib/SimpleSAML/Error/Exception.php
+++ b/lib/SimpleSAML/Error/Exception.php
@@ -200,7 +200,7 @@ class Exception extends \Exception
     protected function logBacktrace(int $level = Logger::DEBUG): void
     {
         // Do nothing if backtraces have been disabled in config.
-        $debug = Configuration::getInstance()->getArray('debug', ['backtraces' => true]);
+        $debug = Configuration::getInstance()->getOptionalArray('debug', ['backtraces' => true]);
         if (array_key_exists('backtraces', $debug) && $debug['backtraces'] === false) {
             return;
         }
diff --git a/lib/SimpleSAML/IdP.php b/lib/SimpleSAML/IdP.php
index 884d934306429549408cdf587e362709f62bf6c5..59fc0de1c9810f988bcccd7c17cae4b542230748 100644
--- a/lib/SimpleSAML/IdP.php
+++ b/lib/SimpleSAML/IdP.php
@@ -81,12 +81,12 @@ class IdP
         $globalConfig = Configuration::getInstance();
 
         if (substr($id, 0, 6) === 'saml2:') {
-            if (!$globalConfig->getBoolean('enable.saml20-idp', false)) {
+            if (!$globalConfig->getOptionalBoolean('enable.saml20-idp', false)) {
                 throw new Error\Exception('enable.saml20-idp disabled in config.php.');
             }
             $this->config = $metadata->getMetaDataConfig(substr($id, 6), 'saml20-idp-hosted');
         } elseif (substr($id, 0, 5) === 'adfs:') {
-            if (!$globalConfig->getBoolean('enable.adfs-idp', false)) {
+            if (!$globalConfig->getOptionalBoolean('enable.adfs-idp', false)) {
                 throw new Error\Exception('enable.adfs-idp disabled in config.php.');
             }
             $this->config = $metadata->getMetaDataConfig(substr($id, 5), 'adfs-idp-hosted');
@@ -423,7 +423,7 @@ class IdP
     public function getLogoutHandler(): LogoutHandlerInterface
     {
         // find the logout handler
-        $logouttype = $this->getConfig()->getString('logouttype', 'traditional');
+        $logouttype = $this->getConfig()->getOptionalString('logouttype', 'traditional');
         switch ($logouttype) {
             case 'traditional':
                 $handler = TraditionalLogoutHandler::class;
diff --git a/lib/SimpleSAML/Locale/Language.php b/lib/SimpleSAML/Locale/Language.php
index 3ae24285f5ec4c47b0ac774a71a4a3fe07cb5eab..da982f670b9b2e8984aa834531fc2ac80c8fa864 100644
--- a/lib/SimpleSAML/Locale/Language.php
+++ b/lib/SimpleSAML/Locale/Language.php
@@ -153,14 +153,14 @@ class Language
     {
         $this->configuration = $configuration;
         $this->availableLanguages = $this->getInstalledLanguages();
-        $this->defaultLanguage = $this->configuration->getString('language.default', self::FALLBACKLANGUAGE);
-        $this->languageParameterName = $this->configuration->getString('language.parameter.name', 'language');
-        $this->customFunction = $this->configuration->getArray('language.get_language_function', null);
-        $this->rtlLanguages = $this->configuration->getArray('language.rtl', []);
+        $this->defaultLanguage = $this->configuration->getOptionalString('language.default', self::FALLBACKLANGUAGE);
+        $this->languageParameterName = $this->configuration->getOptionalString('language.parameter.name', 'language');
+        $this->customFunction = $this->configuration->getOptionalArray('language.get_language_function', null);
+        $this->rtlLanguages = $this->configuration->getOptionalArray('language.rtl', []);
         if (isset($_GET[$this->languageParameterName])) {
             $this->setLanguage(
                 $_GET[$this->languageParameterName],
-                $this->configuration->getBoolean('language.parameter.setcookie', true)
+                $this->configuration->getOptionalBoolean('language.parameter.setcookie', true)
             );
         }
     }
@@ -173,7 +173,10 @@ class Language
      */
     private function getInstalledLanguages(): array
     {
-        $configuredAvailableLanguages = $this->configuration->getArray('language.available', [self::FALLBACKLANGUAGE]);
+        $configuredAvailableLanguages = $this->configuration->getOptionalArray(
+            'language.available',
+            [self::FALLBACKLANGUAGE]
+        );
         $availableLanguages = [];
         foreach ($configuredAvailableLanguages as $code) {
             if (array_key_exists($code, self::$language_names) && isset(self::$language_names[$code])) {
@@ -403,8 +406,8 @@ class Language
     public static function getLanguageCookie(): ?string
     {
         $config = Configuration::getInstance();
-        $availableLanguages = $config->getArray('language.available', [self::FALLBACKLANGUAGE]);
-        $name = $config->getString('language.cookie.name', 'language');
+        $availableLanguages = $config->getOptionalArray('language.available', [self::FALLBACKLANGUAGE]);
+        $name = $config->getOptionalString('language.cookie.name', 'language');
 
         if (isset($_COOKIE[$name])) {
             $language = strtolower((string) $_COOKIE[$name]);
@@ -426,20 +429,20 @@ class Language
     {
         $language = strtolower($language);
         $config = Configuration::getInstance();
-        $availableLanguages = $config->getArray('language.available', [self::FALLBACKLANGUAGE]);
+        $availableLanguages = $config->getOptionalArray('language.available', [self::FALLBACKLANGUAGE]);
 
         if (!in_array($language, $availableLanguages, true) || headers_sent()) {
             return;
         }
 
-        $name = $config->getString('language.cookie.name', 'language');
+        $name = $config->getOptionalString('language.cookie.name', 'language');
         $params = [
-            'lifetime' => ($config->getInteger('language.cookie.lifetime', 60 * 60 * 24 * 900)),
-            'domain'   => ($config->getString('language.cookie.domain', '')),
-            'path'     => ($config->getString('language.cookie.path', '/')),
-            'secure'   => ($config->getBoolean('language.cookie.secure', false)),
-            'httponly' => ($config->getBoolean('language.cookie.httponly', false)),
-            'samesite' => ($config->getString('language.cookie.samesite', null)),
+            'lifetime' => ($config->getOptionalInteger('language.cookie.lifetime', 60 * 60 * 24 * 900)),
+            'domain'   => ($config->getOptionalString('language.cookie.domain', '')),
+            'path'     => ($config->getOptionalString('language.cookie.path', '/')),
+            'secure'   => ($config->getOptionalBoolean('language.cookie.secure', false)),
+            'httponly' => ($config->getOptionalBoolean('language.cookie.httponly', false)),
+            'samesite' => ($config->getOptionalString('language.cookie.samesite', null)),
         ];
 
         $httpUtils = new Utils\HTTP();
diff --git a/lib/SimpleSAML/Locale/Localization.php b/lib/SimpleSAML/Locale/Localization.php
index d88127b04580fdda9fd63fe67d3ccfc052e230ab..1dbb5c1f6ac6af03606ea878393755d3840308c8 100644
--- a/lib/SimpleSAML/Locale/Localization.php
+++ b/lib/SimpleSAML/Locale/Localization.php
@@ -268,7 +268,7 @@ class Localization
     {
         $this->addDomain($this->localeDir, 'attributes');
 
-        list($theme,) = explode(':', $this->configuration->getString('theme.use', 'default'));
+        list($theme,) = explode(':', $this->configuration->getOptionalString('theme.use', 'default'));
         if ($theme !== 'default') {
             $this->addModuleDomain($theme, null, 'attributes');
         }
diff --git a/lib/SimpleSAML/Logger.php b/lib/SimpleSAML/Logger.php
index c390ca82e94d2194bd95886ad619cd2d02d1be6e..15575a58e071525cc931eb4b3f380a8034dda92b 100644
--- a/lib/SimpleSAML/Logger.php
+++ b/lib/SimpleSAML/Logger.php
@@ -449,11 +449,11 @@ class Logger
         $config = Configuration::getInstance();
 
         // setting minimum log_level
-        self::$logLevel = $config->getInteger('logging.level', self::INFO);
+        self::$logLevel = $config->getOptionalInteger('logging.level', self::INFO);
 
         // get the metadata handler option from the configuration
         if (is_null($handler)) {
-            $handler = $config->getString(
+            $handler = $config->getOptionalString(
                 'logging.handler',
                 php_sapi_name() === 'cli' || defined('STDIN') ? 'stderr' : 'syslog'
             );
@@ -473,7 +473,7 @@ class Logger
             $handler = $known_handlers[$handler];
         }
 
-        self::$format = $config->getString('logging.format', self::$format);
+        self::$format = $config->getOptionalString('logging.format', self::$format);
 
         try {
             /** @var \SimpleSAML\Logger\LoggingHandlerInterface */
diff --git a/lib/SimpleSAML/Logger/ErrorLogLoggingHandler.php b/lib/SimpleSAML/Logger/ErrorLogLoggingHandler.php
index 0ccf91d2354f3bb1262f4baabf50dbc209948c48..9c989d725e33e2dfc3d61482b2e620356d226b86 100644
--- a/lib/SimpleSAML/Logger/ErrorLogLoggingHandler.php
+++ b/lib/SimpleSAML/Logger/ErrorLogLoggingHandler.php
@@ -49,7 +49,7 @@ class ErrorLogLoggingHandler implements LoggingHandlerInterface
         $this->processname = preg_replace(
             '/[\x00-\x1F\x7F\xA0]/u',
             '',
-            $config->getString('logging.processname', 'SimpleSAMLphp')
+            $config->getOptionalString('logging.processname', 'SimpleSAMLphp')
         );
     }
 
diff --git a/lib/SimpleSAML/Logger/FileLoggingHandler.php b/lib/SimpleSAML/Logger/FileLoggingHandler.php
index ec06001f32844188d321faf2d0e556d8258910ef..179080bb2436ee1750e0c55f7a97a4ade2616aa2 100644
--- a/lib/SimpleSAML/Logger/FileLoggingHandler.php
+++ b/lib/SimpleSAML/Logger/FileLoggingHandler.php
@@ -54,13 +54,13 @@ class FileLoggingHandler implements LoggingHandlerInterface
     {
         // get the metadata handler option from the configuration
         $this->logFile = $config->getPathValue('loggingdir', 'log/') .
-            $config->getString('logging.logfile', 'simplesamlphp.log');
+            $config->getOptionalString('logging.logfile', 'simplesamlphp.log');
 
         // Remove any non-printable characters before storing
         $this->processname = preg_replace(
             '/[\x00-\x1F\x7F\xA0]/u',
             '',
-            $config->getString('logging.processname', 'SimpleSAMLphp')
+            $config->getOptionalString('logging.processname', 'SimpleSAMLphp')
         );
 
         if (@file_exists($this->logFile)) {
diff --git a/lib/SimpleSAML/Logger/StandardErrorLoggingHandler.php b/lib/SimpleSAML/Logger/StandardErrorLoggingHandler.php
index 2083f9d3224bee76769a208a27316169b49fa682..1fd967955ee9bed379d36fc458829c19198b2d84 100644
--- a/lib/SimpleSAML/Logger/StandardErrorLoggingHandler.php
+++ b/lib/SimpleSAML/Logger/StandardErrorLoggingHandler.php
@@ -26,7 +26,7 @@ class StandardErrorLoggingHandler extends FileLoggingHandler
         $this->processname = preg_replace(
             '/[\x00-\x1F\x7F\xA0]/u',
             '',
-            $config->getString('logging.processname', 'SimpleSAMLphp')
+            $config->getOptionalString('logging.processname', 'SimpleSAMLphp')
         );
         $this->logFile = 'php://stderr';
     }
diff --git a/lib/SimpleSAML/Logger/SyslogLoggingHandler.php b/lib/SimpleSAML/Logger/SyslogLoggingHandler.php
index cdfcf0aa3686c1c6cad259ebb8cc544777547d86..40a0923f6bfb1d25a275ff480266049ff7018385 100644
--- a/lib/SimpleSAML/Logger/SyslogLoggingHandler.php
+++ b/lib/SimpleSAML/Logger/SyslogLoggingHandler.php
@@ -27,13 +27,13 @@ class SyslogLoggingHandler implements LoggingHandlerInterface
      */
     public function __construct(Configuration $config)
     {
-        $facility = $config->getInteger('logging.facility', defined('LOG_LOCAL5') ? constant('LOG_LOCAL5') : LOG_USER);
+        $facility = $config->getOptionalInteger('logging.facility', defined('LOG_LOCAL5') ? constant('LOG_LOCAL5') : LOG_USER);
 
         // Remove any non-printable characters before storing
         $processname = preg_replace(
             '/[\x00-\x1F\x7F\xA0]/u',
             '',
-            $config->getString('logging.processname', 'SimpleSAMLphp')
+            $config->getOptionalString('logging.processname', 'SimpleSAMLphp')
         );
 
         // Setting facility to LOG_USER (only valid in Windows), enable log level rewrite on windows systems
diff --git a/lib/SimpleSAML/Memcache.php b/lib/SimpleSAML/Memcache.php
index 1969fa09b550285472f407d7f1fee15b10b0dfaf..493db7f30b6b191d7f85609823493676fe327569 100644
--- a/lib/SimpleSAML/Memcache.php
+++ b/lib/SimpleSAML/Memcache.php
@@ -375,7 +375,7 @@ class Memcache
         $config = Configuration::getInstance();
 
         // get the expire-value from the configuration
-        $expire = $config->getInteger('memcache_store.expires', 0);
+        $expire = $config->getOptionalInteger('memcache_store.expires', 0);
 
         // it must be a positive integer
         if ($expire < 0) {
diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php
index 4291327288cd4bac81a45765a73582091d3e6cf4..5d8727e759916452e4368e549dd4a1141316abf8 100644
--- a/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php
+++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php
@@ -66,11 +66,11 @@ class MetaDataStorageHandler implements ClearableState
     {
         $config = Configuration::getInstance();
 
-        $sourcesConfig = $config->getArray('metadata.sources', null);
+        $sourcesConfig = $config->getOptionalArray('metadata.sources', null);
 
         // for backwards compatibility, and to provide a default configuration
         if ($sourcesConfig === null) {
-            $type = $config->getString('metadata.handler', 'flatfile');
+            $type = $config->getOptionalString('metadata.handler', 'flatfile');
             $sourcesConfig = [['type' => $type]];
         }
 
diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php
index 3f6e9c5090ec78fffcc7ce930d765e0dcff0ebf2..8b1f39b0a256d01c8de964d1ca2852ef815ac1b8 100644
--- a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php
+++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php
@@ -52,7 +52,7 @@ class MetaDataStorageHandlerFlatFile extends MetaDataStorageSource
         if (array_key_exists('directory', $config)) {
             $this->directory = $config['directory'] ?: 'metadata/';
         } else {
-            $this->directory = $globalConfig->getString('metadatadir', 'metadata/');
+            $this->directory = $globalConfig->getOptionalString('metadatadir', 'metadata/');
         }
 
         /* Resolve this directory relative to the SimpleSAMLphp directory (unless it is
diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php
index ef7ed5c63317baef3cbe81225d15ae25994bf6fd..1dcf7d1cf19265f523bbd52617e4dd0a64d6654c 100644
--- a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php
+++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php
@@ -45,7 +45,6 @@ class MetaDataStorageHandlerSerialize extends MetaDataStorageSource
         $globalConfig = Configuration::getInstance();
 
         $cfgHelp = Configuration::loadFromArray($config, 'serialize metadata source');
-
         $this->directory = $cfgHelp->getString('directory');
 
         /* Resolve this directory relative to the SimpleSAMLphp directory (unless it is
diff --git a/lib/SimpleSAML/Metadata/SAMLBuilder.php b/lib/SimpleSAML/Metadata/SAMLBuilder.php
index f0f96a0a31acd96e21fb110f124b868ed6bf7784..58269de85f48c4c1e47b21478f8de80e8bb02fc9 100644
--- a/lib/SimpleSAML/Metadata/SAMLBuilder.php
+++ b/lib/SimpleSAML/Metadata/SAMLBuilder.php
@@ -416,32 +416,31 @@ class SAMLBuilder
         SPSSODescriptor $spDesc,
         Configuration $metadata
     ): void {
-        $attributes = $metadata->getArray('attributes', []);
-        $name = $metadata->getLocalizedString('name', null);
+        $attributes = $metadata->getOptionalArray('attributes', []);
+        $name = $metadata->getOptionalLocalizedString('name', null);
 
         if ($name === null || count($attributes) == 0) {
             // we cannot add an AttributeConsumingService without name and attributes
             return;
         }
 
-        $attributesrequired = $metadata->getArray('attributes.required', []);
+        $attributesrequired = $metadata->getOptionalArray('attributes.required', []);
 
         /*
          * Add an AttributeConsumingService element with information as name and description and list
          * of requested attributes
          */
         $attributeconsumer = new AttributeConsumingService();
-
-        $attributeconsumer->setIndex($metadata->getInteger('attributes.index', 0));
+        $attributeconsumer->setIndex($metadata->getOptionalInteger('attributes.index', 0));
 
         if ($metadata->hasValue('attributes.isDefault')) {
-            $attributeconsumer->setIsDefault($metadata->getBoolean('attributes.isDefault', false));
+            $attributeconsumer->setIsDefault($metadata->getOptionalBoolean('attributes.isDefault', false));
         }
 
         $attributeconsumer->setServiceName($name);
-        $attributeconsumer->setServiceDescription($metadata->getLocalizedString('description', []));
+        $attributeconsumer->setServiceDescription($metadata->getOptionalLocalizedString('description', []));
 
-        $nameFormat = $metadata->getString('attributes.NameFormat', Constants::NAMEFORMAT_URI);
+        $nameFormat = $metadata->getOptionalString('attributes.NameFormat', Constants::NAMEFORMAT_URI);
         foreach ($attributes as $friendlyName => $attribute) {
             $t = new RequestedAttribute();
             $t->setName($attribute);
@@ -519,10 +518,10 @@ class SAMLBuilder
 
         $e->setSingleLogoutService(self::createEndpoints($metadata->getEndpoints('SingleLogoutService'), false));
 
-        $e->setNameIDFormat($metadata->getArrayizeString('NameIDFormat', []));
+        $e->setNameIDFormat($metadata->getOptionalArrayizeString('NameIDFormat', []));
 
         $endpoints = $metadata->getEndpoints('AssertionConsumerService');
-        foreach ($metadata->getArrayizeString('AssertionConsumerService.artifact', []) as $acs) {
+        foreach ($metadata->getOptionalArrayizeString('AssertionConsumerService.artifact', []) as $acs) {
             $endpoints[] = [
                 'Binding'  => Constants::BINDING_HTTP_ARTIFACT,
                 'Location' => $acs,
@@ -534,7 +533,7 @@ class SAMLBuilder
 
         $this->entityDescriptor->addRoleDescriptor($e);
 
-        foreach ($metadata->getArray('contacts', []) as $contact) {
+        foreach ($metadata->getOptionalArray('contacts', []) as $contact) {
             if (array_key_exists('contactType', $contact) && array_key_exists('emailAddress', $contact)) {
                 $this->addContact(Utils\Config\Metadata::getContact($contact));
             }
@@ -576,13 +575,13 @@ class SAMLBuilder
 
         $e->setSingleLogoutService(self::createEndpoints($metadata->getEndpoints('SingleLogoutService'), false));
 
-        $e->setNameIDFormat($metadata->getArrayizeString('NameIDFormat', []));
+        $e->setNameIDFormat($metadata->getOptionalArrayizeString('NameIDFormat', []));
 
         $e->setSingleSignOnService(self::createEndpoints($metadata->getEndpoints('SingleSignOnService'), false));
 
         $this->entityDescriptor->addRoleDescriptor($e);
 
-        foreach ($metadata->getArray('contacts', []) as $contact) {
+        foreach ($metadata->getOptionalArray('contacts', []) as $contact) {
             if (array_key_exists('contactType', $contact) && array_key_exists('emailAddress', $contact)) {
                 $this->addContact(Utils\Config\Metadata::getContact($contact));
             }
@@ -604,7 +603,7 @@ class SAMLBuilder
         $metadata = Configuration::loadFromArray($metadata, $metadata['entityid']);
 
         $e = new AttributeAuthorityDescriptor();
-        $e->setProtocolSupportEnumeration($metadata->getArray('protocols', [Constants::NS_SAMLP]));
+        $e->setProtocolSupportEnumeration($metadata->getOptionalArray('protocols', [Constants::NS_SAMLP]));
 
         $this->addExtensions($metadata, $e);
         $this->addCertificate($e, $metadata);
@@ -615,7 +614,7 @@ class SAMLBuilder
             false
         ));
 
-        $e->setNameIDFormat($metadata->getArrayizeString('NameIDFormat', []));
+        $e->setNameIDFormat($metadata->getOptionalArrayizeString('NameIDFormat', []));
 
         $this->entityDescriptor->addRoleDescriptor($e);
     }
diff --git a/lib/SimpleSAML/Metadata/Signer.php b/lib/SimpleSAML/Metadata/Signer.php
index 01c1cffc3596fcce924c6cffa83cda8ff7edcf55..10dd1208a74805265e7be1ad8d3edcca5225be54 100644
--- a/lib/SimpleSAML/Metadata/Signer.php
+++ b/lib/SimpleSAML/Metadata/Signer.php
@@ -62,8 +62,8 @@ class Signer
         }
 
         // then we look for default values in the global configuration
-        $privatekey = $config->getString('metadata.sign.privatekey', null);
-        $certificate = $config->getString('metadata.sign.certificate', null);
+        $privatekey = $config->getOptionalString('metadata.sign.privatekey', null);
+        $certificate = $config->getOptionalString('metadata.sign.certificate', null);
         if ($privatekey !== null || $certificate !== null) {
             if ($privatekey === null || $certificate === null) {
                 throw new \Exception(
@@ -75,7 +75,7 @@ class Signer
             }
             $ret = ['privatekey' => $privatekey, 'certificate' => $certificate];
 
-            $privatekey_pass = $config->getString('metadata.sign.privatekey_pass', null);
+            $privatekey_pass = $config->getOptionalString('metadata.sign.privatekey_pass', null);
             if ($privatekey_pass !== null) {
                 $ret['privatekey_pass'] = $privatekey_pass;
             }
@@ -144,7 +144,7 @@ class Signer
             return $entityMetadata['metadata.sign.enable'];
         }
 
-        return $config->getBoolean('metadata.sign.enable', false);
+        return $config->getOptionalBoolean('metadata.sign.enable', false);
     }
 
 
@@ -178,7 +178,7 @@ class Signer
             }
             $alg = $entityMetadata['metadata.sign.algorithm'];
         } else {
-            $alg = $config->getString('metadata.sign.algorithm', XMLSecurityKey::RSA_SHA256);
+            $alg = $config->getOptionalString('metadata.sign.algorithm', XMLSecurityKey::RSA_SHA256);
         }
 
         $supported_algs = [
diff --git a/lib/SimpleSAML/Module.php b/lib/SimpleSAML/Module.php
index 7d84ddd15d68a08c25a648444cff4921a2162ade..c23e4f6430a83e351564def48a4b84b86157a552 100644
--- a/lib/SimpleSAML/Module.php
+++ b/lib/SimpleSAML/Module.php
@@ -117,7 +117,7 @@ class Module
     public static function isModuleEnabled(string $module): bool
     {
         $config = Configuration::getOptionalConfig();
-        return self::isModuleEnabledWithConf($module, $config->getArray('module.enable', self::$core_modules));
+        return self::isModuleEnabledWithConf($module, $config->getOptionalArray('module.enable', self::$core_modules));
     }
 
 
@@ -298,19 +298,18 @@ class Module
             }
         }
 
-        /** @psalm-var \SimpleSAML\Configuration $assetConfig */
+
         $assetConfig = $config->getConfigItem('assets');
-        /** @psalm-var \SimpleSAML\Configuration $cacheConfig */
         $cacheConfig = $assetConfig->getConfigItem('caching');
         $response = new BinaryFileResponse($path);
         $response->setCache([
             // "public" allows response caching even if the request was authenticated,
             // which is exactly what we want for static resources
             'public' => true,
-            'max_age' => strval($cacheConfig->getInteger('max_age', 86400))
+            'max_age' => strval($cacheConfig->getOptionalInteger('max_age', 86400))
         ]);
         $response->setAutoLastModified();
-        if ($cacheConfig->getBoolean('etag', false)) {
+        if ($cacheConfig->getOptionalBoolean('etag', false)) {
             $response->setAutoEtag();
         }
         $response->isNotModified($request);
@@ -522,7 +521,7 @@ class Module
     public static function callHooks(string $hook, &$data = null): void
     {
         $modules = self::getModules();
-        $config = Configuration::getOptionalConfig()->getArray('module.enable', []);
+        $config = Configuration::getOptionalConfig()->getOptionalArray('module.enable', []);
         sort($modules);
         foreach ($modules as $module) {
             if (!self::isModuleEnabledWithConf($module, $config)) {
diff --git a/lib/SimpleSAML/Session.php b/lib/SimpleSAML/Session.php
index 401fd6ade6777427523960471778c80236a4603f..0b317b1a55e4801b1ff6d2f088cabfa735d0aa48 100644
--- a/lib/SimpleSAML/Session.php
+++ b/lib/SimpleSAML/Session.php
@@ -180,7 +180,7 @@ class Session implements Utils\ClearableState
             $this->markDirty();
 
             // initialize data for session check function if defined
-            $checkFunction = self::$config->getValue('session.check_function', null);
+            $checkFunction = self::$config->getOptionalValue('session.check_function', null);
             if (is_callable($checkFunction)) {
                 call_user_func($checkFunction, $this, true);
             }
@@ -355,7 +355,7 @@ class Session implements Utils\ClearableState
             $globalConfig = Configuration::getInstance();
 
             if ($session->authToken !== null) {
-                $authTokenCookieName = $globalConfig->getString(
+                $authTokenCookieName = $globalConfig->getOptionalString(
                     'session.authtoken.cookiename',
                     'SimpleSAMLAuthToken'
                 );
@@ -371,7 +371,7 @@ class Session implements Utils\ClearableState
             }
 
             // run session check function if defined
-            $checkFunction = $globalConfig->getValue('session.check_function', null);
+            $checkFunction = $globalConfig->getOptionalValue('session.check_function', null);
             if (is_callable($checkFunction)) {
                 $check = call_user_func($checkFunction, $session);
                 if ($check !== true) {
@@ -575,7 +575,7 @@ class Session implements Utils\ClearableState
     public function setRememberMeExpire(int $lifetime = null): void
     {
         if ($lifetime === null) {
-            $lifetime = self::$config->getInteger('session.rememberme.lifetime', 14 * 86400);
+            $lifetime = self::$config->getOptionalInteger('session.rememberme.lifetime', 14 * 86400);
         }
         $this->rememberMeExpire = time() + $lifetime;
 
@@ -611,7 +611,7 @@ class Session implements Utils\ClearableState
             $data['AuthnInstant'] = time();
         }
 
-        $maxSessionExpire = time() + self::$config->getInteger('session.duration', 8 * 60 * 60);
+        $maxSessionExpire = time() + self::$config->getOptionalInteger('session.duration', 8 * 60 * 60);
         if (!isset($data['Expire']) || $data['Expire'] > $maxSessionExpire) {
             // unset, or beyond our session lifetime. Clamp it to our maximum session lifetime
             $data['Expire'] = $maxSessionExpire;
@@ -653,14 +653,14 @@ class Session implements Utils\ClearableState
             !$this->transient
             && (!empty($data['RememberMe'])
             || $this->rememberMeExpire !== null)
-            && self::$config->getBoolean('session.rememberme.enable', false)
+            && self::$config->getOptionalBoolean('session.rememberme.enable', false)
         ) {
             $this->setRememberMeExpire();
         } else {
             $httpUtils = new Utils\HTTP();
             try {
                 $httpUtils->setCookie(
-                    self::$config->getString('session.authtoken.cookiename', 'SimpleSAMLAuthToken'),
+                    self::$config->getOptionalString('session.authtoken.cookiename', 'SimpleSAMLAuthToken'),
                     $this->authToken,
                     $sessionHandler->getCookieParams()
                 );
@@ -790,7 +790,7 @@ class Session implements Utils\ClearableState
         if ($this->authToken !== null) {
             $httpUtils = new Utils\HTTP();
             $httpUtils->setCookie(
-                self::$config->getString('session.authtoken.cookiename', 'SimpleSAMLAuthToken'),
+                self::$config->getOptionalString('session.authtoken.cookiename', 'SimpleSAMLAuthToken'),
                 $this->authToken,
                 $params
             );
@@ -809,7 +809,7 @@ class Session implements Utils\ClearableState
         $this->markDirty();
 
         if ($expire === null) {
-            $expire = time() + self::$config->getInteger('session.duration', 8 * 60 * 60);
+            $expire = time() + self::$config->getOptionalInteger('session.duration', 8 * 60 * 60);
         }
 
         $this->authData[$authority]['Expire'] = $expire;
@@ -884,7 +884,7 @@ class Session implements Utils\ClearableState
 
         if ($timeout === null) {
             // use the default timeout
-            $timeout = self::$config->getInteger('session.datastore.timeout', null);
+            $timeout = self::$config->getOptionalInteger('session.datastore.timeout', null);
             if ($timeout !== null) {
                 if ($timeout <= 0) {
                     throw new \Exception(
diff --git a/lib/SimpleSAML/SessionHandler.php b/lib/SimpleSAML/SessionHandler.php
index 324c4f52117b8a3eecf8f93bbe51b02603febe69..b15f0513d31e331d5f28a23f80756f1203ba59a0 100644
--- a/lib/SimpleSAML/SessionHandler.php
+++ b/lib/SimpleSAML/SessionHandler.php
@@ -136,7 +136,7 @@ abstract class SessionHandler
     private static function createSessionHandler(): void
     {
         $config = Configuration::getInstance();
-        $storeType = $config->getString('store.type', 'phpsession');
+        $storeType = $config->getOptionalString('store.type', 'phpsession');
 
         $store = StoreFactory::getInstance($storeType);
         if ($store === false) {
@@ -158,11 +158,11 @@ abstract class SessionHandler
         $config = Configuration::getInstance();
 
         return [
-            'lifetime' => $config->getInteger('session.cookie.lifetime', 0),
-            'path'     => $config->getString('session.cookie.path', '/'),
-            'domain'   => $config->getString('session.cookie.domain', null),
-            'secure'   => $config->getBoolean('session.cookie.secure', false),
-            'samesite' => $config->getString('session.cookie.samesite', null),
+            'lifetime' => $config->getOptionalInteger('session.cookie.lifetime', 0),
+            'path'     => $config->getOptionalString('session.cookie.path', '/'),
+            'domain'   => $config->getOptionalString('session.cookie.domain', null),
+            'secure'   => $config->getOptionalBoolean('session.cookie.secure', false),
+            'samesite' => $config->getOptionalString('session.cookie.samesite', null),
             'httponly' => true,
         ];
     }
diff --git a/lib/SimpleSAML/SessionHandlerCookie.php b/lib/SimpleSAML/SessionHandlerCookie.php
index 86bad84468289287e7ec569af70858ea43d60135..1c0d2223c4c36fb826d3dc239a7e3cfad8964eec 100644
--- a/lib/SimpleSAML/SessionHandlerCookie.php
+++ b/lib/SimpleSAML/SessionHandlerCookie.php
@@ -45,7 +45,7 @@ abstract class SessionHandlerCookie extends SessionHandler
         parent::__construct();
 
         $config = Configuration::getInstance();
-        $this->cookie_name = $config->getString('session.cookie.name', 'SimpleSAMLSessionID');
+        $this->cookie_name = $config->getOptionalString('session.cookie.name', 'SimpleSAMLSessionID');
     }
 
 
diff --git a/lib/SimpleSAML/SessionHandlerPHP.php b/lib/SimpleSAML/SessionHandlerPHP.php
index 1f6b22f8f0624c336e2aee52f88c6656647e1c5a..b8e83a88588bca95bde6dad9f9b34fbddc513644 100644
--- a/lib/SimpleSAML/SessionHandlerPHP.php
+++ b/lib/SimpleSAML/SessionHandlerPHP.php
@@ -48,7 +48,7 @@ class SessionHandlerPHP extends SessionHandler
         parent::__construct();
 
         $config = Configuration::getInstance();
-        $this->cookie_name = $config->getString(
+        $this->cookie_name = $config->getOptionalString(
             'session.phpsession.cookiename',
             ini_get('session.name') ?: 'PHPSESSID'
         );
@@ -92,7 +92,7 @@ class SessionHandlerPHP extends SessionHandler
             ]);
         }
 
-        $savepath = $config->getString('session.phpsession.savepath', null);
+        $savepath = $config->getOptionalString('session.phpsession.savepath', null);
         if (!empty($savepath)) {
             session_save_path($savepath);
         }
@@ -287,13 +287,13 @@ class SessionHandlerPHP extends SessionHandler
                 'You cannot set both the session.phpsession.limitedpath and session.cookie.path options.'
             );
         } elseif ($config->hasValue('session.phpsession.limitedpath')) {
-            $ret['path'] = $config->getBoolean(
+            $ret['path'] = $config->getOptionalBoolean(
                 'session.phpsession.limitedpath',
                 false
             ) ? $config->getBasePath() : '/';
         }
 
-        $ret['httponly'] = $config->getBoolean('session.phpsession.httponly', true);
+        $ret['httponly'] = $config->getOptionalBoolean('session.phpsession.httponly', true);
 
         return $ret;
     }
diff --git a/lib/SimpleSAML/SessionHandlerStore.php b/lib/SimpleSAML/SessionHandlerStore.php
index f4f89e71068def0cc4206318dfd850ce233394d5..a995551d877db949306b1191306171fbd6d49de5 100644
--- a/lib/SimpleSAML/SessionHandlerStore.php
+++ b/lib/SimpleSAML/SessionHandlerStore.php
@@ -79,7 +79,7 @@ class SessionHandlerStore extends SessionHandlerCookie
         $sessionId = $session->getSessionId();
 
         $config = Configuration::getInstance();
-        $sessionDuration = $config->getInteger('session.duration', 8 * 60 * 60);
+        $sessionDuration = $config->getOptionalInteger('session.duration', 8 * 60 * 60);
         $expire = time() + $sessionDuration;
 
         $this->store->set('session', $sessionId, $session, $expire);
diff --git a/lib/SimpleSAML/Stats.php b/lib/SimpleSAML/Stats.php
index 4b3d020accd5a0f6fc0769244ddf1dd0c9a8f62b..1ce45cce678f86f49be9e5645640f04093095c33 100644
--- a/lib/SimpleSAML/Stats.php
+++ b/lib/SimpleSAML/Stats.php
@@ -56,7 +56,7 @@ class Stats
     private static function initOutputs(): void
     {
         $config = Configuration::getInstance();
-        $outputCfgs = $config->getArray('statistics.out', []);
+        $outputCfgs = $config->getOptionalArray('statistics.out', []);
 
         self::$outputs = [];
         foreach ($outputCfgs as $cfg) {
diff --git a/lib/SimpleSAML/Store/MemcacheStore.php b/lib/SimpleSAML/Store/MemcacheStore.php
index b67a1f8acd774b21e1c53082d3057a013101aee7..0c74907e24fac174be77d025705998229347f467 100644
--- a/lib/SimpleSAML/Store/MemcacheStore.php
+++ b/lib/SimpleSAML/Store/MemcacheStore.php
@@ -29,7 +29,7 @@ class MemcacheStore implements StoreInterface
     public function __construct()
     {
         $config = Configuration::getInstance();
-        $this->prefix = $config->getString('memcache_store.prefix', 'simpleSAMLphp');
+        $this->prefix = $config->getOptionalString('memcache_store.prefix', 'simpleSAMLphp');
     }
 
 
diff --git a/lib/SimpleSAML/Store/RedisStore.php b/lib/SimpleSAML/Store/RedisStore.php
index d78871fb6dfc4fb1dd9f8c8b24419f6de1824957..7781d3c107122c88ffa31060b10f2360808c90dc 100644
--- a/lib/SimpleSAML/Store/RedisStore.php
+++ b/lib/SimpleSAML/Store/RedisStore.php
@@ -35,11 +35,11 @@ class RedisStore implements StoreInterface
         if ($redis === null) {
             $config = Configuration::getInstance();
 
-            $host = $config->getString('store.redis.host', 'localhost');
-            $port = $config->getInteger('store.redis.port', 6379);
-            $prefix = $config->getString('store.redis.prefix', 'SimpleSAMLphp');
-            $password = $config->getString('store.redis.password', '');
-            $database = $config->getInteger('store.redis.database', 0);
+            $host = $config->getOptionalString('store.redis.host', 'localhost');
+            $port = $config->getOptionalInteger('store.redis.port', 6379);
+            $prefix = $config->getOptionalString('store.redis.prefix', 'SimpleSAMLphp');
+            $password = $config->getOptionalString('store.redis.password', null);
+            $database = $config->getOptionalInteger('store.redis.database', 0);
 
             $redis = new Client(
                 [
diff --git a/lib/SimpleSAML/Store/SQLStore.php b/lib/SimpleSAML/Store/SQLStore.php
index eef08f25bf542b72a7b1d433804e64439b3a5271..3bdf83dcc85811712cfef54681ff1c15b4a072c8 100644
--- a/lib/SimpleSAML/Store/SQLStore.php
+++ b/lib/SimpleSAML/Store/SQLStore.php
@@ -55,10 +55,10 @@ class SQLStore implements StoreInterface
         $config = Configuration::getInstance();
 
         $dsn = $config->getString('store.sql.dsn');
-        $username = $config->getString('store.sql.username', null);
-        $password = $config->getString('store.sql.password', null);
-        $options = $config->getArray('store.sql.options', null);
-        $this->prefix = $config->getString('store.sql.prefix', 'simpleSAMLphp');
+        $username = $config->getOptionalString('store.sql.username', null);
+        $password = $config->getOptionalString('store.sql.password', null);
+        $options = $config->getOptionalArray('store.sql.options', null);
+        $this->prefix = $config->getOptionalString('store.sql.prefix', 'simpleSAMLphp');
         try {
             $this->pdo = new PDO($dsn, $username, $password, $options);
         } catch (PDOException $e) {
diff --git a/lib/SimpleSAML/Utils/Config/Metadata.php b/lib/SimpleSAML/Utils/Config/Metadata.php
index 0ec059974b21c0966e78efecfa463a9b02ab8853..f7a1a389aaa29328fb81157b4a51a641a1f0ca0f 100644
--- a/lib/SimpleSAML/Utils/Config/Metadata.php
+++ b/lib/SimpleSAML/Utils/Config/Metadata.php
@@ -277,11 +277,11 @@ class Metadata
             // handle current configurations specifying an array in the NameIDPolicy config option
             $nameIdPolicy_cf = Configuration::loadFromArray($nameIdPolicy);
             $policy = [
-                'Format'      => $nameIdPolicy_cf->getString('Format', Constants::NAMEID_TRANSIENT),
-                'AllowCreate' => $nameIdPolicy_cf->getBoolean('AllowCreate', true),
+                'Format'      => $nameIdPolicy_cf->getOptionalString('Format', Constants::NAMEID_TRANSIENT),
+                'AllowCreate' => $nameIdPolicy_cf->getOptionalBoolean('AllowCreate', true),
             ];
-            $spNameQualifier = $nameIdPolicy_cf->getString('SPNameQualifier', false);
-            if ($spNameQualifier !== false) {
+            $spNameQualifier = $nameIdPolicy_cf->getOptionalString('SPNameQualifier', null);
+            if ($spNameQualifier !== null) {
                 $policy['SPNameQualifier'] = $spNameQualifier;
             }
         } elseif ($nameIdPolicy === null) {
diff --git a/lib/SimpleSAML/Utils/Crypto.php b/lib/SimpleSAML/Utils/Crypto.php
index 0be2ec3ee136158723abd9f7977f952124cb34d2..5d741a4b1df67c80e0d165be5e953a36d587e175 100644
--- a/lib/SimpleSAML/Utils/Crypto.php
+++ b/lib/SimpleSAML/Utils/Crypto.php
@@ -203,7 +203,7 @@ class Crypto
         string $prefix = '',
         bool $full_path = false
     ): ?array {
-        $file = $metadata->getString($prefix . 'privatekey', null);
+        $file = $metadata->getOptionalString($prefix . 'privatekey', null);
         if ($file === null) {
             // no private key found
             if ($required) {
@@ -225,7 +225,7 @@ class Crypto
 
         $ret = [
             'PEM' => $data,
-            'password' => $metadata->getString($prefix . 'privatekey_pass', null),
+            'password' => $metadata->getOptionalString($prefix . 'privatekey_pass', null),
         ];
 
         return $ret;
diff --git a/lib/SimpleSAML/Utils/EMail.php b/lib/SimpleSAML/Utils/EMail.php
index 8a99fe0de908ad7da546db779a2c9b51f343e344..60f98b85293c920251dbbc1f774760082d99b305 100644
--- a/lib/SimpleSAML/Utils/EMail.php
+++ b/lib/SimpleSAML/Utils/EMail.php
@@ -65,7 +65,7 @@ class EMail
     public function getDefaultMailAddress(): string
     {
         $config = Configuration::getInstance();
-        $address = $config->getString('technicalcontact_email', 'na@example.org');
+        $address = $config->getOptionalString('technicalcontact_email', 'na@example.org');
         $address = preg_replace('/^mailto:/i', '', $address);
         if ('na@example.org' === $address) {
             throw new \Exception('technicalcontact_email must be changed from the default value');
@@ -228,8 +228,8 @@ class EMail
     {
         $config = Configuration::getInstance();
         $EMail->setTransportMethod(
-            $config->getString('mail.transport.method', 'mail'),
-            $config->getArrayize('mail.transport.options', [])
+            $config->getOptionalString('mail.transport.method', 'mail'),
+            $config->getOptionalArrayize('mail.transport.options', [])
         );
 
         return $EMail;
diff --git a/lib/SimpleSAML/Utils/HTTP.php b/lib/SimpleSAML/Utils/HTTP.php
index 402901b67f67f9459cfbbe83ea6a72dc60f59cc6..e264a4e2516b0da2b924422398ca6898f97e8b52 100644
--- a/lib/SimpleSAML/Utils/HTTP.php
+++ b/lib/SimpleSAML/Utils/HTTP.php
@@ -375,7 +375,7 @@ class HTTP
 
         // get the white list of domains
         if ($trustedSites === null) {
-            $trustedSites = Configuration::getInstance()->getValue('trusted.url.domains', []);
+            $trustedSites = Configuration::getInstance()->getOptionalArray('trusted.url.domains', []);
         }
 
         // validates the URL's host is among those allowed
@@ -406,10 +406,10 @@ class HTTP
 
             $self_host = $this->getSelfHostWithNonStandardPort();
 
-            $trustedRegex = Configuration::getInstance()->getValue('trusted.url.regex', false);
+            $trustedRegex = Configuration::getInstance()->getOptionalValue('trusted.url.regex', null);
 
             $trusted = false;
-            if ($trustedRegex) {
+            if (!in_array($trustedRegex, [null, false])) {
                 // add self host to the white list
                 $trustedSites[] = preg_quote($self_host);
                 foreach ($trustedSites as $regex) {
@@ -455,13 +455,13 @@ class HTTP
     {
         $config = Configuration::getInstance();
 
-        $proxy = $config->getString('proxy', null);
+        $proxy = $config->getOptionalString('proxy', null);
         if ($proxy !== null) {
             if (!isset($context['http']['proxy'])) {
                 $context['http']['proxy'] = $proxy;
             }
-            $proxy_auth = $config->getString('proxy.auth', false);
-            if ($proxy_auth !== false) {
+            $proxy_auth = $config->getOptionalString('proxy.auth', null);
+            if ($proxy_auth !== null) {
                 $context['http']['header'] = "Proxy-Authorization: Basic " . base64_encode($proxy_auth);
             }
             if (!isset($context['http']['request_fulluri'])) {
@@ -638,7 +638,7 @@ class HTTP
     public function getBaseURL(): string
     {
         $globalConfig = Configuration::getInstance();
-        $baseURL = $globalConfig->getString('baseurlpath', 'simplesaml/');
+        $baseURL = $globalConfig->getOptionalString('baseurlpath', 'simplesaml/');
 
         if (preg_match('#^https?://.*/?$#D', $baseURL, $matches)) {
             // full URL in baseurlpath, override local server values
@@ -688,7 +688,7 @@ class HTTP
     public function getPOSTRedirectURL(string $destination, array $data): string
     {
         $config = Configuration::getInstance();
-        $allowed = $config->getBoolean('enable.http_post', false);
+        $allowed = $config->getOptionalBoolean('enable.http_post', false);
 
         if ($allowed && preg_match("#^http:#", $destination) && $this->isHTTPS()) {
             // we need to post the data to HTTP
@@ -802,10 +802,9 @@ class HTTP
              * current URI, so we need to build it back from the PHP environment, unless we have a base URL specified
              * for this case in the configuration. First, check if that's the case.
              */
+            $appcfg = $cfg->getOptionalConfigItem('application', null);
+            $appurl = ($appcfg !== null) ? $appcfg->getOptionalString('baseURL', null) : null;
 
-            /** @var \SimpleSAML\Configuration $appcfg */
-            $appcfg = $cfg->getConfigItem('application');
-            $appurl = $appcfg->getString('baseURL', '');
             if (!empty($appurl)) {
                 $protocol = parse_url($appurl, PHP_URL_SCHEME);
                 $hostname = parse_url($appurl, PHP_URL_HOST);
@@ -1181,7 +1180,7 @@ class HTTP
         }
 
         $config = Configuration::getInstance();
-        $allowed = $config->getBoolean('enable.http_post', false);
+        $allowed = $config->getOptionalBoolean('enable.http_post', false);
 
         if ($allowed && preg_match("#^http:#", $destination) && $this->isHTTPS()) {
             // we need to post the data to HTTP
diff --git a/lib/SimpleSAML/Utils/System.php b/lib/SimpleSAML/Utils/System.php
index 33efcae4e170c20a3f3aa0fab6380c0470998972..e2dde4be3c3a5a42c1ef147aa2c50293d50e1993 100644
--- a/lib/SimpleSAML/Utils/System.php
+++ b/lib/SimpleSAML/Utils/System.php
@@ -75,7 +75,7 @@ class System
         $globalConfig = Configuration::getInstance();
 
         $tempDir = rtrim(
-            $globalConfig->getString(
+            $globalConfig->getOptionalString(
                 'tempdir',
                 sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'simplesaml'
             ),
diff --git a/lib/SimpleSAML/Utils/Time.php b/lib/SimpleSAML/Utils/Time.php
index b152edb05b8c724acf8f61cbed2210543433a75a..f0e37c2b40e9036895be94cdf6f78ba392fd3a63 100644
--- a/lib/SimpleSAML/Utils/Time.php
+++ b/lib/SimpleSAML/Utils/Time.php
@@ -56,7 +56,7 @@ class Time
 
         $globalConfig = Configuration::getInstance();
 
-        $timezone = $globalConfig->getString('timezone', null);
+        $timezone = $globalConfig->getOptionalString('timezone', null);
         if ($timezone !== null) {
             if (!date_default_timezone_set($timezone)) {
                 throw new Error\Exception('Invalid timezone set in the "timezone" option in config.php.');
diff --git a/lib/SimpleSAML/Utils/XML.php b/lib/SimpleSAML/Utils/XML.php
index 51928aaffe7f7fdf041e0b5e4082cfe2806d5291..a77d9892cd5231f517497dd576a4f4ead96e1d68 100644
--- a/lib/SimpleSAML/Utils/XML.php
+++ b/lib/SimpleSAML/Utils/XML.php
@@ -52,7 +52,7 @@ class XML
         }
 
         // see if debugging is enabled for XML validation
-        $debug = Configuration::getInstance()->getArray('debug', ['validatexml' => false]);
+        $debug = Configuration::getInstance()->getOptionalArray('debug', ['validatexml' => false]);
 
         if (
             !(
@@ -100,7 +100,7 @@ class XML
         }
 
         // see if debugging is enabled for SAML messages
-        $debug = Configuration::getInstance()->getArray('debug', ['saml' => false]);
+        $debug = Configuration::getInstance()->getOptionalArray('debug', ['saml' => false]);
 
         if (
             !(
diff --git a/lib/SimpleSAML/XHTML/IdPDisco.php b/lib/SimpleSAML/XHTML/IdPDisco.php
index 5abcd97c39ea631ec3dac8273f05a1d297b758a6..852a2d6de6f0afb7420047c01fbcc8dbce1c5dc2 100644
--- a/lib/SimpleSAML/XHTML/IdPDisco.php
+++ b/lib/SimpleSAML/XHTML/IdPDisco.php
@@ -243,7 +243,7 @@ class IdPDisco
             return null;
         }
 
-        if (!$this->config->getBoolean('idpdisco.validate', true)) {
+        if (!$this->config->getOptionalBoolean('idpdisco.validate', true)) {
             return $idp;
         }
 
@@ -308,7 +308,7 @@ class IdPDisco
      */
     protected function getSavedIdP(): ?string
     {
-        if (!$this->config->getBoolean('idpdisco.enableremember', false)) {
+        if (!$this->config->getOptionalBoolean('idpdisco.enableremember', false)) {
             // saving of IdP choices is disabled
             return null;
         }
@@ -402,7 +402,7 @@ class IdPDisco
      */
     protected function saveIdP(): bool
     {
-        if (!$this->config->getBoolean('idpdisco.enableremember', false)) {
+        if (!$this->config->getOptionalBoolean('idpdisco.enableremember', false)) {
             // saving of IdP choices is disabled
             return false;
         }
@@ -513,7 +513,7 @@ class IdPDisco
         $httpUtils = new Utils\HTTP();
         $idp = $this->getTargetIdP();
         if ($idp !== null) {
-            $extDiscoveryStorage = $this->config->getString('idpdisco.extDiscoveryStorage', null);
+            $extDiscoveryStorage = $this->config->getOptionalString('idpdisco.extDiscoveryStorage', null);
             if ($extDiscoveryStorage !== null) {
                 $this->log('Choice made [' . $idp . '] (Forwarding to external discovery storage)');
                 $httpUtils->redirectTrustedURL($extDiscoveryStorage, [
@@ -576,7 +576,7 @@ class IdPDisco
          * Make use of an XHTML template to present the select IdP choice to the user. Currently the supported options
          * is either a drop down menu or a list view.
          */
-        switch ($this->config->getString('idpdisco.layout', 'links')) {
+        switch ($this->config->getOptionalString('idpdisco.layout', 'links')) {
             case 'dropdown':
                 $templateFile = 'selectidp-dropdown.twig';
                 break;
@@ -618,8 +618,8 @@ class IdPDisco
         $t->data['returnIDParam'] = $this->returnIdParam;
         $t->data['entityID'] = $this->spEntityId;
         $t->data['urlpattern'] = htmlspecialchars($httpUtils->getSelfURLNoQuery());
-        $t->data['rememberenabled'] = $this->config->getBoolean('idpdisco.enableremember', false);
-        $t->data['rememberchecked'] = $this->config->getBoolean('idpdisco.rememberchecked', false);
+        $t->data['rememberenabled'] = $this->config->getOptionalBoolean('idpdisco.enableremember', false);
+        $t->data['rememberchecked'] = $this->config->getOptionalBoolean('idpdisco.rememberchecked', false);
         $t->send();
     }
 }
diff --git a/lib/SimpleSAML/XHTML/Template.php b/lib/SimpleSAML/XHTML/Template.php
index 5ad664905b14648e909f624f0f4c1cfd36d60de2..8b0c421aeea76ac00ea28c2859312987cf8b6c68 100644
--- a/lib/SimpleSAML/XHTML/Template.php
+++ b/lib/SimpleSAML/XHTML/Template.php
@@ -129,7 +129,7 @@ class Template extends Response
 
         // parse config to find theme and module theme is in, if any
         list($this->theme['module'], $this->theme['name']) = $this->findModuleAndTemplateName(
-            $this->configuration->getString('theme.use', 'default')
+            $this->configuration->getOptionalString('theme.use', 'default')
         );
 
         // initialize internationalization system
@@ -137,9 +137,9 @@ class Template extends Response
         $this->localization = new Localization($configuration);
 
         // check if we need to attach a theme controller
-        $controller = $this->configuration->getString('theme.controller', false);
+        $controller = $this->configuration->getOptionalString('theme.controller', null);
         if (
-            $controller
+            $controller !== null
             && class_exists($controller)
             && in_array(TemplateControllerInterface::class, class_implements($controller))
         ) {
@@ -265,8 +265,8 @@ class Template extends Response
      */
     private function setupTwig(): Environment
     {
-        $auto_reload = $this->configuration->getBoolean('template.auto_reload', true);
-        $cache = $this->configuration->getString('template.cache', false);
+        $auto_reload = $this->configuration->getOptionalBoolean('template.auto_reload', true);
+        $cache = $this->configuration->getOptionalString('template.cache', null);
 
         // set up template paths
         $loader = $this->setupTwigTemplatepaths();
@@ -287,7 +287,7 @@ class Template extends Response
         // set up translation
         $options = [
             'auto_reload' => $auto_reload,
-            'cache' => $cache,
+            'cache' => $cache ?? false,
             'strict_variables' => true,
         ];
 
@@ -299,7 +299,7 @@ class Template extends Response
         $twig->addFunction(new TwigFunction('moduleURL', [Module::class, 'getModuleURL']));
 
         // initialize some basic context
-        $langParam = $this->configuration->getString('language.parameter.name', 'language');
+        $langParam = $this->configuration->getOptionalString('language.parameter.name', 'language');
         $twig->addGlobal('languageParameterName', $langParam);
         $twig->addGlobal('currentLanguage', $this->translator->getLanguage()->getLanguage());
         $twig->addGlobal('isRTL', false); // language RTL configuration
@@ -312,7 +312,7 @@ class Template extends Response
         }
         $twig->addGlobal('queryParams', $queryParams);
         $twig->addGlobal('templateId', str_replace('.twig', '', $this->normalizeTemplateName($this->template)));
-        $twig->addGlobal('isProduction', $this->configuration->getBoolean('production', true));
+        $twig->addGlobal('isProduction', $this->configuration->getOptionalBoolean('production', true));
         $twig->addGlobal('baseurlpath', ltrim($this->configuration->getBasePath(), '/'));
 
         // add a filter for translations out of arrays
@@ -488,7 +488,7 @@ class Template extends Response
 
         $this->data['year'] = date('Y');
 
-        $this->data['header'] = $this->configuration->getValue('theme.header', 'SimpleSAMLphp');
+        $this->data['header'] = $this->configuration->getOptionalString('theme.header', 'SimpleSAMLphp');
     }
 
     /**
diff --git a/modules/admin/lib/Controller/Config.php b/modules/admin/lib/Controller/Config.php
index f29180ab7dc5cfb94cd693ba2c04fee5f4664343..aeba9b7e9edc94689072e589678130c0a8aaf608 100644
--- a/modules/admin/lib/Controller/Config.php
+++ b/modules/admin/lib/Controller/Config.php
@@ -132,7 +132,7 @@ class Config
                 ]
             ],
             'enablematrix' => [
-                'saml20idp' => $this->config->getBoolean('enable.saml20-idp', false),
+                'saml20idp' => $this->config->getOptionalBoolean('enable.saml20-idp', false),
             ],
             'funcmatrix' => $this->getPrerequisiteChecks(),
             'logouturl' => $this->authUtils->getAdminLogoutURL(),
@@ -202,7 +202,7 @@ class Config
                 'enabled' => version_compare(phpversion(), '7.4', '>=')
             ]
         ];
-        $store = $this->config->getString('store.type', '');
+        $store = $this->config->getOptionalString('store.type', null);
 
         // check dependencies used via normal functions
         $functions = [
@@ -267,7 +267,7 @@ class Config
                 ]
             ],
             'curl_init' => [
-                'required' => $this->config->getBoolean('admin.checkforupdates', true) ? 'required' : 'optional',
+                'required' => $this->config->getOptionalBoolean('admin.checkforupdates', true) ? 'required' : 'optional',
                 'descr' => [
                     'optional' => Translate::noop(
                         'cURL (might be required by some modules)'
@@ -354,19 +354,19 @@ class Config
         $matrix[] = [
             'required' => 'optional',
             'descr' => Translate::noop('The <code>technicalcontact_email</code> configuration option should be set'),
-            'enabled' => $this->config->getString('technicalcontact_email', 'na@example.org') !== 'na@example.org',
+            'enabled' => $this->config->getOptionalString('technicalcontact_email', 'na@example.org') !== 'na@example.org',
         ];
 
         $matrix[] = [
             'required' => 'required',
             'descr' => Translate::noop('The auth.adminpassword configuration option must be set'),
-            'enabled' => $this->config->getString('auth.adminpassword', '123') !== '123',
+            'enabled' => $this->config->getOptionalString('auth.adminpassword', '123') !== '123',
         ];
 
         $cryptoUtils = new Utils\Crypto();
 
         // perform some sanity checks on the configured certificates
-        if ($this->config->getBoolean('enable.saml20-idp', false) !== false) {
+        if ($this->config->getOptionalBoolean('enable.saml20-idp', false) !== false) {
             $handler = MetaDataStorageHandler::getMetadataHandler();
             try {
                 $metadata = $handler->getMetaDataCurrent('saml20-idp-hosted');
@@ -401,7 +401,7 @@ class Config
             }
         }
 
-        if ($this->config->getBoolean('metadata.sign.enable', false) !== false) {
+        if ($this->config->getOptionalBoolean('metadata.sign.enable', false) !== false) {
             $private = $cryptoUtils->loadPrivateKey($this->config, false, 'metadata.sign.');
             $public = $cryptoUtils->loadPublicKey($this->config, false, 'metadata.sign.');
             $matrix[] = [
@@ -442,7 +442,7 @@ class Config
         }
 
         // make sure we have a secret salt set
-        if ($this->config->getValue('secretsalt') === 'defaultsecretsalt') {
+        if ($this->config->getString('secretsalt') === 'defaultsecretsalt') {
             $warnings[] = Translate::noop(
                 '<strong>The configuration uses the default secret salt</strong>. Make sure to modify the <code>' .
                 'secretsalt</code> option in the SimpleSAMLphp configuration in production environments. <a ' .
@@ -455,7 +455,7 @@ class Config
          * Check for updates. Store the remote result in the session so we don't need to fetch it on every access to
          * this page.
          */
-        if ($this->config->getBoolean('admin.checkforupdates', true) && $this->config->getVersion() !== 'master') {
+        if ($this->config->getOptionalBoolean('admin.checkforupdates', true) && $this->config->getVersion() !== 'master') {
             if (!function_exists('curl_init')) {
                 $warnings[] = Translate::noop(
                     'The cURL PHP extension is missing. Cannot check for SimpleSAMLphp updates.'
@@ -468,8 +468,8 @@ class Config
                     curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
                     curl_setopt($ch, CURLOPT_USERAGENT, 'SimpleSAMLphp');
                     curl_setopt($ch, CURLOPT_TIMEOUT, 2);
-                    curl_setopt($ch, CURLOPT_PROXY, $this->config->getString('proxy', null));
-                    curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->config->getValue('proxy.auth', null));
+                    curl_setopt($ch, CURLOPT_PROXY, $this->config->getOptionalString('proxy', null));
+                    curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->config->getOptionalValue('proxy.auth', null));
                     $response = curl_exec($ch);
 
                     if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) === 200) {
diff --git a/modules/admin/lib/Controller/Federation.php b/modules/admin/lib/Controller/Federation.php
index cada26dd1d5bb51149d24547918235704450e78e..6969ca17daae9ba435a6bc093da65c1d3de6cdc3 100644
--- a/modules/admin/lib/Controller/Federation.php
+++ b/modules/admin/lib/Controller/Federation.php
@@ -123,9 +123,9 @@ class Federation
             'remote' => [
                 'saml20-idp-remote' => !empty($hostedSPs)
                     ? $this->mdHandler->getList('saml20-idp-remote', true) : [],
-                'saml20-sp-remote' => $this->config->getBoolean('enable.saml20-idp', false) === true
+                'saml20-sp-remote' => $this->config->getOptionalBoolean('enable.saml20-idp', false) === true
                     ? $this->mdHandler->getList('saml20-sp-remote', true) : [],
-                'adfs-sp-remote' => ($this->config->getBoolean('enable.adfs-idp', false) === true) &&
+                'adfs-sp-remote' => ($this->config->getOptionalBoolean('enable.adfs-idp', false) === true) &&
                     Module::isModuleEnabled('adfs') ? $this->mdHandler->getList('adfs-sp-remote', true) : [],
             ],
         ];
@@ -190,7 +190,7 @@ class Federation
         $entities = [];
 
         // SAML 2
-        if ($this->config->getBoolean('enable.saml20-idp', false)) {
+        if ($this->config->getOptionalBoolean('enable.saml20-idp', false)) {
             try {
                 $idps = $this->mdHandler->getList('saml20-idp-hosted');
                 $saml2entities = [];
@@ -230,7 +230,7 @@ class Federation
         }
 
         // ADFS
-        if ($this->config->getBoolean('enable.adfs-idp', false) && Module::isModuleEnabled('adfs')) {
+        if ($this->config->getOptionalBoolean('enable.adfs-idp', false) && Module::isModuleEnabled('adfs')) {
             try {
                 $idps = $this->mdHandler->getList('adfs-idp-hosted');
                 $adfsentities = [];
@@ -329,9 +329,9 @@ class Federation
             }
 
             // get the name
-            $name = $source->getMetadata()->getLocalizedString(
+            $name = $source->getMetadata()->getOptionalLocalizedString(
                 'name',
-                $source->getMetadata()->getLocalizedString('OrganizationDisplayName', $source->getAuthId())
+                $source->getMetadata()->getOptionalLocalizedString('OrganizationDisplayName', ['en' => $source->getAuthId()])
             );
 
             $builder = new SAMLBuilder($source->getEntityId());
diff --git a/modules/core/hooks/hook_sanitycheck.php b/modules/core/hooks/hook_sanitycheck.php
index 53c0d9e8219c9874f7dbe3f68ee26f6bcefba2b8..09befb775d91d00371986930bf23c730dcf3c898 100644
--- a/modules/core/hooks/hook_sanitycheck.php
+++ b/modules/core/hooks/hook_sanitycheck.php
@@ -18,13 +18,13 @@ function core_hook_sanitycheck(array &$hookinfo): void
 
     $config = Configuration::getInstance();
 
-    if ($config->getString('auth.adminpassword', '123') === '123') {
+    if ($config->getOptionalString('auth.adminpassword', '123') === '123') {
         $hookinfo['errors'][] = '[core] Password in config.php is not set properly';
     } else {
         $hookinfo['info'][] = '[core] Password in config.php is set properly';
     }
 
-    if ($config->getString('technicalcontact_email', 'na@example.org') === 'na@example.org') {
+    if ($config->getOptionalString('technicalcontact_email', 'na@example.org') === 'na@example.org') {
         $hookinfo['errors'][] = '[core] In config.php technicalcontact_email is not set properly';
     } else {
         $hookinfo['info'][] = '[core] In config.php technicalcontact_email is set properly';
diff --git a/modules/core/lib/Auth/Process/ExtendIdPSession.php b/modules/core/lib/Auth/Process/ExtendIdPSession.php
index 09ce3d08c3a1077cb05daf0bca7df3fc116436e0..f3931f506c0b8c92dcd3cd98e8cf7601ec53c15f 100644
--- a/modules/core/lib/Auth/Process/ExtendIdPSession.php
+++ b/modules/core/lib/Auth/Process/ExtendIdPSession.php
@@ -28,7 +28,7 @@ class ExtendIdPSession extends Auth\ProcessingFilter
         $delta = $state['Expire'] - $now;
 
         $globalConfig = Configuration::getInstance();
-        $sessionDuration = $globalConfig->getInteger('session.duration', 28800); // 8*60*60
+        $sessionDuration = $globalConfig->getOptionalInteger('session.duration', 28800); // 8*60*60
 
         // Extend only if half of session duration already passed
         if ($delta >= ($sessionDuration * 0.5)) {
@@ -46,7 +46,7 @@ class ExtendIdPSession extends Auth\ProcessingFilter
         if (
             !empty($state['RememberMe'])
             && $rememberMeExpire !== null
-            && $globalConfig->getBoolean('session.rememberme.enable', false)
+            && $globalConfig->getOptionalBoolean('session.rememberme.enable', false)
         ) {
             $session->setRememberMeExpire();
             return;
diff --git a/modules/core/lib/Auth/Process/ScopeAttribute.php b/modules/core/lib/Auth/Process/ScopeAttribute.php
index aec51485d558bab25749c58de9c564abf12e0ac4..11241324ab67eff713199622628414970d597a3b 100644
--- a/modules/core/lib/Auth/Process/ScopeAttribute.php
+++ b/modules/core/lib/Auth/Process/ScopeAttribute.php
@@ -60,7 +60,7 @@ class ScopeAttribute extends Auth\ProcessingFilter
         $this->scopeAttribute = $cfg->getString('scopeAttribute');
         $this->sourceAttribute = $cfg->getString('sourceAttribute');
         $this->targetAttribute = $cfg->getString('targetAttribute');
-        $this->onlyIfEmpty = $cfg->getBoolean('onlyIfEmpty', false);
+        $this->onlyIfEmpty = $cfg->getOptionalBoolean('onlyIfEmpty', false);
     }
 
 
diff --git a/modules/core/lib/Auth/Source/AdminPassword.php b/modules/core/lib/Auth/Source/AdminPassword.php
index 0af50a1ed782534d5c814bada7e7c90311135a59..467a798f10fc74dced06d7a11c0d2a5ea5922d94 100644
--- a/modules/core/lib/Auth/Source/AdminPassword.php
+++ b/modules/core/lib/Auth/Source/AdminPassword.php
@@ -50,7 +50,7 @@ class AdminPassword extends UserPassBase
     protected function login(string $username, string $password): array
     {
         $config = Configuration::getInstance();
-        $adminPassword = $config->getString('auth.adminpassword', '123');
+        $adminPassword = $config->getOptionalString('auth.adminpassword', '123');
         if ($adminPassword === '123') {
             // We require that the user changes the password
             throw new Error\Error('NOTSET');
diff --git a/modules/core/lib/Auth/UserPassBase.php b/modules/core/lib/Auth/UserPassBase.php
index 661159d84302c6bdfb155ee483a2da0265b0e828..d596eb638db74312ffeed2102f783f3b67cffac9 100644
--- a/modules/core/lib/Auth/UserPassBase.php
+++ b/modules/core/lib/Auth/UserPassBase.php
@@ -120,8 +120,8 @@ abstract class UserPassBase extends Auth\Source
 
         // get the "remember me" config options
         $sspcnf = Configuration::getInstance();
-        $this->rememberMeEnabled = $sspcnf->getBoolean('session.rememberme.enable', false);
-        $this->rememberMeChecked = $sspcnf->getBoolean('session.rememberme.checked', false);
+        $this->rememberMeEnabled = $sspcnf->getOptionalBoolean('session.rememberme.enable', false);
+        $this->rememberMeChecked = $sspcnf->getOptionalBoolean('session.rememberme.checked', false);
     }
 
 
diff --git a/modules/core/lib/Stats/Output/Log.php b/modules/core/lib/Stats/Output/Log.php
index 58c554b574c555616847965b98b59545b5402ec7..86d740270e804d6c266ec17d432dc9c78e0a974d 100644
--- a/modules/core/lib/Stats/Output/Log.php
+++ b/modules/core/lib/Stats/Output/Log.php
@@ -29,7 +29,7 @@ class Log extends \SimpleSAML\Stats\Output
      */
     public function __construct(Configuration $config)
     {
-        $logLevel = $config->getString('level', 'notice');
+        $logLevel = $config->getOptionalString('level', 'notice');
         $this->logger = [Logger::class, $logLevel];
         if (!is_callable($this->logger)) {
             throw new \Exception('Invalid log level: ' . var_export($logLevel, true));
diff --git a/modules/core/www/idp/logout-iframe-post.php b/modules/core/www/idp/logout-iframe-post.php
index 6dc9c8d35ddf9dbff909f717d1d2d052d0b1d4ac..d20af29fc730946e66356db79aba6132653d97df 100644
--- a/modules/core/www/idp/logout-iframe-post.php
+++ b/modules/core/www/idp/logout-iframe-post.php
@@ -30,15 +30,15 @@ $lr = \SimpleSAML\Module\saml\Message::buildLogoutRequest($idpMetadata, $spMetad
 $lr->setSessionIndex($association['saml:SessionIndex']);
 $lr->setNameId($association['saml:NameID']);
 
-$assertionLifetime = $spMetadata->getInteger('assertion.lifetime', null);
+$assertionLifetime = $spMetadata->getOptionalInteger('assertion.lifetime', null);
 if ($assertionLifetime === null) {
-    $assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300);
+    $assertionLifetime = $idpMetadata->getOptionalInteger('assertion.lifetime', 300);
 }
 $lr->setNotOnOrAfter(time() + $assertionLifetime);
 
-$encryptNameId = $spMetadata->getBoolean('nameid.encryption', null);
+$encryptNameId = $spMetadata->getOptionalBoolean('nameid.encryption', null);
 if ($encryptNameId === null) {
-    $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', false);
+    $encryptNameId = $idpMetadata->getOptionalBoolean('nameid.encryption', false);
 }
 if ($encryptNameId) {
     $lr->encryptNameId(\SimpleSAML\Module\saml\Message::getEncryptionKey($spMetadata));
diff --git a/modules/cron/hooks/hook_cron.php b/modules/cron/hooks/hook_cron.php
index 843d4dd5dc894568456a0c288fc75cab8f4e2b78..c21b3015d23c935779d6fbecefde39182fa42dad 100644
--- a/modules/cron/hooks/hook_cron.php
+++ b/modules/cron/hooks/hook_cron.php
@@ -15,7 +15,7 @@ function cron_hook_cron(array &$croninfo): void
 
     $cronconfig = Configuration::getConfig('module_cron.php');
 
-    if ($cronconfig->getValue('debug_message', true)) {
+    if ($cronconfig->getOptionalBoolean('debug_message', true)) {
         $croninfo['summary'][] = 'Cron did run tag [' . $croninfo['tag'] . '] at ' . date(DATE_RFC822);
     }
 }
diff --git a/modules/cron/lib/Controller/Cron.php b/modules/cron/lib/Controller/Cron.php
index 42e1c62270330db6e971fe2a2a454b5f3cbff2b4..80346cd1217478f1fd27e28dedf45f9143aed345 100644
--- a/modules/cron/lib/Controller/Cron.php
+++ b/modules/cron/lib/Controller/Cron.php
@@ -85,8 +85,8 @@ class Cron
     {
         $this->authUtils->requireAdmin();
 
-        $key = $this->cronconfig->getValue('key', 'secret');
-        $tags = $this->cronconfig->getValue('allowed_tags', []);
+        $key = $this->cronconfig->getOptionalString('key', 'secret');
+        $tags = $this->cronconfig->getOptionalArray('allowed_tags', []);
 
         $def = [
             'weekly' => "22 0 * * 0",
@@ -127,7 +127,7 @@ class Cron
      */
     public function run(string $tag, string $key, string $output = 'xhtml'): Response
     {
-        $configKey = $this->cronconfig->getValue('key', 'secret');
+        $configKey = $this->cronconfig->getOptionalString('key', 'secret');
         if ($key !== $configKey) {
             Logger::error('Cron - Wrong key provided. Cron will not run.');
             exit;
@@ -146,7 +146,7 @@ class Cron
         $croninfo = $cron->runTag($tag);
         $summary = $croninfo['summary'];
 
-        if ($this->cronconfig->getValue('sendemail', true) && count($summary) > 0) {
+        if ($this->cronconfig->getOptionalBoolean('sendemail', true) && count($summary) > 0) {
             $mail = new Utils\EMail('SimpleSAMLphp cron report');
             $mail->setData(['url' => $url, 'tag' => $croninfo['tag'], 'summary' => $croninfo['summary']]);
             try {
diff --git a/modules/multiauth/lib/Auth/Source/MultiAuth.php b/modules/multiauth/lib/Auth/Source/MultiAuth.php
index 8af4fa0372630fdd6028fcb2a1e66594c3772cd7..9b974105e59009dce4ef49ca5f6892c8fc704dc9 100644
--- a/modules/multiauth/lib/Auth/Source/MultiAuth.php
+++ b/modules/multiauth/lib/Auth/Source/MultiAuth.php
@@ -80,7 +80,7 @@ class MultiAuth extends Auth\Source
         }
 
         $globalConfiguration = Configuration::getInstance();
-        $defaultLanguage = $globalConfiguration->getString('language.default', 'en');
+        $defaultLanguage = $globalConfiguration->getOptionalString('language.default', 'en');
         $authsources = Configuration::getConfig('authsources.php');
         $this->sources = [];
 
@@ -108,7 +108,7 @@ class MultiAuth extends Auth\Source
                 $css_class = $info['css-class'];
             } else {
                 // Use the authtype as the css class
-                $authconfig = $authsources->getArray($source, null);
+                $authconfig = $authsources->getOptionalArray($source, null);
                 if (!array_key_exists(0, $authconfig) || !is_string($authconfig[0])) {
                     $css_class = "";
                 } else {
diff --git a/modules/multiauth/lib/Controller/DiscoController.php b/modules/multiauth/lib/Controller/DiscoController.php
index 09f577e9fc99acdb7cd132d183901eff8a0d27ca..57591e81ae9b316382a4d77522ca992b953df6ed 100644
--- a/modules/multiauth/lib/Controller/DiscoController.php
+++ b/modules/multiauth/lib/Controller/DiscoController.php
@@ -129,7 +129,7 @@ class DiscoController
 
         $t = new Template($this->config, 'multiauth:selectsource.twig');
 
-        $defaultLanguage = $this->config->getString('language.default', 'en');
+        $defaultLanguage = $this->config->getOptionalString('language.default', 'en');
         $language = $t->getTranslator()->getLanguage()->getLanguage();
 
         $sources = $state[MultiAuth::SOURCESID];
diff --git a/modules/saml/lib/Auth/Source/SP.php b/modules/saml/lib/Auth/Source/SP.php
index 34986aeecde77d5cdeb65e85466f0d5ff13bfeaa..e17726f5a07115c32faabf6278a880edf1e3bf94 100644
--- a/modules/saml/lib/Auth/Source/SP.php
+++ b/modules/saml/lib/Auth/Source/SP.php
@@ -94,9 +94,9 @@ class SP extends \SimpleSAML\Auth\Source
             'authsources[' . var_export($this->authId, true) . ']'
         );
         $this->entityId = $this->metadata->getString('entityID');
-        $this->idp = $this->metadata->getString('idp', null);
-        $this->discoURL = $this->metadata->getString('discoURL', null);
-        $this->disable_scoping = $this->metadata->getBoolean('disable_scoping', false);
+        $this->idp = $this->metadata->getOptionalString('idp', null);
+        $this->discoURL = $this->metadata->getOptionalString('discoURL', null);
+        $this->disable_scoping = $this->metadata->getOptionalBoolean('disable_scoping', false);
     }
 
 
@@ -141,7 +141,7 @@ class SP extends \SimpleSAML\Auth\Source
         if ($this->metadata->hasValue('NameIDPolicy')) {
             $format = $this->metadata->getValue('NameIDPolicy');
             if (is_array($format)) {
-                $metadata['NameIDFormat'] = Configuration::loadFromArray($format)->getString(
+                $metadata['NameIDFormat'] = Configuration::loadFromArray($format)->getOptionalString(
                     'Format',
                     Constants::NAMEID_TRANSIENT
                 );
@@ -151,8 +151,8 @@ class SP extends \SimpleSAML\Auth\Source
         }
 
         // add attributes
-        $name = $this->metadata->getLocalizedString('name', null);
-        $attributes = $this->metadata->getArray('attributes', []);
+        $name = $this->metadata->getOptionalLocalizedString('name', null);
+        $attributes = $this->metadata->getOptionalArray('attributes', []);
         if ($name !== null) {
             if (!empty($attributes)) {
                 $metadata['name'] = $name;
@@ -176,11 +176,11 @@ class SP extends \SimpleSAML\Auth\Source
         }
 
         // add organization info
-        $org = $this->metadata->getLocalizedString('OrganizationName', null);
+        $org = $this->metadata->getOptionalLocalizedString('OrganizationName', null);
         if ($org !== null) {
             $metadata['OrganizationName'] = $org;
-            $metadata['OrganizationDisplayName'] = $this->metadata->getLocalizedString('OrganizationDisplayName', $org);
-            $metadata['OrganizationURL'] = $this->metadata->getLocalizedString('OrganizationURL', null);
+            $metadata['OrganizationDisplayName'] = $this->metadata->getOptionalLocalizedString('OrganizationDisplayName', $org);
+            $metadata['OrganizationURL'] = $this->metadata->getOptionalLocalizedString('OrganizationURL', null);
             if ($metadata['OrganizationURL'] === null) {
                 throw new Error\Exception(
                     'If OrganizationName is set, OrganizationURL must also be set.'
@@ -189,18 +189,18 @@ class SP extends \SimpleSAML\Auth\Source
         }
 
         // add contacts
-        $contacts = $this->metadata->getArray('contacts', []);
+        $contacts = $this->metadata->getOptionalArray('contacts', []);
         foreach ($contacts as $contact) {
             $metadata['contacts'][] = Utils\Config\Metadata::getContact($contact);
         }
 
         // add technical contact
         $globalConfig = Configuration::getInstance();
-        $email = $globalConfig->getString('technicalcontact_email', 'na@example.org');
-        if ($email && $email !== 'na@example.org') {
+        $email = $globalConfig->getOptionalString('technicalcontact_email', 'na@example.org');
+        if (!empty($email) && $email !== 'na@example.org') {
             $contact = [
                 'emailAddress' => $email,
-                'givenName' => $globalConfig->getString('technicalcontact_name', null),
+                'givenName' => $globalConfig->getOptionalString('technicalcontact_name', null),
                 'contactType' => 'technical',
             ];
             $metadata['contacts'][] = Utils\Config\Metadata::getContact($contact);
@@ -339,11 +339,11 @@ class SP extends \SimpleSAML\Auth\Source
             Constants::BINDING_HTTP_POST,
             Constants::BINDING_HTTP_ARTIFACT,
         ];
-        if ($this->metadata->getString('ProtocolBinding', '') === Constants::BINDING_HOK_SSO) {
+        if ($this->metadata->getOptionalString('ProtocolBinding', null) === Constants::BINDING_HOK_SSO) {
             $default[] = Constants::BINDING_HOK_SSO;
         }
 
-        $bindings = $this->metadata->getArray('acs.Bindings', $default);
+        $bindings = $this->metadata->getOptionalArray('acs.Bindings', $default);
         $index = 0;
         foreach ($bindings as $service) {
             switch ($service) {
@@ -387,10 +387,10 @@ class SP extends \SimpleSAML\Auth\Source
     private function getSLOEndpoints(): array
     {
         $config = Configuration::getInstance();
-        $storeType = $config->getString('store.type', 'phpsession');
+        $storeType = $config->getOptionalString('store.type', 'phpsession');
 
         $store = StoreFactory::getInstance($storeType);
-        $bindings = $this->metadata->getArray(
+        $bindings = $this->metadata->getOptionalArray(
             'SingleLogoutServiceBinding',
             [
                 Constants::BINDING_HTTP_REDIRECT,
@@ -398,7 +398,7 @@ class SP extends \SimpleSAML\Auth\Source
             ]
         );
         $defaultLocation = Module::getModuleURL('saml/sp/saml2-logout.php/' . $this->getAuthId());
-        $location = $this->metadata->getString('SingleLogoutServiceLocation', $defaultLocation);
+        $location = $this->metadata->getOptionalString('SingleLogoutServiceLocation', $defaultLocation);
 
         $endpoints = [];
         foreach ($bindings as $binding) {
@@ -441,7 +441,7 @@ class SP extends \SimpleSAML\Auth\Source
         $arrayUtils = new Utils\Arrays();
 
         $accr = null;
-        if ($idpMetadata->getString('AuthnContextClassRef', false)) {
+        if ($idpMetadata->getOptionalString('AuthnContextClassRef', null) !== null) {
             $accr = $arrayUtils->arrayize($idpMetadata->getString('AuthnContextClassRef'));
         } elseif (isset($state['saml:AuthnContextClassRef'])) {
             $accr = $arrayUtils->arrayize($state['saml:AuthnContextClassRef']);
@@ -449,7 +449,7 @@ class SP extends \SimpleSAML\Auth\Source
 
         if ($accr !== null) {
             $comp = Constants::COMPARISON_EXACT;
-            if ($idpMetadata->getString('AuthnContextComparison', false)) {
+            if ($idpMetadata->getOptionalString('AuthnContextComparison', null) !== null) {
                 $comp = $idpMetadata->getString('AuthnContextComparison');
             } elseif (
                 isset($state['saml:AuthnContextComparison'])
@@ -532,17 +532,17 @@ class SP extends \SimpleSAML\Auth\Source
         $requesterID = [];
 
         /* Only check for real info for Scoping element if we are going to send Scoping element */
-        if ($this->disable_scoping !== true && $idpMetadata->getBoolean('disable_scoping', false) !== true) {
+        if ($this->disable_scoping !== true && $idpMetadata->getOptionalBoolean('disable_scoping', false) !== true) {
             if (isset($state['saml:IDPList'])) {
                 $IDPList = $state['saml:IDPList'];
             }
 
             if (isset($state['saml:ProxyCount']) && $state['saml:ProxyCount'] !== null) {
                 $ar->setProxyCount($state['saml:ProxyCount']);
-            } elseif ($idpMetadata->getInteger('ProxyCount', null) !== null) {
-                $ar->setProxyCount($idpMetadata->getInteger('ProxyCount', null));
-            } elseif ($this->metadata->getInteger('ProxyCount', null) !== null) {
-                $ar->setProxyCount($this->metadata->getInteger('ProxyCount', null));
+            } elseif ($idpMetadata->hasValue('ProxyCount')) {
+                $ar->setProxyCount($idpMetadata->getInteger('ProxyCount'));
+            } elseif ($this->metadata->hasValue('ProxyCount')) {
+                $ar->setProxyCount($this->metadata->getInteger('ProxyCount'));
             }
 
             $requesterID = [];
@@ -560,8 +560,8 @@ class SP extends \SimpleSAML\Auth\Source
         $ar->setIDPList(
             array_unique(
                 array_merge(
-                    $this->metadata->getArray('IDPList', []),
-                    $idpMetadata->getArray('IDPList', []),
+                    $this->metadata->getOptionalArray('IDPList', []),
+                    $idpMetadata->getOptionalArray('IDPList', []),
                     (array) $IDPList
                 )
             )
@@ -573,11 +573,11 @@ class SP extends \SimpleSAML\Auth\Source
         // Otherwise use extensions that might be defined in the local SP (only makes sense in a proxy scenario)
         if (isset($state['saml:Extensions']) && count($state['saml:Extensions']) > 0) {
             $ar->setExtensions($state['saml:Extensions']);
-        } elseif ($this->metadata->getArray('saml:Extensions', null) !== null) {
+        } elseif ($this->metadata->getOptionalArray('saml:Extensions', null) !== null) {
             $ar->setExtensions($this->metadata->getArray('saml:Extensions'));
         }
 
-        $providerName = $this->metadata->getString("ProviderName", null);
+        $providerName = $this->metadata->getOptionalString("ProviderName", null);
         if ($providerName !== null) {
             $ar->setProviderName($providerName);
         }
@@ -990,13 +990,13 @@ class SP extends \SimpleSAML\Auth\Source
 
         if (isset($state['saml:logout:Extensions']) && count($state['saml:logout:Extensions']) > 0) {
             $lr->setExtensions($state['saml:logout:Extensions']);
-        } elseif ($this->metadata->getArray('saml:logout:Extensions', null) !== null) {
+        } elseif ($this->metadata->getOptionalArray('saml:logout:Extensions', null) !== null) {
             $lr->setExtensions($this->metadata->getArray('saml:logout:Extensions'));
         }
 
-        $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', null);
+        $encryptNameId = $idpMetadata->getOptionalBoolean('nameid.encryption', null);
         if ($encryptNameId === null) {
-            $encryptNameId = $this->metadata->getBoolean('nameid.encryption', false);
+            $encryptNameId = $this->metadata->getOptionalBoolean('nameid.encryption', false);
         }
         if ($encryptNameId) {
             $lr->encryptNameId(Module\saml\Message::getEncryptionKey($idpMetadata));
@@ -1141,7 +1141,7 @@ class SP extends \SimpleSAML\Auth\Source
             if (!empty($state['saml:sp:RelayState'])) {
                 $redirectTo = $state['saml:sp:RelayState'];
             } else {
-                $redirectTo = $source->getMetadata()->getString('RelayState', '/');
+                $redirectTo = $source->getMetadata()->getOptionalString('RelayState', '/');
             }
 
             self::handleUnsolicitedAuth($sourceId, $state, $redirectTo);
diff --git a/modules/saml/lib/IdP/SAML2.php b/modules/saml/lib/IdP/SAML2.php
index f5bbb817d54ef0ace446afa37c9c529a25ee1bde..da86d9c1c78a12b94d24b4380dc9819aebfac538 100644
--- a/modules/saml/lib/IdP/SAML2.php
+++ b/modules/saml/lib/IdP/SAML2.php
@@ -255,7 +255,7 @@ class SAML2
 
         $skipEndpointValidation = false;
         if ($authnRequestSigned === true) {
-            $skipEndpointValidationWhenSigned = $spMetadata->getValue('skipEndpointValidationWhenSigned', false);
+            $skipEndpointValidationWhenSigned = $spMetadata->getOptionalValue('skipEndpointValidationWhenSigned', false);
             if (is_bool($skipEndpointValidationWhenSigned) === true) {
                 $skipEndpointValidation = $skipEndpointValidationWhenSigned;
             } elseif (is_callable($skipEndpointValidationWhenSigned) === true) {
@@ -305,13 +305,13 @@ class SAML2
         $httpUtils = new Utils\HTTP();
 
         $supportedBindings = [Constants::BINDING_HTTP_POST];
-        if ($idpMetadata->getBoolean('saml20.sendartifact', false)) {
+        if ($idpMetadata->getOptionalBoolean('saml20.sendartifact', false)) {
             $supportedBindings[] = Constants::BINDING_HTTP_ARTIFACT;
         }
-        if ($idpMetadata->getBoolean('saml20.hok.assertion', false)) {
+        if ($idpMetadata->getOptionalBoolean('saml20.hok.assertion', false)) {
             $supportedBindings[] = Constants::BINDING_HOK_SSO;
         }
-        if ($idpMetadata->getBoolean('saml20.ecp', false)) {
+        if ($idpMetadata->getOptionalBoolean('saml20.ecp', false)) {
             $supportedBindings[] = Constants::BINDING_PAOS;
         }
 
@@ -456,13 +456,13 @@ class SAML2
             throw new Exception('Unable to use any of the ACS endpoints found for SP \'' . $spEntityId . '\'');
         }
 
-        $IDPList = array_unique(array_merge($IDPList, $spMetadata->getArrayizeString('IDPList', [])));
+        $IDPList = array_unique(array_merge($IDPList, $spMetadata->getOptionalArrayizeString('IDPList', [])));
         if ($ProxyCount === null) {
-            $ProxyCount = $spMetadata->getInteger('ProxyCount', null);
+            $ProxyCount = $spMetadata->getOptionalInteger('ProxyCount', null);
         }
 
         if (!$forceAuthn) {
-            $forceAuthn = $spMetadata->getBoolean('ForceAuthn', false);
+            $forceAuthn = $spMetadata->getOptionalBoolean('ForceAuthn', false);
         }
 
         $sessionLostParams = [
@@ -662,7 +662,7 @@ class SAML2
                 'idpEntityID' => $idpMetadata->getString('entityid'),
             ]);
 
-            $spStatsId = $spMetadata->getString('core:statistics-id', $spEntityId);
+            $spStatsId = $spMetadata->getOptionalString('core:statistics-id', $spEntityId);
             Logger::stats('saml20-idp-SLO spinit ' . $spStatsId . ' ' . $idpMetadata->getString('entityid'));
 
             $state = [
@@ -796,7 +796,7 @@ class SAML2
             'entityid' => $entityid,
             'SingleSignOnService' => $sso,
             'SingleLogoutService' => $slo,
-            'NameIDFormat' => $config->getArrayizeString('NameIDFormat', [Constants::NAMEID_TRANSIENT]),
+            'NameIDFormat' => $config->getOptionalArrayizeString('NameIDFormat', [Constants::NAMEID_TRANSIENT]),
         ];
 
         $cryptoUtils = new Utils\Crypto();
@@ -844,7 +844,7 @@ class SAML2
         $metadata['keys'] = $keys;
 
         // add ArtifactResolutionService endpoint, if enabled
-        if ($config->getBoolean('saml20.sendartifact', false)) {
+        if ($config->getOptionalBoolean('saml20.sendartifact', false)) {
             $metadata['ArtifactResolutionService'][] = [
                 'index' => 0,
                 'Binding' => Constants::BINDING_SOAP,
@@ -853,7 +853,7 @@ class SAML2
         }
 
         // add Holder of Key, if enabled
-        if ($config->getBoolean('saml20.hok.assertion', false)) {
+        if ($config->getOptionalBoolean('saml20.hok.assertion', false)) {
             array_unshift(
                 $metadata['SingleSignOnService'],
                 [
@@ -865,7 +865,7 @@ class SAML2
         }
 
         // add ECP profile, if enabled
-        if ($config->getBoolean('saml20.ecp', false)) {
+        if ($config->getOptionalBoolean('saml20.ecp', false)) {
             $metadata['SingleSignOnService'][] = [
                 'index' => 0,
                 'Binding' => Constants::BINDING_SOAP,
@@ -876,7 +876,7 @@ class SAML2
         // add organization information
         if ($config->hasValue('OrganizationName')) {
             $metadata['OrganizationName'] = $config->getLocalizedString('OrganizationName');
-            $metadata['OrganizationDisplayName'] = $config->getLocalizedString(
+            $metadata['OrganizationDisplayName'] = $config->getOptionalLocalizedString(
                 'OrganizationDisplayName',
                 $metadata['OrganizationName']
             );
@@ -937,11 +937,11 @@ class SAML2
         }
 
         $globalConfig = Configuration::getInstance();
-        $email = $globalConfig->getString('technicalcontact_email', false);
-        if ($email && $email !== 'na@example.org') {
+        $email = $globalConfig->getOptionalString('technicalcontact_email', 'na@example.org');
+        if (!empty($email) && $email !== 'na@example.org') {
             $contact = [
                 'emailAddress' => $email,
-                'givenName' => $globalConfig->getString('technicalcontact_name', null),
+                'givenName' => $globalConfig->getOptionalString('technicalcontact_name', null),
                 'contactType' => 'technical',
             ];
             $metadata['contacts'][] = Utils\Config\Metadata::getContact($contact);
@@ -965,9 +965,9 @@ class SAML2
         Configuration $spMetadata,
         array &$state
     ): ?string {
-        $attribute = $spMetadata->getString('simplesaml.nameidattribute', null);
+        $attribute = $spMetadata->getOptionalString('simplesaml.nameidattribute', null);
         if ($attribute === null) {
-            $attribute = $idpMetadata->getString('simplesaml.nameidattribute', null);
+            $attribute = $idpMetadata->getOptionalString('simplesaml.nameidattribute', null);
             if ($attribute === null) {
                 Logger::error('Unable to generate NameID. Check the simplesaml.nameidattribute option.');
                 return null;
@@ -1001,9 +1001,9 @@ class SAML2
         Configuration $spMetadata,
         array $attributes
     ): array {
-        $base64Attributes = $spMetadata->getBoolean('base64attributes', null);
+        $base64Attributes = $spMetadata->getOptionalBoolean('base64attributes', null);
         if ($base64Attributes === null) {
-            $base64Attributes = $idpMetadata->getBoolean('base64attributes', false);
+            $base64Attributes = $idpMetadata->getOptionalBoolean('base64attributes', false);
         }
 
         if ($base64Attributes) {
@@ -1012,8 +1012,8 @@ class SAML2
             $defaultEncoding = 'string';
         }
 
-        $srcEncodings = $idpMetadata->getArray('attributeencodings', []);
-        $dstEncodings = $spMetadata->getArray('attributeencodings', []);
+        $srcEncodings = $idpMetadata->getOptionalArray('attributeencodings', []);
+        $dstEncodings = $spMetadata->getOptionalArray('attributeencodings', []);
 
         /*
          * Merge the two encoding arrays. Encodings specified in the target metadata
@@ -1083,21 +1083,21 @@ class SAML2
         Configuration $spMetadata
     ): string {
         // try SP metadata first
-        $attributeNameFormat = $spMetadata->getString('attributes.NameFormat', null);
+        $attributeNameFormat = $spMetadata->getOptionalString('attributes.NameFormat', null);
         if ($attributeNameFormat !== null) {
             return $attributeNameFormat;
         }
-        $attributeNameFormat = $spMetadata->getString('AttributeNameFormat', null);
+        $attributeNameFormat = $spMetadata->getOptionalString('AttributeNameFormat', null);
         if ($attributeNameFormat !== null) {
             return $attributeNameFormat;
         }
 
         // look in IdP metadata
-        $attributeNameFormat = $idpMetadata->getString('attributes.NameFormat', null);
+        $attributeNameFormat = $idpMetadata->getOptionalString('attributes.NameFormat', null);
         if ($attributeNameFormat !== null) {
             return $attributeNameFormat;
         }
-        $attributeNameFormat = $idpMetadata->getString('AttributeNameFormat', null);
+        $attributeNameFormat = $idpMetadata->getOptionalString('AttributeNameFormat', null);
         if ($attributeNameFormat !== null) {
             return $attributeNameFormat;
         }
@@ -1129,9 +1129,9 @@ class SAML2
         $httpUtils = new Utils\HTTP();
         $now = time();
 
-        $signAssertion = $spMetadata->getBoolean('saml20.sign.assertion', null);
+        $signAssertion = $spMetadata->getOptionalBoolean('saml20.sign.assertion', null);
         if ($signAssertion === null) {
-            $signAssertion = $idpMetadata->getBoolean('saml20.sign.assertion', true);
+            $signAssertion = $idpMetadata->getOptionalBoolean('saml20.sign.assertion', true);
         }
 
         $config = Configuration::getInstance();
@@ -1146,14 +1146,14 @@ class SAML2
         $issuer->setFormat(Constants::NAMEID_ENTITY);
         $a->setIssuer($issuer);
 
-        $audience = array_merge([$spMetadata->getString('entityid')], $spMetadata->getArray('audience', []));
+        $audience = array_merge([$spMetadata->getString('entityid')], $spMetadata->getOptionalArray('audience', []));
         $a->setValidAudiences($audience);
 
         $a->setNotBefore($now - 30);
 
-        $assertionLifetime = $spMetadata->getInteger('assertion.lifetime', null);
+        $assertionLifetime = $spMetadata->getOptionalInteger('assertion.lifetime', null);
         if ($assertionLifetime === null) {
-            $assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300);
+            $assertionLifetime = $idpMetadata->getOptionalInteger('assertion.lifetime', 300);
         }
         $a->setNotOnOrAfter($now + $assertionLifetime);
 
@@ -1171,7 +1171,7 @@ class SAML2
             $sessionStart = $state['AuthnInstant'];
         }
 
-        $sessionLifetime = $config->getInteger('session.duration', 8 * 60 * 60);
+        $sessionLifetime = $config->getOptionalInteger('session.duration', 8 * 60 * 60);
         $a->setSessionNotOnOrAfter($sessionStart + $sessionLifetime);
 
         $randomUtils = new Utils\Random();
@@ -1190,7 +1190,7 @@ class SAML2
             $hokAssertion = true;
         }
         if ($hokAssertion === null) {
-            $hokAssertion = $idpMetadata->getBoolean('saml20.hok.assertion', false);
+            $hokAssertion = $idpMetadata->getOptionalBoolean('saml20.hok.assertion', false);
         }
 
         if ($hokAssertion) {
@@ -1238,7 +1238,7 @@ class SAML2
         $a->setSubjectConfirmation([$sc]);
 
         // add attributes
-        if ($spMetadata->getBoolean('simplesaml.attributes', true)) {
+        if ($spMetadata->getOptionalBoolean('simplesaml.attributes', true)) {
             $attributeNameFormat = self::getAttributeNameFormat($idpMetadata, $spMetadata);
             $a->setAttributeNameFormat($attributeNameFormat);
             $attributes = self::encodeAttributes($idpMetadata, $spMetadata, $state['Attributes']);
@@ -1254,9 +1254,11 @@ class SAML2
 
         if ($nameIdFormat === null || !isset($state['saml:NameID'][$nameIdFormat])) {
             // either not set in request, or not set to a format we supply. Fall back to old generation method
-            $nameIdFormat = current($spMetadata->getArrayizeString('NameIDFormat', []));
+            $nameIdFormat = current($spMetadata->getOptionalArrayizeString('NameIDFormat', []));
             if ($nameIdFormat === false) {
-                $nameIdFormat = current($idpMetadata->getArrayizeString('NameIDFormat', [Constants::NAMEID_TRANSIENT]));
+                $nameIdFormat = current(
+                    $idpMetadata->getOptionalArrayizeString('NameIDFormat', [Constants::NAMEID_TRANSIENT])
+                );
             }
         }
 
@@ -1264,7 +1266,7 @@ class SAML2
             $nameId = $state['saml:NameID'][$nameIdFormat];
             $nameId->setFormat($nameIdFormat);
         } else {
-            $spNameQualifier = $spMetadata->getString('SPNameQualifier', null);
+            $spNameQualifier = $spMetadata->getOptionalString('SPNameQualifier', null);
             if ($spNameQualifier === null) {
                 $spNameQualifier = $spMetadata->getString('entityid');
             }
@@ -1293,9 +1295,9 @@ class SAML2
 
         $a->setNameId($nameId);
 
-        $encryptNameId = $spMetadata->getBoolean('nameid.encryption', null);
+        $encryptNameId = $spMetadata->getOptionalBoolean('nameid.encryption', null);
         if ($encryptNameId === null) {
-            $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', false);
+            $encryptNameId = $idpMetadata->getOptionalBoolean('nameid.encryption', false);
         }
         if ($encryptNameId) {
             $a->encryptNameId(\SimpleSAML\Module\saml\Message::getEncryptionKey($spMetadata));
@@ -1324,9 +1326,9 @@ class SAML2
         Configuration $spMetadata,
         Assertion $assertion
     ) {
-        $encryptAssertion = $spMetadata->getBoolean('assertion.encryption', null);
+        $encryptAssertion = $spMetadata->getOptionalBoolean('assertion.encryption', null);
         if ($encryptAssertion === null) {
-            $encryptAssertion = $idpMetadata->getBoolean('assertion.encryption', false);
+            $encryptAssertion = $idpMetadata->getOptionalBoolean('assertion.encryption', false);
         }
         if (!$encryptAssertion) {
             // we are _not_ encrypting this assertion, and are therefore done
@@ -1334,12 +1336,12 @@ class SAML2
         }
 
 
-        $sharedKey = $spMetadata->getString('sharedkey', null);
+        $sharedKey = $spMetadata->getOptionalString('sharedkey', null);
         if ($sharedKey !== null) {
-            $algo = $spMetadata->getString('sharedkey_algorithm', null);
+            $algo = $spMetadata->getOptionalString('sharedkey_algorithm', null);
             if ($algo === null) {
                 // If no algorithm is configured, use a sane default
-                $algo = $idpMetadata->getString('sharedkey_algorithm', XMLSecurityKey::AES128_GCM);
+                $algo = $idpMetadata->getOptionalString('sharedkey_algorithm', XMLSecurityKey::AES128_GCM);
             }
 
             $key = new XMLSecurityKey($algo);
@@ -1397,15 +1399,15 @@ class SAML2
         $lr->setSessionIndex($association['saml:SessionIndex']);
         $lr->setNameId($association['saml:NameID']);
 
-        $assertionLifetime = $spMetadata->getInteger('assertion.lifetime', null);
+        $assertionLifetime = $spMetadata->getOptionalInteger('assertion.lifetime', null);
         if ($assertionLifetime === null) {
-            $assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300);
+            $assertionLifetime = $idpMetadata->getOptionalInteger('assertion.lifetime', 300);
         }
         $lr->setNotOnOrAfter(time() + $assertionLifetime);
 
-        $encryptNameId = $spMetadata->getBoolean('nameid.encryption', null);
+        $encryptNameId = $spMetadata->getOptionalBoolean('nameid.encryption', null);
         if ($encryptNameId === null) {
-            $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', false);
+            $encryptNameId = $idpMetadata->getOptionalBoolean('nameid.encryption', false);
         }
         if ($encryptNameId) {
             $lr->encryptNameId(\SimpleSAML\Module\saml\Message::getEncryptionKey($spMetadata));
@@ -1429,9 +1431,9 @@ class SAML2
         Configuration $spMetadata,
         string $consumerURL
     ): Response {
-        $signResponse = $spMetadata->getBoolean('saml20.sign.response', null);
+        $signResponse = $spMetadata->getOptionalBoolean('saml20.sign.response', null);
         if ($signResponse === null) {
-            $signResponse = $idpMetadata->getBoolean('saml20.sign.response', true);
+            $signResponse = $idpMetadata->getOptionalBoolean('saml20.sign.response', true);
         }
 
         $r = new Response();
diff --git a/modules/saml/lib/IdP/SQLNameID.php b/modules/saml/lib/IdP/SQLNameID.php
index b0dda950c285e032be7d928d63e59545f1b5ef33..24e2e0f85c49d2c15865f3e67955bc911666ed0c 100644
--- a/modules/saml/lib/IdP/SQLNameID.php
+++ b/modules/saml/lib/IdP/SQLNameID.php
@@ -159,7 +159,7 @@ class SQLNameID
     private static function getStore(): Store\SQLStore
     {
         $config = Configuration::getInstance();
-        $storeType = $config->getString('store.type', 'phpsession');
+        $storeType = $config->getOptionalString('store.type', 'phpsession');
 
         $store = StoreFactory::getInstance($storeType);
         Assert::isInstanceOf(
diff --git a/modules/saml/lib/Message.php b/modules/saml/lib/Message.php
index d69935c5cc92be7759a0701d66ea1c3998a035ef..32108fc3eaed8088b9c3b3c79ae915efdf521054 100644
--- a/modules/saml/lib/Message.php
+++ b/modules/saml/lib/Message.php
@@ -43,7 +43,7 @@ class Message
         Configuration $dstMetadata,
         SignedElement $element
     ): void {
-        $dstPrivateKey = $dstMetadata->getString('signature.privatekey', null);
+        $dstPrivateKey = $dstMetadata->getOptionalString('signature.privatekey', null);
         $cryptoUtils = new Utils\Crypto();
 
         if ($dstPrivateKey !== null) {
@@ -56,9 +56,9 @@ class Message
             $certArray = $cryptoUtils->loadPublicKey($srcMetadata, false);
         }
 
-        $algo = $dstMetadata->getString('signature.algorithm', null);
+        $algo = $dstMetadata->getOptionalString('signature.algorithm', null);
         if ($algo === null) {
-            $algo = $srcMetadata->getString('signature.algorithm', XMLSecurityKey::RSA_SHA256);
+            $algo = $srcMetadata->getOptionalString('signature.algorithm', XMLSecurityKey::RSA_SHA256);
         }
 
         $privateKey = new XMLSecurityKey($algo, ['type' => 'private']);
@@ -97,21 +97,21 @@ class Message
     ): void {
         $signingEnabled = null;
         if ($message instanceof LogoutRequest || $message instanceof LogoutResponse) {
-            $signingEnabled = $srcMetadata->getBoolean('sign.logout', null);
+            $signingEnabled = $srcMetadata->getOptionalBoolean('sign.logout', null);
             if ($signingEnabled === null) {
-                $signingEnabled = $dstMetadata->getBoolean('sign.logout', null);
+                $signingEnabled = $dstMetadata->getOptionalBoolean('sign.logout', null);
             }
         } elseif ($message instanceof AuthnRequest) {
-            $signingEnabled = $srcMetadata->getBoolean('sign.authnrequest', null);
+            $signingEnabled = $srcMetadata->getOptionalBoolean('sign.authnrequest', null);
             if ($signingEnabled === null) {
-                $signingEnabled = $dstMetadata->getBoolean('sign.authnrequest', null);
+                $signingEnabled = $dstMetadata->getOptionalBoolean('sign.authnrequest', null);
             }
         }
 
         if ($signingEnabled === null) {
-            $signingEnabled = $dstMetadata->getBoolean('redirect.sign', null);
+            $signingEnabled = $dstMetadata->getOptionalBoolean('redirect.sign', null);
             if ($signingEnabled === null) {
-                $signingEnabled = $srcMetadata->getBoolean('redirect.sign', false);
+                $signingEnabled = $srcMetadata->getOptionalBoolean('redirect.sign', false);
             }
         }
         if (!$signingEnabled) {
@@ -203,14 +203,14 @@ class Message
     ): bool {
         $enabled = null;
         if ($message instanceof LogoutRequest || $message instanceof LogoutResponse) {
-            $enabled = $srcMetadata->getBoolean('validate.logout', null);
+            $enabled = $srcMetadata->getOptionalBoolean('validate.logout', null);
             if ($enabled === null) {
-                $enabled = $dstMetadata->getBoolean('validate.logout', null);
+                $enabled = $dstMetadata->getOptionalBoolean('validate.logout', null);
             }
         } elseif ($message instanceof AuthnRequest) {
-            $enabled = $srcMetadata->getBoolean('validate.authnrequest', null);
+            $enabled = $srcMetadata->getOptionalBoolean('validate.authnrequest', null);
             if ($enabled === null) {
-                $enabled = $dstMetadata->getBoolean('validate.authnrequest', null);
+                $enabled = $dstMetadata->getOptionalBoolean('validate.authnrequest', null);
             }
         }
 
@@ -222,9 +222,9 @@ class Message
         ) {
             $enabled = true;
         } elseif ($enabled === null) {
-            $enabled = $srcMetadata->getBoolean('redirect.validate', null);
+            $enabled = $srcMetadata->getOptionalBoolean('redirect.validate', null);
             if ($enabled === null) {
-                $enabled = $dstMetadata->getBoolean('redirect.validate', false);
+                $enabled = $dstMetadata->getOptionalBoolean('redirect.validate', false);
             }
         }
 
@@ -254,15 +254,15 @@ class Message
         Configuration $dstMetadata,
         $encryptionMethod = null
     ): array {
-        $sharedKey = $srcMetadata->getString('sharedkey', null);
+        $sharedKey = $srcMetadata->getOptionalString('sharedkey', null);
         if ($sharedKey !== null) {
             if ($encryptionMethod !== null) {
                 $algo = $encryptionMethod->getAlgorithm();
             } else {
-                $algo = $srcMetadata->getString('sharedkey_algorithm', null);
+                $algo = $srcMetadata->getOptionalString('sharedkey_algorithm', null);
                 if ($algo === null) {
                     // If no algorithm is supplied or configured, use a sane default as a last resort
-                    $algo = $dstMetadata->getString('sharedkey_algorithm', XMLSecurityKey::AES128_GCM);
+                    $algo = $dstMetadata->getOptionalString('sharedkey_algorithm', XMLSecurityKey::AES128_GCM);
                 }
             }
 
@@ -320,9 +320,9 @@ class Message
         Configuration $srcMetadata,
         Configuration $dstMetadata
     ): array {
-        $blacklist = $srcMetadata->getArray('encryption.blacklisted-algorithms', null);
+        $blacklist = $srcMetadata->getOptionalArray('encryption.blacklisted-algorithms', null);
         if ($blacklist === null) {
-            $blacklist = $dstMetadata->getArray('encryption.blacklisted-algorithms', [XMLSecurityKey::RSA_1_5]);
+            $blacklist = $dstMetadata->getOptionalArray('encryption.blacklisted-algorithms', [XMLSecurityKey::RSA_1_5]);
         }
         return $blacklist;
     }
@@ -349,9 +349,9 @@ class Message
         Assert::isInstanceOfAny($assertion, [Assertion::class, EncryptedAssertion::class]);
 
         if ($assertion instanceof Assertion) {
-            $encryptAssertion = $srcMetadata->getBoolean('assertion.encryption', null);
+            $encryptAssertion = $srcMetadata->getOptionalBoolean('assertion.encryption', null);
             if ($encryptAssertion === null) {
-                $encryptAssertion = $dstMetadata->getBoolean('assertion.encryption', false);
+                $encryptAssertion = $dstMetadata->getOptionalBoolean('assertion.encryption', false);
             }
             if ($encryptAssertion) {
                 /* The assertion was unencrypted, but we have encryption enabled. */
@@ -480,10 +480,10 @@ class Message
             $ar->setNameIdPolicy($policy);
         }
 
-        $ar->setForceAuthn($spMetadata->getBoolean('ForceAuthn', false));
-        $ar->setIsPassive($spMetadata->getBoolean('IsPassive', false));
+        $ar->setForceAuthn($spMetadata->getOptionalBoolean('ForceAuthn', false));
+        $ar->setIsPassive($spMetadata->getOptionalBoolean('IsPassive', false));
 
-        $protbind = $spMetadata->getValueValidate('ProtocolBinding', [
+        $protbind = $spMetadata->getOptionalValueValidate('ProtocolBinding', [
             Constants::BINDING_HTTP_POST,
             Constants::BINDING_HOK_SSO,
             Constants::BINDING_HTTP_ARTIFACT,
@@ -495,12 +495,16 @@ class Message
         $issuer = new Issuer();
         $issuer->setValue($spMetadata->getString('entityid'));
         $ar->setIssuer($issuer);
-        $ar->setAssertionConsumerServiceIndex($spMetadata->getInteger('AssertionConsumerServiceIndex', null));
-        $ar->setAttributeConsumingServiceIndex($spMetadata->getInteger('AttributeConsumingServiceIndex', null));
+        $ar->setAssertionConsumerServiceIndex(
+            $spMetadata->getOptionalInteger('AssertionConsumerServiceIndex', null)
+        );
+        $ar->setAttributeConsumingServiceIndex(
+            $spMetadata->getOptionalInteger('AttributeConsumingServiceIndex', null)
+        );
 
         if ($spMetadata->hasValue('AuthnContextClassRef')) {
             $accr = $spMetadata->getArrayizeString('AuthnContextClassRef');
-            $comp = $spMetadata->getValueValidate('AuthnContextComparison', [
+            $comp = $spMetadata->getOptionalValueValidate('AuthnContextComparison', [
                 Constants::COMPARISON_EXACT,
                 Constants::COMPARISON_MINIMUM,
                 Constants::COMPARISON_MAXIMUM,
@@ -652,7 +656,7 @@ class Message
 
         // check various properties of the assertion
         $config = Configuration::getInstance();
-        $allowed_clock_skew = $config->getInteger('assertion.allowed_clock_skew', 180);
+        $allowed_clock_skew = $config->getOptionalInteger('assertion.allowed_clock_skew', 180);
         $options = [
             'options' => [
                 'default' => 180,
@@ -702,9 +706,9 @@ class Message
             }
 
             // is SSO with HoK enabled? IdP remote metadata overwrites SP metadata configuration
-            $hok = $idpMetadata->getBoolean('saml20.hok.assertion', null);
+            $hok = $idpMetadata->getOptionalBoolean('saml20.hok.assertion', null);
             if ($hok === null) {
-                $hok = $spMetadata->getBoolean('saml20.hok.assertion', false);
+                $hok = $spMetadata->getOptionalBoolean('saml20.hok.assertion', false);
             }
             if ($method === Constants::CM_BEARER && $hok) {
                 $lastError = 'Bearer SubjectConfirmation received, but Holder-of-Key SubjectConfirmation needed';
@@ -824,7 +828,7 @@ class Message
         // as far as we can tell, the assertion is valid
 
         // maybe we need to base64 decode the attributes in the assertion?
-        if ($idpMetadata->getBoolean('base64attributes', false)) {
+        if ($idpMetadata->getOptionalBoolean('base64attributes', false)) {
             $attributes = $assertion->getAttributes();
             $newAttributes = [];
             foreach ($attributes as $name => $values) {
@@ -880,7 +884,7 @@ class Message
      */
     public static function getEncryptionKey(Configuration $metadata): XMLSecurityKey
     {
-        $sharedKey = $metadata->getString('sharedkey', null);
+        $sharedKey = $metadata->getOptionalString('sharedkey', null);
         if ($sharedKey !== null) {
             $key = new XMLSecurityKey(XMLSecurityKey::AES128_CBC);
             $key->loadKey($sharedKey);
diff --git a/modules/saml/lib/SP/LogoutStore.php b/modules/saml/lib/SP/LogoutStore.php
index 6911e0eb70b452dafc8fd48c1c592e50408cce50..09f46cc1fc73231c9466e7da566870e79564f4e0 100644
--- a/modules/saml/lib/SP/LogoutStore.php
+++ b/modules/saml/lib/SP/LogoutStore.php
@@ -214,7 +214,7 @@ class LogoutStore
         }
 
         $config = Configuration::getInstance();
-        $storeType = $config->getString('store.type', 'phpsession');
+        $storeType = $config->getOptionalString('store.type', 'phpsession');
 
         $store = StoreFactory::getInstance($storeType);
         if ($store === false) {
@@ -253,7 +253,7 @@ class LogoutStore
     public static function logoutSessions(string $authId, NameID $nameId, array $sessionIndexes)
     {
         $config = Configuration::getInstance();
-        $storeType = $config->getString('store.type', 'phpsession');
+        $storeType = $config->getOptionalString('store.type', 'phpsession');
 
         $store = StoreFactory::getInstance($storeType);
         if ($store === false) {
diff --git a/modules/saml/www/sp/metadata.php b/modules/saml/www/sp/metadata.php
index f330dd7c9266e7219e4f021bc1d8fec238acb817..4992710fbee9066de45a92f2ad0318876df0641b 100644
--- a/modules/saml/www/sp/metadata.php
+++ b/modules/saml/www/sp/metadata.php
@@ -13,7 +13,7 @@ if (!array_key_exists('PATH_INFO', $_SERVER)) {
 }
 
 $config = Configuration::getInstance();
-if ($config->getBoolean('admin.protectmetadata', false)) {
+if ($config->getOptionalBoolean('admin.protectmetadata', false)) {
     $authUtils = new Utils\Auth();
     $authUtils->requireAdmin();
 }
@@ -34,7 +34,7 @@ $entityId = $source->getEntityId();
 $spconfig = $source->getMetadata();
 $metaArray20 = $source->getHostedMetadata();
 
-$storeType = $config->getString('store.type', 'phpsession');
+$storeType = $config->getOptionalString('store.type', 'phpsession');
 $store = StoreFactory::getInstance($storeType);
 
 $metaBuilder = new Metadata\SAMLBuilder($entityId);
diff --git a/modules/saml/www/sp/saml2-acs.php b/modules/saml/www/sp/saml2-acs.php
index 3da83aa49567b33b6132e80e5e222e61f68a53ce..091a657813690d08647599358bf8d24d0a89cc06 100644
--- a/modules/saml/www/sp/saml2-acs.php
+++ b/modules/saml/www/sp/saml2-acs.php
@@ -101,7 +101,7 @@ if (!empty($stateId)) {
     }
 }
 
-$enableUnsolicited = $spMetadata->getBoolean('enable_unsolicited', true);
+$enableUnsolicited = $spMetadata->getOptionalBoolean('enable_unsolicited', true);
 if ($state === null && $enableUnsolicited === false) {
     throw new Error\BadRequest('Unsolicited responses are denied by configuration.');
 }
@@ -119,7 +119,7 @@ if ($state) {
     Assert::keyExists($state, 'ExpectedIssuer');
     if ($state['ExpectedIssuer'] !== $issuer) {
         $idpMetadata = $source->getIdPMetadata($issuer);
-        $idplist = $idpMetadata->getArrayize('IDPList', []);
+        $idplist = $idpMetadata->getOptionalArrayize('IDPList', []);
         if (!in_array($state['ExpectedIssuer'], $idplist, true)) {
             Logger::warning(
                 'The issuer of the response not match to the identity provider we sent the request to.'
@@ -128,7 +128,7 @@ if ($state) {
     }
 } else {
     // this is an unsolicited response
-    $relaystate = $spMetadata->getString('RelayState', $response->getRelayState());
+    $relaystate = $spMetadata->getOptionalString('RelayState', $response->getRelayState());
     $state = [
         'saml:sp:isUnsolicited' => true,
         'saml:sp:AuthId'        => $sourceId,
@@ -159,7 +159,7 @@ $attributes = [];
 $foundAuthnStatement = false;
 
 $config = Configuration::getInstance();
-$storeType = $config->getString('store.type', 'phpsession');
+$storeType = $config->getOptionalString('store.type', 'phpsession');
 
 $store = StoreFactory::getInstance($storeType);
 
diff --git a/tests/lib/SimpleSAML/ConfigurationTest.php b/tests/lib/SimpleSAML/ConfigurationTest.php
index 7950667680e9eeb135ad10c7f5d9845716d3f35b..8509797ca209c2919b17bb3793c0504ea923fe0d 100644
--- a/tests/lib/SimpleSAML/ConfigurationTest.php
+++ b/tests/lib/SimpleSAML/ConfigurationTest.php
@@ -6,6 +6,7 @@ namespace SimpleSAML\Test;
 
 use Exception;
 use SAML2\Constants;
+use SimpleSAML\Assert\AssertionFailedException;
 use SimpleSAML\Configuration;
 use SimpleSAML\Error;
 use SimpleSAML\TestUtils\ClearStateTestCase;
@@ -68,25 +69,31 @@ class ConfigurationTest extends ClearStateTestCase
             'exists_true' => true,
             'exists_null' => null,
         ]);
-        $this->assertEquals($c->getValue('missing'), null);
-        $this->assertEquals($c->getValue('missing', true), true);
-        $this->assertEquals($c->getValue('missing', true), true);
 
-        $this->assertEquals($c->getValue('exists_true'), true);
+        // Normal use
+        $this->assertTrue($c->getValue('exists_true'));
+        $this->assertNull($c->getValue('exists_null'));
 
-        $this->assertEquals($c->getValue('exists_null'), null);
-        $this->assertEquals($c->getValue('exists_null', false), null);
+        // Missing option
+        $this->expectException(AssertionFailedException::class);
+        $c->getValue('missing');
     }
 
 
     /**
-     * Test \SimpleSAML\Configuration::getValue(), REQUIRED_OPTION flag.
+     * Test \SimpleSAML\Configuration::getOptionalValue().
      */
-    public function testGetValueRequired(): void
+    public function testGetOptionalValue(): void
     {
-        $this->expectException(Exception::class);
-        $c = Configuration::loadFromArray([]);
-        $c->getValue('missing', Configuration::REQUIRED_OPTION);
+        $c = Configuration::loadFromArray([
+            'exists_true' => true,
+        ]);
+
+        // Normal use
+        $this->assertTrue($c->getOptionalValue('exists_true', 'something else'));
+
+        // Missing option
+        $this->assertNull($c->getOptionalValue('missing', null));
     }
 
 
@@ -249,34 +256,48 @@ class ConfigurationTest extends ClearStateTestCase
         $c = Configuration::loadFromArray([
             'true_opt' => true,
             'false_opt' => false,
+            'wrong_opt' => 'true',
         ]);
-        $this->assertEquals($c->getBoolean('missing_opt', '--missing--'), '--missing--');
-        $this->assertEquals($c->getBoolean('true_opt', '--missing--'), true);
-        $this->assertEquals($c->getBoolean('false_opt', '--missing--'), false);
-    }
 
+        // Normal use
+        $this->assertTrue($c->getBoolean('true_opt'));
+        $this->assertFalse($c->getBoolean('false_opt'));
 
-    /**
-     * Test \SimpleSAML\Configuration::getBoolean() missing option
-     */
-    public function testGetBooleanMissing(): void
-    {
-        $this->expectException(Exception::class);
-        $c = Configuration::loadFromArray([]);
+        // Missing option
+        $this->expectException(AssertionFailedException::class);
         $c->getBoolean('missing_opt');
+
+        // Invalid option type
+        $this->expectException(AssertionFailedException::class);
+        $c->getBoolean('wrong_opt');
     }
 
 
     /**
-     * Test \SimpleSAML\Configuration::getBoolean() wrong option
+     * Test \SimpleSAML\Configuration::getOptionalBoolean()
      */
-    public function testGetBooleanWrong(): void
+    public function testGetOptionalBoolean(): void
     {
-        $this->expectException(Exception::class);
         $c = Configuration::loadFromArray([
-            'wrong' => 'true',
+            'true_opt' => true,
+            'false_opt' => false,
+            'wrong_opt' => 'true',
         ]);
-        $c->getBoolean('wrong');
+
+        // Normal use
+        $this->assertTrue($c->getOptionalBoolean('true_opt', true));
+        $this->assertTrue($c->getOptionalBoolean('true_opt', false));
+        $this->assertFalse($c->getOptionalBoolean('false_opt', false));
+        $this->assertFalse($c->getOptionalBoolean('false_opt', true));
+
+        // Missing option
+        $this->assertEquals($c->getOptionalBoolean('missing_opt', null), null);
+        $this->assertEquals($c->getOptionalBoolean('missing_opt', false), false);
+        $this->assertEquals($c->getOptionalBoolean('missing_opt', true), true);
+
+        // Invalid option type
+        $this->expectException(AssertionFailedException::class);
+        $c->getOptionalBoolean('wrong_opt', null);
     }
 
 
@@ -287,33 +308,42 @@ class ConfigurationTest extends ClearStateTestCase
     {
         $c = Configuration::loadFromArray([
             'str_opt' => 'Hello World!',
+            'wrong_opt' => true,
         ]);
-        $this->assertEquals($c->getString('missing_opt', '--missing--'), '--missing--');
-        $this->assertEquals($c->getString('str_opt', '--missing--'), 'Hello World!');
-    }
 
+        // Normal use
+        $this->assertEquals($c->getString('str_opt'), 'Hello World!');
 
-    /**
-     * Test \SimpleSAML\Configuration::getString() missing option
-     */
-    public function testGetStringMissing(): void
-    {
-        $this->expectException(Exception::class);
-        $c = Configuration::loadFromArray([]);
+        // Missing option
+        $this->expectException(AssertionFailedException::class);
         $c->getString('missing_opt');
+
+        // Invalid option type
+        $this->expectException(AssertionFailedException::class);
+        $c->getString('wrong_opt');
     }
 
 
     /**
-     * Test \SimpleSAML\Configuration::getString() wrong option
+     * Test \SimpleSAML\Configuration::getOptionalString() missing option
      */
-    public function testGetStringWrong(): void
+    public function testGetOptionalString(): void
     {
-        $this->expectException(Exception::class);
         $c = Configuration::loadFromArray([
-            'wrong' => false,
+            'str_opt' => 'Hello World!',
+            'wrong_opt' => true,
         ]);
-        $c->getString('wrong');
+
+        // Normal use
+        $this->assertEquals($c->getOptionalString('str_opt', 'Hello World!'), 'Hello World!');
+        $this->assertEquals($c->getOptionalString('str_opt', 'something else'), 'Hello World!');
+
+        // Missing option
+        $this->assertEquals($c->getOptionalString('missing_opt', 'Hello World!'), 'Hello World!');
+
+        // Invalid option type
+        $this->expectException(AssertionFailedException::class);
+        $c->getOptionalString('wrong_opt', 'Hello World!');
     }
 
 
@@ -324,33 +354,42 @@ class ConfigurationTest extends ClearStateTestCase
     {
         $c = Configuration::loadFromArray([
             'int_opt' => 42,
+            'wrong_opt' => 'test',
         ]);
-        $this->assertEquals($c->getInteger('missing_opt', '--missing--'), '--missing--');
-        $this->assertEquals($c->getInteger('int_opt', '--missing--'), 42);
-    }
 
+        // Normal use
+        $this->assertEquals($c->getInteger('int_opt'), 42);
 
-    /**
-     * Test \SimpleSAML\Configuration::getInteger() missing option
-     */
-    public function testGetIntegerMissing(): void
-    {
-        $this->expectException(Exception::class);
-        $c = Configuration::loadFromArray([]);
+        // Missing option
+        $this->expectException(AssertionFailedException::class);
         $c->getInteger('missing_opt');
+
+        // Invalid option type
+        $this->expectException(AssertionFailedException::class);
+        $c->getInteger('wrong_opt');
     }
 
 
     /**
-     * Test \SimpleSAML\Configuration::getInteger() wrong option
+     * Test \SimpleSAML\Configuration::getOptionalInteger()
      */
-    public function testGetIntegerWrong(): void
+    public function testGetOptionalInteger(): void
     {
-        $this->expectException(Exception::class);
         $c = Configuration::loadFromArray([
-            'wrong' => '42',
+            'int_opt' => 42,
+            'wrong_opt' => 'test',
         ]);
-        $c->getInteger('wrong');
+
+
+        // Normal use
+        $this->assertEquals($c->getOptionalInteger('int_opt', 42), 42);
+
+        // Missing option
+        $this->assertEquals($c->getOptionalInteger('missing_opt', 32), 32);
+
+        // Invalid option type
+        $this->expectException(AssertionFailedException::class);
+        $c->getOptionalInteger('wrong_opt', 10);
     }
 
 
@@ -360,36 +399,63 @@ class ConfigurationTest extends ClearStateTestCase
     public function testGetIntegerRange(): void
     {
         $c = Configuration::loadFromArray([
-            'int_opt' => 42,
+            'min_opt' => 0,
+            'max_opt' => 100,
+            'wrong_opt' => 'test',
         ]);
-        $this->assertEquals($c->getIntegerRange('missing_opt', 0, 100, '--missing--'), '--missing--');
-        $this->assertEquals($c->getIntegerRange('int_opt', 0, 100), 42);
-    }
 
+        // Normal use
+        $this->assertEquals($c->getIntegerRange('min_opt', 0, 100), 0);
+        $this->assertEquals($c->getIntegerRange('max_opt', 0, 100), 100);
 
-    /**
-     * Test \SimpleSAML\Configuration::getIntegerRange() below limit
-     */
-    public function testGetIntegerRangeBelow(): void
-    {
-        $this->expectException(Exception::class);
-        $c = Configuration::loadFromArray([
-            'int_opt' => 9,
-        ]);
-        $this->assertEquals($c->getIntegerRange('int_opt', 10, 100), 42);
+        // Missing option
+        $this->expectException(AssertionFailedException::class);
+        $c->getIntegerRange('missing_opt', 0, 100);
+
+        // Invalid option type
+        $this->expectException(AssertionFailedException::class);
+        $c->getIntegerRange('wrong_opt', 0, 100);
+
+        // Below range
+        $this->expectException(AssertionFailedException::class);
+        $c->getIntegerRange('min_opt', 1, 100);
+
+        // Above range
+        $this->expectException(AssertionFailedException::class);
+        $c->getIntegerRange('max_opt', 0, 99);
     }
 
 
     /**
-     * Test \SimpleSAML\Configuration::getIntegerRange() above limit
+     * Test \SimpleSAML\Configuration::getOptionalIntegerRange()
      */
-    public function testGetIntegerRangeAbove(): void
+    public function testGetOptionalIntegerRange(): void
     {
-        $this->expectException(Exception::class);
         $c = Configuration::loadFromArray([
-            'int_opt' => 101,
+            'min_opt' => 0,
+            'max_opt' => 100,
+            'wrong_opt' => 'test',
         ]);
-        $this->assertEquals($c->getIntegerRange('int_opt', 10, 100), 42);
+
+
+        // Normal use
+        $this->assertEquals($c->getOptionalIntegerRange('min_opt', 0, 100, 50), 0);
+        $this->assertEquals($c->getOptionalIntegerRange('max_opt', 0, 100, 50), 100);
+
+        // Missing option
+        $this->assertEquals($c->getOptionalIntegerRange('missing_opt', 0, 100, 50), 50);
+
+        // Invalid option type
+        $this->expectException(AssertionFailedException::class);
+        $c->getOptionalIntegerRange('wrong_opt', 0, 100, null);
+
+        // Below range
+        $this->expectException(AssertionFailedException::class);
+        $c->getOptionalIntegerRange('min_opt', 1, 100, null);
+
+        // Above range
+        $this->expectException(AssertionFailedException::class);
+        $c->getOptionalIntegerRange('max_opt', 0, 99, null);
     }
 
 
@@ -401,21 +467,39 @@ class ConfigurationTest extends ClearStateTestCase
         $c = Configuration::loadFromArray([
             'opt' => 'b',
         ]);
-        $this->assertEquals($c->getValueValidate('missing_opt', ['a', 'b', 'c'], '--missing--'), '--missing--');
+
+        // Normal use
         $this->assertEquals($c->getValueValidate('opt', ['a', 'b', 'c']), 'b');
+
+        // Value not allowed
+        $this->expectException(AssertionFailedException::class);
+        $c->getValueValidate('opt', ['d', 'e', 'f']);
+
+        // Missing option
+        $this->expectException(AssertionFailedException::class);
+        $c->getValueValidate('missing_opt', ['a', 'b', 'c']);
     }
 
 
     /**
-     * Test \SimpleSAML\Configuration::getValueValidate() wrong option
+     * Test \SimpleSAML\Configuration::getOptionalValueValidate()
      */
-    public function testGetValueValidateWrong(): void
+    public function testGetOptionalValueValidate(): void
     {
-        $this->expectException(Exception::class);
         $c = Configuration::loadFromArray([
-            'opt' => 'd',
+            'opt' => 'b',
         ]);
-        $c->getValueValidate('opt', ['a', 'b', 'c']);
+
+        // Normal use
+        $this->assertEquals($c->getOptionalValueValidate('opt', ['a', 'b', 'c'], 'f'), 'b');
+
+        // Missing option
+        $this->assertEquals($c->getOptionalValueValidate('missing_opt', ['a', 'b', 'c'], 'b'), 'b');
+
+        // Value not allowed
+        $this->expectException(AssertionFailedException::class);
+        $c->getOptionalValueValidate('opt', ['d', 'e', 'f'], 'c');
+        $c->getOptionalValueValidate('missing_opt', ['d', 'e', 'f'], 'c');
     }
 
 
@@ -426,22 +510,41 @@ class ConfigurationTest extends ClearStateTestCase
     {
         $c = Configuration::loadFromArray([
             'opt' => ['a', 'b', 'c'],
+            'wrong_opt' => false,
         ]);
-        $this->assertEquals($c->getArray('missing_opt', '--missing--'), '--missing--');
+
+        // Normal use
         $this->assertEquals($c->getArray('opt'), ['a', 'b', 'c']);
+
+        // Missing option
+        $this->expectException(AssertionFailedException::class);
+        $c->getArray('missing_opt');
+
+        // Value not allowed
+        $this->expectException(AssertionFailedException::class);
+        $c->getArray('wrong_opt');
     }
 
 
     /**
-     * Test \SimpleSAML\Configuration::getArray() wrong option
+     * Test \SimpleSAML\Configuration::getOptionalArray()
      */
-    public function testGetArrayWrong(): void
+    public function testGetOptionalArray(): void
     {
-        $this->expectException(Exception::class);
         $c = Configuration::loadFromArray([
-            'opt' => 'not_an_array',
+            'opt' => ['a', 'b', 'c'],
+            'wrong_opt' => false,
         ]);
-        $c->getArray('opt');
+
+        // Normal use
+        $this->assertEquals($c->getOptionalArray('opt', ['d', 'e', 'f']), ['a', 'b', 'c']);
+
+        // Missing option
+        $this->assertEquals($c->getOptionalArray('missing_opt', ['d', 'e', 'f']), ['d', 'e', 'f']);
+
+        // Value not allowed
+        $this->expectException(AssertionFailedException::class);
+        $c->getArray('wrong_opt');
     }
 
 
@@ -455,10 +558,36 @@ class ConfigurationTest extends ClearStateTestCase
             'opt_int' => 42,
             'opt_str' => 'string',
         ]);
-        $this->assertEquals($c->getArrayize('missing_opt', '--missing--'), '--missing--');
+
+        // Normal use
         $this->assertEquals($c->getArrayize('opt'), ['a', 'b', 'c']);
         $this->assertEquals($c->getArrayize('opt_int'), [42]);
         $this->assertEquals($c->getArrayize('opt_str'), ['string']);
+
+        // Missing option
+        $this->expectException(AssertionFailedException::class);
+        $c->getArrayize('missing_opt');
+    }
+
+
+    /**
+     * Test \SimpleSAML\Configuration::getOptionalArrayize()
+     */
+    public function testGetOptionalArrayize(): void
+    {
+        $c = Configuration::loadFromArray([
+            'opt' => ['a', 'b', 'c'],
+            'opt_int' => 42,
+            'opt_str' => 'string',
+        ]);
+
+        // Normal use
+        $this->assertEquals($c->getOptionalArrayize('opt', ['d']), ['a', 'b', 'c']);
+        $this->assertEquals($c->getOptionalArrayize('opt_int', [1]), [42]);
+        $this->assertEquals($c->getOptionalArrayize('opt_str', ['test']), ['string']);
+
+        // Missing option
+        $this->assertEquals($c->getOptionalArrayize('missing_opt', ['test']), ['test']);
     }
 
 
@@ -470,24 +599,44 @@ class ConfigurationTest extends ClearStateTestCase
         $c = Configuration::loadFromArray([
             'opt' => ['a', 'b', 'c'],
             'opt_str' => 'string',
+            'opt_wrong' => 4,
         ]);
-        $this->assertEquals($c->getArrayizeString('missing_opt', '--missing--'), '--missing--');
+
+        // Normale use
         $this->assertEquals($c->getArrayizeString('opt'), ['a', 'b', 'c']);
         $this->assertEquals($c->getArrayizeString('opt_str'), ['string']);
+
+        // Missing option
+        $this->expectException(AssertionFailedException::class);
+        $c->getArrayizeString('missing_opt');
+
+        // Wrong option
+        $this->expectException(AssertionFailedException::class);
+        $c->getArrayizeString('opt_wrong');
     }
 
 
     /**
-     * Test \SimpleSAML\Configuration::getArrayizeString() option
-     * with an array that contains something that isn't a string.
+     * Test \SimpleSAML\Configuration::getOptionalArrayizeString()
      */
-    public function testGetArrayizeStringWrongValue(): void
+    public function testGetOptionalArrayizeString(): void
     {
-        $this->expectException(Exception::class);
         $c = Configuration::loadFromArray([
-            'opt' => ['a', 'b', 42],
+            'opt' => ['a', 'b', 'c'],
+            'opt_str' => 'string',
+            'opt_wrong' => 4,
         ]);
-        $c->getArrayizeString('opt');
+
+        // Normale use
+        $this->assertEquals($c->getOptionalArrayizeString('opt', ['d']), ['a', 'b', 'c']);
+        $this->assertEquals($c->getOptionalArrayizeString('opt_str', ['test']), ['string']);
+
+        // Missing option
+        $this->assertEquals($c->getOptionalArrayizeString('missing_opt', ['test']), ['test']);
+
+        // Wrong option
+        $this->expectException(AssertionFailedException::class);
+        $c->getOptionalArrayizeString('opt_wrong', ['test']);
     }
 
 
@@ -499,25 +648,30 @@ class ConfigurationTest extends ClearStateTestCase
         $c = Configuration::loadFromArray([
             'opt' => ['a' => 42],
         ]);
-        $this->assertNull($c->getConfigItem('missing_opt', null));
+
         $opt = $c->getConfigItem('opt');
-        $notOpt = $c->getConfigItem('notOpt');
         $this->assertInstanceOf(Configuration::class, $opt);
-        $this->assertInstanceOf(Configuration::class, $notOpt);
-        $this->assertEquals($opt->getValue('a'), 42);
+
+        // Missing option
+        $this->expectException(AssertionFailedException::class);
+        $c->getConfigItem('missing_opt');
     }
 
 
     /**
-     * Test \SimpleSAML\Configuration::getConfigItem() wrong option
+     * Test \SimpleSAML\Configuration::getOptionalConfigItem()
      */
-    public function testGetConfigItemWrong(): void
+    public function testGetOptionalConfigItem(): void
     {
-        $this->expectException(Exception::class);
         $c = Configuration::loadFromArray([
-            'opt' => 'not_an_array',
+            'opt' => ['a' => 42],
         ]);
-        $c->getConfigItem('opt');
+
+        $opt = $c->getOptionalConfigItem('opt', null);
+        $this->assertInstanceOf(Configuration::class, $opt);
+
+        // Missing option
+        $this->assertNull($c->getOptionalConfigItem('missing_opt', null));
     }
 
 
@@ -858,9 +1012,11 @@ class ConfigurationTest extends ClearStateTestCase
                 'no' => 'Hei Verden!',
             ],
         ]);
-        $this->assertEquals($c->getLocalizedString('missing_opt', '--missing--'), '--missing--');
         $this->assertEquals($c->getLocalizedString('str_opt'), ['en' => 'Hello World!']);
         $this->assertEquals($c->getLocalizedString('str_array'), ['en' => 'Hello World!', 'no' => 'Hei Verden!']);
+
+        $this->expectException(AssertionFailedException::class);
+        $c->getLocalizedString('missing_opt');
     }
 
 
@@ -924,7 +1080,7 @@ class ConfigurationTest extends ClearStateTestCase
         $virtualFile = 'nonexistent-preload.php';
         Configuration::setPreLoadedConfig($c, $virtualFile);
         $nc = Configuration::getConfig($virtualFile);
-        $this->assertEquals('value', $nc->getValue('key', null));
+        $this->assertEquals('value', $nc->getOptionalValue('key', null));
     }
 
 
@@ -940,10 +1096,10 @@ class ConfigurationTest extends ClearStateTestCase
         ];
         // test loading a custom instance
         Configuration::loadFromArray($c, '', 'dummy');
-        $this->assertEquals('value', Configuration::getInstance('dummy')->getValue('key', null));
+        $this->assertEquals('value', Configuration::getInstance('dummy')->getOptionalValue('key', null));
 
         // test loading the default instance
         Configuration::loadFromArray($c, '', 'simplesaml');
-        $this->assertEquals('value', Configuration::getInstance()->getValue('key', null));
+        $this->assertEquals('value', Configuration::getInstance()->getOptionalValue('key', null));
     }
 }
diff --git a/tests/lib/SimpleSAML/Store/StoreFactoryTest.php b/tests/lib/SimpleSAML/Store/StoreFactoryTest.php
index 3164d5f1c67dfe7cebaa00870f39fd53b16cd538..27a421916c813f64dd052bc7f3121d2ee977d41f 100644
--- a/tests/lib/SimpleSAML/Store/StoreFactoryTest.php
+++ b/tests/lib/SimpleSAML/Store/StoreFactoryTest.php
@@ -32,7 +32,7 @@ class StoreFactoryTest extends TestCase
         Configuration::loadFromArray([], '[ARRAY]', 'simplesaml');
 
         $config = Configuration::getInstance();
-        $storeType = $config->getString('store.type', 'phpsession');
+        $storeType = $config->getOptionalString('store.type', 'phpsession');
 
         /** @var false $store */
         $store = StoreFactory::getInstance($storeType);
@@ -66,7 +66,7 @@ class StoreFactoryTest extends TestCase
     public function memcacheStore(): void
     {
         Configuration::loadFromArray([
-            'store.type'                    => 'memcache',
+            'store.type' => 'memcache',
         ], '[ARRAY]', 'simplesaml');
 
         $config = Configuration::getInstance();
@@ -166,7 +166,7 @@ class StoreFactoryTest extends TestCase
     protected function tearDown(): void
     {
         $config = Configuration::getInstance();
-        $storeType = $config->getString('store.type', 'phpsession');
+        $storeType = $config->getOptionalString('store.type', 'phpsession');
 
         /** @var \SimpleSAML\Store\StoreInterface $store */
         $store = StoreFactory::getInstance($storeType);
diff --git a/www/errorreport.php b/www/errorreport.php
index 4af111475ab3a28e0dcdb8fa36e94166e60d1387..5200c8dc301bc5506040a42e28a3b374dbb513fa 100644
--- a/www/errorreport.php
+++ b/www/errorreport.php
@@ -48,7 +48,7 @@ $data['version'] = $config->getVersion();
 $data['hostname'] = php_uname('n');
 $data['directory'] = dirname(dirname(__FILE__));
 
-if ($config->getBoolean('errorreporting', true)) {
+if ($config->getOptionalBoolean('errorreporting', true)) {
     $mail = new SimpleSAML\Utils\EMail('SimpleSAMLphp error report from ' . $email);
     $mail->setData($data);
     if ($email) {
diff --git a/www/index.php b/www/index.php
index 2e366c1d2c491de59e7939593c84be965103d022..a01831ec12fd6e37e32ad5a880a897dd08205c44 100644
--- a/www/index.php
+++ b/www/index.php
@@ -5,5 +5,5 @@ require_once('_include.php');
 $config = \SimpleSAML\Configuration::getInstance();
 $httpUtils = new \SimpleSAML\Utils\HTTP();
 
-$redirect = $config->getString('frontpage.redirect', SimpleSAML\Module::getModuleURL('core/welcome'));
+$redirect = $config->getOptionalString('frontpage.redirect', SimpleSAML\Module::getModuleURL('core/welcome'));
 $httpUtils->redirectTrustedURL($redirect);
diff --git a/www/saml2/idp/ArtifactResolutionService.php b/www/saml2/idp/ArtifactResolutionService.php
index 30b3652acc772e7bbaf33b3e1ed87e71b2e432d8..6ba0ea8b3a85e2d6428d4dd42c7de09172720d93 100644
--- a/www/saml2/idp/ArtifactResolutionService.php
+++ b/www/saml2/idp/ArtifactResolutionService.php
@@ -23,7 +23,7 @@ use SimpleSAML\Metadata;
 use SimpleSAML\Store\StoreFactory;
 
 $config = Configuration::getInstance();
-if (!$config->getBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) {
+if (!$config->getOptionalBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) {
     throw new Error\Error('NOACCESS', null, 403);
 }
 
@@ -31,11 +31,11 @@ $metadata = Metadata\MetaDataStorageHandler::getMetadataHandler();
 $idpEntityId = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted');
 $idpMetadata = $metadata->getMetaDataConfig($idpEntityId, 'saml20-idp-hosted');
 
-if (!$idpMetadata->getBoolean('saml20.sendartifact', false)) {
+if (!$idpMetadata->getOptionalBoolean('saml20.sendartifact', false)) {
     throw new Error\Error('NOACCESS');
 }
 
-$storeType = $config->getString('store.type', 'phpsession');
+$storeType = $config->getOptionalString('store.type', 'phpsession');
 $store = StoreFactory::getInstance($storeType);
 if ($store === false) {
     throw new Exception('Unable to send artifact without a datastore configured.');
diff --git a/www/saml2/idp/SSOService.php b/www/saml2/idp/SSOService.php
index f139b2db3a8a60edfe3fedc8b12412533ebfbf1f..ac617b94c95fbc35bc4cade0aae5dd4bb88deb97 100644
--- a/www/saml2/idp/SSOService.php
+++ b/www/saml2/idp/SSOService.php
@@ -22,7 +22,7 @@ use SimpleSAML\Module;
 Logger::info('SAML2.0 - IdP.SSOService: Accessing SAML 2.0 IdP endpoint SSOService');
 
 $config = Configuration::getInstance();
-if (!$config->getBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) {
+if (!$config->getOptionalBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) {
     throw new Error\Error('NOACCESS', null, 403);
 }
 
diff --git a/www/saml2/idp/SingleLogoutService.php b/www/saml2/idp/SingleLogoutService.php
index 2a617332f0e0bf42e72cd311ac68e31b9b8aa9ec..df1d597b63e0cdd3f92af2507e92312204103430 100644
--- a/www/saml2/idp/SingleLogoutService.php
+++ b/www/saml2/idp/SingleLogoutService.php
@@ -22,7 +22,7 @@ use SimpleSAML\Utils;
 Logger::info('SAML2.0 - IdP.SingleLogoutService: Accessing SAML 2.0 IdP endpoint SingleLogoutService');
 
 $config = Configuration::getInstance();
-if (!$config->getBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) {
+if (!$config->getOptionalBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) {
     throw new Error\Error('NOACCESS', null, 403);
 }
 
diff --git a/www/saml2/idp/initSLO.php b/www/saml2/idp/initSLO.php
index 11961711827be96764b280b0a47d9e686c5d8f3d..5f49209038299ebebc998533a2361ffbaaefede9 100644
--- a/www/saml2/idp/initSLO.php
+++ b/www/saml2/idp/initSLO.php
@@ -14,7 +14,7 @@ use SimpleSAML\Utils;
 Logger::info('SAML2.0 - IdP.initSLO: Accessing SAML 2.0 IdP endpoint init Single Logout');
 
 $config = Configuration::getInstance();
-if (!$config->getBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) {
+if (!$config->getOptionalBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) {
     throw new Error\Error('NOACCESS', null, 403);
 }
 
diff --git a/www/saml2/idp/metadata.php b/www/saml2/idp/metadata.php
index 31d3d53f66c8d44a3513df9655e60da9bc25bf3c..2ef9f313864d3dc89f8d3df61b72090467dc985c 100644
--- a/www/saml2/idp/metadata.php
+++ b/www/saml2/idp/metadata.php
@@ -9,12 +9,12 @@ use SimpleSAML\Module\saml\IdP\SAML2 as SAML2_IdP;
 use SimpleSAML\Utils;
 
 $config = Configuration::getInstance();
-if (!$config->getBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) {
+if (!$config->getOptionalBoolean('enable.saml20-idp', false) || !Module::isModuleEnabled('saml')) {
     throw new Error\Error('NOACCESS', null, 403);
 }
 
 // check if valid local session exists
-if ($config->getBoolean('admin.protectmetadata', false)) {
+if ($config->getOptionalBoolean('admin.protectmetadata', false)) {
     $authUtils = new Utils\Auth();
     $authUtils->requireAdmin();
 }