Repository: SAP/cloud-security-xsuaa-integration
Pull request #1075 (2023-02-01 11:03:04)

Switch to service binding accessor library
... instead of using CFEnvParser


Patch:
diff --git a/api/pom.xml b/api/pom.xml
index b7f29809..7c35c4b3 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -28,7 +28,7 @@
 			<plugin>
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-javadoc-plugin</artifactId>
-				<version>3.4.1</version>
+				<version>3.5.0</version>
 				<executions>
 					<execution>
 						<id>attach-javadocs</id>
diff --git a/bom/pom.xml b/bom/pom.xml
index 6afa32f2..abbdea3f 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -140,7 +140,7 @@
                     <plugin>
                         <groupId>org.apache.maven.plugins</groupId>
                         <artifactId>maven-javadoc-plugin</artifactId>
-                        <version>3.4.1</version>
+                        <version>3.5.0</version>
                         <executions>
                             <execution>
                                 <id>attach-javadocs</id>
diff --git a/env/pom.xml b/env/pom.xml
index b19ad14f..295e5351 100644
--- a/env/pom.xml
+++ b/env/pom.xml
@@ -23,6 +23,10 @@
             <artifactId>java-api</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>com.sap.cloud.environment.servicebinding</groupId>
+            <artifactId>java-sap-vcap-services</artifactId>
+        </dependency>
         <dependency>
             <groupId>com.sap.cloud.environment.servicebinding</groupId>
             <artifactId>java-sap-service-operator</artifactId>
@@ -81,18 +85,6 @@
             <artifactId>wiremock-jre8</artifactId>
             <scope>test</scope>
         </dependency>
-        <dependency>
-            <groupId>com.github.stefanbirkner</groupId>
-            <artifactId>system-rules</artifactId>
-            <version>1.19.0</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>uk.org.webcompere</groupId>
-            <artifactId>system-stubs-jupiter</artifactId>
-            <version>2.0.2</version>
-            <scope>test</scope>
-        </dependency>
         <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
diff --git a/env/src/main/java/com/sap/cloud/security/config/Environments.java b/env/src/main/java/com/sap/cloud/security/config/Environments.java
index c6f25973..81366cb7 100644
--- a/env/src/main/java/com/sap/cloud/security/config/Environments.java
+++ b/env/src/main/java/com/sap/cloud/security/config/Environments.java
@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
 
 import java.io.InputStream;
 import java.util.Scanner;
+import java.util.function.UnaryOperator;
 
 import static com.sap.cloud.security.config.k8s.K8sConstants.KUBERNETES_SERVICE_HOST;
 
@@ -22,8 +23,8 @@ import static com.sap.cloud.security.config.k8s.K8sConstants.KUBERNETES_SERVICE_
 public class Environments {
 
 	private static final Logger LOGGER = LoggerFactory.getLogger(Environments.class);
-
 	private static Environment currentEnvironment;
+	private static UnaryOperator<String> environmentVariableReader = System::getenv;
 
 	private Environments() {
 		// use factoryMethods instead
@@ -47,11 +48,15 @@ public class Environments {
 		return currentEnvironment;
 	}
 
+	static void setEnvironmentVariableReader(UnaryOperator<String> environmentVariableReader) {
+		Environments.environmentVariableReader = environmentVariableReader;
+	}
+
 	/**
 	 * Reads {@link Environment} not from system environment but from
 	 * {@link InputStream}. Is applicable only to CF environment and expects the
 	 * input to be in VCAP services format.
-	 * 
+	 *
 	 * @param input
 	 *            e.g. from file
 	 * @return the environment
@@ -62,11 +67,12 @@ public class Environments {
 		while (scanner.hasNext()) {
 			vcapServices.append(scanner.nextLine());
 		}
-		return CFEnvironment.getInstance(str -> vcapServices.toString(), str -> null);
+
+		return CFEnvironment.getInstance(str -> vcapServices.toString());
 	}
 
 	private static boolean isK8sEnv() {
-		return System.getenv().get(KUBERNETES_SERVICE_HOST) != null;
+		return environmentVariableReader.apply(KUBERNETES_SERVICE_HOST) != null;
 	}
 
 }
diff --git a/env/src/main/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilder.java b/env/src/main/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilder.java
index d129dafa..b32df3e0 100644
--- a/env/src/main/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilder.java
+++ b/env/src/main/java/com/sap/cloud/security/config/OAuth2ServiceConfigurationBuilder.java
@@ -16,6 +16,7 @@ import static com.sap.cloud.security.config.cf.CFConstants.*;
  * dedicated identity ({@link Service}) based on the properties applied.
  */
 public class OAuth2ServiceConfigurationBuilder {
+
 	private Service service;
 	private boolean runInLegacyMode;
 	private final Map<String, String> properties = new HashMap<>();
@@ -118,7 +119,7 @@ public class OAuth2ServiceConfigurationBuilder {
 	 * this is the url where the service instance was created.
 	 *
 	 * @param url
-	 *            base url, e.g. https://paastenant.idservice.com
+	 *            base url, e.g. <a href="https://paastenant.idservice.com">...</a>
 	 * @return this builder itself
 	 */
 	public OAuth2ServiceConfigurationBuilder withUrl(String url) {
@@ -130,7 +131,8 @@ public class OAuth2ServiceConfigurationBuilder {
 	 * Cert URL of the OAuth2 identity service instance.
 	 *
 	 * @param url
-	 *            cert url, e.g. https://paastenant.cert.idservice.com
+	 *            cert url, e.g.
+	 *            <a href="https://paastenant.cert.idservice.com">...</a>
 	 * @return this builder itself
 	 */
 	public OAuth2ServiceConfigurationBuilder withCertUrl(String url) {
diff --git a/env/src/main/java/com/sap/cloud/security/config/ServiceBindingMapper.java b/env/src/main/java/com/sap/cloud/security/config/ServiceBindingMapper.java
new file mode 100644
index 00000000..c3be5cbc
--- /dev/null
+++ b/env/src/main/java/com/sap/cloud/security/config/ServiceBindingMapper.java
@@ -0,0 +1,49 @@
+package com.sap.cloud.security.config;
+
+import com.sap.cloud.environment.servicebinding.api.ServiceBinding;
+import com.sap.cloud.environment.servicebinding.api.TypedMapView;
+import com.sap.cloud.security.config.k8s.K8sConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+import java.util.List;
+
+import static com.sap.cloud.security.config.Service.IAS;
+import static com.sap.cloud.security.config.cf.CFConstants.IAS.DOMAINS;
+import static com.sap.cloud.security.config.cf.CFConstants.SERVICE_PLAN;
+
+@Deprecated
+public class ServiceBindingMapper {
+	private static final Logger LOGGER = LoggerFactory.getLogger(ServiceBindingMapper.class);
+
+	@Deprecated
+	@Nullable
+	public static OAuth2ServiceConfigurationBuilder mapToOAuth2ServiceConfigurationBuilder(ServiceBinding b) {
+		if (!b.getServiceName().isPresent()) {
+			LOGGER.error("Ignores Service Binding with name {} as service name is not provided.", b.getName());
+			return null; // as of now, method is never called when service name isn't given
+		}
+
+		final Service service = Service.from(b.getServiceName().get());
+		if (service == null) {
+			LOGGER.error(
+					"Service name {} is unknown. Could not create a OAuth2ServiceConfiguration from a service binding.",
+					b.getServiceName().get());
+			return null;
+		}
+
+		OAuth2ServiceConfigurationBuilder builder = OAuth2ServiceConfigurationBuilder.forService(service)
+				.withProperties(TypedMapView.ofCredentials(b).getEntries(String.class))
+				.withProperty(SERVICE_PLAN,
+						b.getServicePlan().orElse(K8sConstants.Plan.APPLICATION.name()).toUpperCase());
+
+		if (IAS.equals(service)) {
+			List<String> domains = TypedMapView.ofCredentials(b).getListView(DOMAINS).getItems(String.class);
+			LOGGER.info("first domain : {}", domains.get(0));
+			builder.withDomains(domains.toArray(new String[] {}));
+		}
+
+		return builder;
+	}
+}
\ No newline at end of file
diff --git a/env/src/main/java/com/sap/cloud/security/config/cf/CFEnvironment.java b/env/src/main/java/com/sap/cloud/security/config/cf/CFEnvironment.java
index bb8cb3c7..10ecdf5f 100644
--- a/env/src/main/java/com/sap/cloud/security/config/cf/CFEnvironment.java
+++ b/env/src/main/java/com/sap/cloud/security/config/cf/CFEnvironment.java
@@ -1,25 +1,28 @@
 /**
  * SPDX-FileCopyrightText: 2018-2022 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors
- * 
+ * <p>
  * SPDX-License-Identifier: Apache-2.0
  */
 package com.sap.cloud.security.config.cf;
 
-import static com.sap.cloud.security.config.Service.IAS;
-import static com.sap.cloud.security.config.Service.XSUAA;
-import static com.sap.cloud.security.config.cf.CFConstants.VCAP_APPLICATION;
-import static com.sap.cloud.security.config.cf.CFConstants.VCAP_SERVICES;
+import com.sap.cloud.environment.servicebinding.SapVcapServicesServiceBindingAccessor;
+import com.sap.cloud.environment.servicebinding.api.ServiceBinding;
+import com.sap.cloud.environment.servicebinding.api.ServiceBindingAccessor;
+import com.sap.cloud.security.annotation.Beta;
+import com.sap.cloud.security.config.*;
+import com.sap.cloud.security.config.cf.CFConstants.Plan;
+import com.sap.cloud.security.json.DefaultJsonObject;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
-
-import com.sap.cloud.security.config.Environment;
-import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
-import com.sap.cloud.security.config.Service;
-import com.sap.cloud.security.config.cf.CFConstants.Plan;
-
 import java.util.*;
 import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+
+import static com.sap.cloud.security.config.Service.IAS;
+import static com.sap.cloud.security.config.Service.XSUAA;
+import static com.sap.cloud.security.config.cf.CFConstants.VCAP_APPLICATION;
+import static com.sap.cloud.security.config.cf.CFConstants.VCAP_SERVICES;
 
 /**
  * Loads the OAuth configuration ({@link OAuth2ServiceConfiguration}) of a
@@ -28,90 +31,112 @@ import java.util.function.UnaryOperator;
  */
 public class CFEnvironment implements Environment {
 
-	private static final String EMPTY_JSON = "{}";
-
-	private Map<Service, List<OAuth2ServiceConfiguration>> serviceConfigurations;
-	private UnaryOperator<String> systemEnvironmentProvider;
-	private UnaryOperator<String> systemPropertiesProvider;
+	private ServiceBindingAccessor serviceBindingAccessor;
+	private UnaryOperator<String> environmentVariableReader = System::getenv;
+	private final Map<Service, List<OAuth2ServiceConfiguration>> serviceConfigurations;
 
 	private CFEnvironment() {
-		// implemented in getInstance() factory method
+		serviceConfigurations = new EnumMap<>(Service.class);
 	}
 
+	/**
+	 * Creates a new CFEnvironment that reads {@link CFConstants#VCAP_SERVICES} from
+	 * 		<ul>
+	 * 		    <li>system properties OR</li>
+	 * 		    <li>environment variables if system properties has no {@link CFConstants#VCAP_SERVICES}.</li>
+	 * 		</ul>
+	 * */
 	public static CFEnvironment getInstance() {
-		return getInstance(System::getenv, System::getProperty);
+		return getInstance(pickEnvironmentAccessor(System::getProperty, System::getenv));
 	}
 
-	public static CFEnvironment getInstance(UnaryOperator<String> systemEnvironmentProvider,
-			UnaryOperator<String> systemPropertiesProvider) {
+	/**
+	 * Creates a new CFEnvironment that reads {@link CFConstants#VCAP_SERVICES} via the provided vcapProvider.
+	 * <br>
+	 * <b>Example use:</b>
+	 * CFEnvironment.getInstance(any -&gt; VCAP_SERVICES_JSON)
+	 *
+	 * @param vcapProvider provides Cloud Foundry environment configurations as JSON string including but not
+	 *                        necessarily limited to key {@link com.sap.cloud.security.config.cf.CFConstants#VCAP_SERVICES}.
+	 * */
+	public static CFEnvironment getInstance(UnaryOperator<String> vcapProvider) {
 		CFEnvironment instance = new CFEnvironment();
-		instance.systemEnvironmentProvider = systemEnvironmentProvider;
-		instance.systemPropertiesProvider = systemPropertiesProvider;
-		instance.serviceConfigurations = CFEnvParser.loadAll(instance.extractVcapServicesJson(),
-				instance.extractVcapApplicationJson());
+		instance.environmentVariableReader = vcapProvider;
+		instance.serviceBindingAccessor = new SapVcapServicesServiceBindingAccessor(vcapProvider);
+		instance.readServiceConfigurations();
 		return instance;
 	}
 
-	@Override
-	public OAuth2ServiceConfiguration getXsuaaConfiguration() {
-		return loadXsuaa();
-	}
-
-	@Nullable
-	@Override
-	public OAuth2ServiceConfiguration getIasConfiguration() {
-		return loadAllForService(IAS).stream().filter(Objects::nonNull).findFirst().orElse(null);
-	}
-
-	@Override
-	public int getNumberOfXsuaaConfigurations() {
-		return loadAllForService(XSUAA).size();
-	}
-
-	@Override
-	public OAuth2ServiceConfiguration getXsuaaConfigurationForTokenExchange() {
-		if (getNumberOfXsuaaConfigurations() > 1) {
-			return loadForServicePlan(XSUAA, Plan.BROKER);
-		}
-		return getXsuaaConfiguration();
-	}
-
 	/**
-	 * Loads all configurations of all service instances of the dedicated service.
+	 * Beta feature.
 	 *
-	 * @param service
-	 *            the service name
-	 * @return the list of all found configurations or empty list, in case there are
-	 *         no service bindings.
+	 * Creates a new CFEnvironment that uses the provided ServiceBindingAccessor to create a configuration.
+	 * @param serviceBindingAccessor a ServiceBindingAccessor that is used to build a {@link OAuth2ServiceConfiguration}
+	 * from the service bindings.
 	 */
-	List<OAuth2ServiceConfiguration> loadAllForService(Service service) {
-		return serviceConfigurations.getOrDefault(service, Collections.emptyList());
+	@Beta
+	public static CFEnvironment getInstance(ServiceBindingAccessor serviceBindingAccessor) {
+		CFEnvironment instance = new CFEnvironment();
+		instance.serviceBindingAccessor = serviceBindingAccessor;
+		instance.readServiceConfigurations();
+
+		return instance;
 	}
 
-	@Override
-	@Nonnull
-	public Type getType() {
-		return Type.CF;
+	/**
+	 * @deprecated in favor of *
+	 *             {@link com.sap.cloud.security.config.cf.CFEnvironment#getInstance(UnaryOperator)}.
+	 *             * Will be deleted with version 3.0.0.
+	 */
+	@Deprecated
+	public static CFEnvironment getInstance(UnaryOperator<String> systemEnvironmentProvider,
+			UnaryOperator<String> systemPropertiesProvider) {
+		return getInstance(pickEnvironmentAccessor(systemEnvironmentProvider, systemPropertiesProvider));
 	}
 
-	private String extractVcapServicesJson() {
+	private static UnaryOperator<String> pickEnvironmentAccessor(UnaryOperator<String> systemEnvironmentProvider,
+			UnaryOperator<String> systemPropertiesProvider) {
 		String env = systemPropertiesProvider.apply(VCAP_SERVICES);
-		if (env == null) {
+		if (env != null) {
+			return systemPropertiesProvider;
+		} else {
 			env = systemEnvironmentProvider.apply(VCAP_SERVICES);
+			if (env != null) {
+				return systemEnvironmentProvider;
+			}
 		}
-		return emptyStringOrNull(env) ? EMPTY_JSON : env;
+		return systemEnvironmentProvider;
 	}
 
-	private boolean emptyStringOrNull(String env) {
-		return env == null || env.trim().isEmpty();
+	private void readServiceConfigurations() {
+		List<ServiceBinding> serviceBindings = serviceBindingAccessor.getServiceBindings();
+
+		List<OAuth2ServiceConfiguration> xsuaaPlans = serviceBindings.stream()
+				.filter(b -> Service.XSUAA.equals(Service.from(b.getServiceName().orElse(""))))
+				.map(ServiceBindingMapper::mapToOAuth2ServiceConfigurationBuilder)
+				.filter(Objects::nonNull)
+				.map(builder -> builder.runInLegacyMode(runInLegacyMode()))
+				.map(OAuth2ServiceConfigurationBuilder::build)
+				.collect(Collectors.toList());
+		List<OAuth2ServiceConfiguration> iasPlans = serviceBindings.stream()
+				.filter(b -> Service.IAS.equals(Service.from(b.getServiceName().orElse(""))))
+				.map(ServiceBindingMapper::mapToOAuth2ServiceConfigurationBuilder)
+				.filter(Objects::nonNull)
+				.map(OAuth2ServiceConfigurationBuilder::build)
+				.collect(Collectors.toList());
+
+		serviceConfigurations.put(XSUAA, xsuaaPlans);
+		serviceConfigurations.put(IAS, iasPlans);
 	}
 
-	private String extractVcapApplicationJson() {
-		String env = System.getenv(VCAP_APPLICATION);
-		return env != null ? env : "{}";
+	@Override
+	@Nonnull
+	public Type getType() {
+		return Type.CF;
 	}
 
-	OAuth2ServiceConfiguration loadXsuaa() {
+	@Override
+	public OAuth2ServiceConfiguration getXsuaaConfiguration() {
 		Optional<OAuth2ServiceConfiguration> applicationService = Optional
 				.ofNullable(loadForServicePlan(XSUAA, Plan.APPLICATION));
 		Optional<OAuth2ServiceConfiguration> brokerService = Optional
@@ -127,6 +152,25 @@ public class CFEnvironment implements Environment {
 								legacyServiceSimple.orElse(null))));
 	}
 
+	@Nullable
+	@Override
+	public OAuth2ServiceConfiguration getIasConfiguration() {
+		return loadAllForService(IAS).stream().filter(Objects::nonNull).findFirst().orElse(null);
+	}
+
+	@Override
+	public int getNumberOfXsuaaConfigurations() {
+		return loadAllForService(XSUAA).size();
+	}
+
+	@Override
+	public OAuth2ServiceConfiguration getXsuaaConfigurationForTokenExchange() {
+		if (getNumberOfXsuaaConfigurations() > 1) {
+			return loadForServicePlan(XSUAA, Plan.BROKER);
+		}
+		return getXsuaaConfiguration();
+	}
+
 	/**
 	 * Loads the configuration for a dedicated service plan.
 	 *
@@ -145,4 +189,23 @@ public class CFEnvironment implements Environment {
 				.orElse(null);
 	}
 
+	/**
+	 * Loads all configurations of all service instances of the dedicated service.
+	 *
+	 * @param service
+	 *            the service name
+	 * @return the list of all found configurations or empty list, in case there are
+	 *         no service bindings.
+	 */
+	List<OAuth2ServiceConfiguration> loadAllForService(Service service) {
+		return serviceConfigurations.getOrDefault(service, Collections.emptyList());
+	}
+
+	private boolean runInLegacyMode() {
+		String vcapApplicationJson = environmentVariableReader.apply(VCAP_APPLICATION);
+		if (vcapApplicationJson != null) {
+			return new DefaultJsonObject(vcapApplicationJson).contains("xs_api");
+		}
+		return false;
+	}
 }
diff --git a/env/src/main/java/com/sap/cloud/security/config/k8s/K8sEnvironment.java b/env/src/main/java/com/sap/cloud/security/config/k8s/K8sEnvironment.java
index 62b3fb1d..435091a5 100644
--- a/env/src/main/java/com/sap/cloud/security/config/k8s/K8sEnvironment.java
+++ b/env/src/main/java/com/sap/cloud/security/config/k8s/K8sEnvironment.java
@@ -7,11 +7,7 @@ package com.sap.cloud.security.config.k8s;
 
 import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingAccessor;
 import com.sap.cloud.environment.servicebinding.api.ServiceBinding;
-import com.sap.cloud.environment.servicebinding.api.TypedMapView;
-import com.sap.cloud.security.config.Environment;
-import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
-import com.sap.cloud.security.config.OAuth2ServiceConfigurationBuilder;
-import com.sap.cloud.security.config.Service;
+import com.sap.cloud.security.config.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -21,9 +17,7 @@ import java.util.*;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
-import static com.sap.cloud.security.config.cf.CFConstants.IAS.DOMAINS;
 import static com.sap.cloud.security.config.cf.CFConstants.SERVICE_PLAN;
-import static com.sap.cloud.security.config.cf.CFConstants.XSUAA.UAA_DOMAIN;
 import static com.sap.cloud.security.config.k8s.K8sConstants.Plan;
 
 /**
@@ -60,48 +54,29 @@ public class K8sEnvironment implements Environment {
 		List<ServiceBinding> serviceBindings = DefaultServiceBindingAccessor.getInstance().getServiceBindings();
 
 		Map<String, OAuth2ServiceConfiguration> xsuaaPlans = serviceBindings.stream()
-				.filter(b -> Service.XSUAA.getCFName().equalsIgnoreCase(b.getServiceName().orElse(null)))
-				.map(this::mapToOAuth2ServiceConfiguration)
+				.filter(b -> Service.XSUAA.equals(Service.from(b.getServiceName().orElse(null))))
+				.map(ServiceBindingMapper::mapToOAuth2ServiceConfigurationBuilder)
+				.filter(Objects::nonNull)
+				.map(OAuth2ServiceConfigurationBuilder::build)
 				.collect(Collectors.toMap(config -> config.getProperty(SERVICE_PLAN),
 						Function.identity()));
 		Map<String, OAuth2ServiceConfiguration> identityPlans = serviceBindings.stream()
-				.filter(b -> Service.IAS.getCFName().equalsIgnoreCase(b.getServiceName().orElse(null)))
-				.map(this::mapToOAuth2ServiceConfiguration)
+				.filter(b -> Service.IAS.equals(Service.from(b.getServiceName().orElse(null))))
+				.map(ServiceBindingMapper::mapToOAuth2ServiceConfigurationBuilder)
+				.filter(Objects::nonNull)
+				.map(OAuth2ServiceConfigurationBuilder::build)
 				.collect(Collectors.toMap(config -> config.getProperty(SERVICE_PLAN),
 						Function.identity()));
 		serviceConfigurations.put(Service.XSUAA, xsuaaPlans);
 		serviceConfigurations.put(Service.IAS, identityPlans);
 	}
 
-	private OAuth2ServiceConfiguration mapToOAuth2ServiceConfiguration(ServiceBinding b) {
-		if (!b.getServiceName().isPresent()) {
-			LOGGER.error("Ignores Service Binding with name {} as service name is not provided.", b.getName());
-			return null; // as of now, method is never called when service name isn't given
-		}
-		final Service service = Service.from(b.getServiceName().get());
-		OAuth2ServiceConfigurationBuilder configBuilder = OAuth2ServiceConfigurationBuilder.forService(service)
-				.withProperties(TypedMapView.ofCredentials(b).getEntries(String.class))
-				.withProperty(SERVICE_PLAN, b.getServicePlan().orElse(Plan.APPLICATION.name()).toUpperCase());
-		switch (service) {
-		case XSUAA:
-			configBuilder.withProperty(UAA_DOMAIN,
-					(String) b.getCredentials().get(UAA_DOMAIN));
-			break;
-		case IAS:
-			List<String> domains = TypedMapView.ofCredentials(b).getListView(DOMAINS).getItems(String.class);
-			LOGGER.info("first domain : {}", domains.get(0));
-			configBuilder.withDomains(domains.toArray(new String[] {}));
-			break;
-		}
-		return configBuilder.build();
-	}
-
 	/**
 	 * Loads all configurations of all service instances of the dedicated service.
 	 *
 	 * @param service
 	 *            the service name
-	 * @return the list of all found configurations or empty map, in case there are
+	 * @return the map of all found configurations or empty map, in case there are
 	 *         no service bindings.
 	 */
 	Map<String, OAuth2ServiceConfiguration> getServiceConfigurationsOf(Service service) {
diff --git a/java-api/src/main/java/com/sap/cloud/security/annotation/Beta.java b/java-api/src/main/java/com/sap/cloud/security/annotation/Beta.java
new file mode 100644
index 00000000..4e04b186
--- /dev/null
+++ b/java-api/src/main/java/com/sap/cloud/security/annotation/Beta.java
@@ -0,0 +1,12 @@
+/**
+ * SPDX-FileCopyrightText: 2018-2023 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.sap.cloud.security.annotation;
+
+/**
+ * Signifies that a public API (public class, method or field) is subject to incompatible changes, or even removal, in a future release.
+ */
+public @interface Beta {
+}
diff --git a/java-api/src/main/java/com/sap/cloud/security/token/Token.java b/java-api/src/main/java/com/sap/cloud/security/token/Token.java
index 7e461ea5..51f3f162 100644
--- a/java-api/src/main/java/com/sap/cloud/security/token/Token.java
+++ b/java-api/src/main/java/com/sap/cloud/security/token/Token.java
@@ -300,7 +300,7 @@ public interface Token extends Serializable {
 	 *     token.getAttributeFromClaimAsString(XS_USER_ATTRIBUTES, "custom_role");
 	 *     </code>
 	 *
-	 * @return the String value of a claim attribute or empty List if claim or its
+	 * @return the list of String values of a claim attribute or empty List if claim or its
 	 *         attribute does not exist.
 	 **/
 	default List<String> getAttributeFromClaimAsStringList(String claimName, String attributeName) {
diff --git a/java-security-it/pom.xml b/java-security-it/pom.xml
index d5b8f52c..4aa62888 100644
--- a/java-security-it/pom.xml
+++ b/java-security-it/pom.xml
@@ -57,6 +57,7 @@
             <groupId>org.junit.vintage</groupId>
             <artifactId>junit-vintage-engine</artifactId>
             <version>${junit-jupiter.version}</version>
+            <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.junit.jupiter</groupId>
@@ -86,10 +87,20 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-failsafe-plugin</artifactId>
                 <version>3.0.0-M4</version>
+                <configuration>
+                    <includes>
+                        <include>**/*.java</include>
+                    </includes>
+                </configuration>
             </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <includes>
+                        <include>**/*.java</include>
+                    </includes>
+                </configuration>
             </plugin>
         </plugins>
     </build>
diff --git a/java-security/src/main/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapter.java b/java-security/src/main/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapter.java
index 3a562e52..d3249a7c 100644
--- a/java-security/src/main/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapter.java
+++ b/java-security/src/main/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapter.java
@@ -405,11 +405,22 @@ public class XSUserInfoAdapter implements XSUserInfo {
 	}
 
 	private String[] getMultiValueAttributeFromExtObject(String claimName, String attributeName) {
-		List<String> claims = accessToken.getAttributeFromClaimAsStringList(claimName, attributeName);
-		if (claims.isEmpty() && !(claims instanceof ArrayList)) {
-			throw new XSUserInfoException(INVALID_USER_ATTRIBUTE + attributeName);
+		List<String> values = null;
+
+		try {
+			values = accessToken.getAttributeFromClaimAsStringList(claimName, attributeName);
+		} catch (JsonParsingException e) {
+			String stringValue = accessToken.getAttributeFromClaimAsString(claimName, attributeName);
+			values = stringValue != null
+					? Collections.singletonList(stringValue)
+					: Collections.emptyList();
+		} finally {
+			if (values == null || (values.isEmpty() && !(values instanceof ArrayList))) {
+				throw new XSUserInfoException(INVALID_USER_ATTRIBUTE + attributeName);
+			}
 		}
-		return claims.toArray(new String[0]);
+
+		return values.toArray(new String[values.size()]);
 	}
 
 	private void checkNotGrantTypeClientCredentials(String methodName) {
diff --git a/pom.xml b/pom.xml
index 9f1b1188..b09ef1c2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,7 +75,7 @@
 		<junit.version>4.13.2</junit.version>
 		<junit.version>4.13.2</junit.version>
 		<junit-jupiter.version>5.7.2</junit-jupiter.version>
-		<maven-surefire-plugin>3.0.0-M8</maven-surefire-plugin>
+		<maven-surefire-plugin>3.0.0-M9</maven-surefire-plugin>
 		<hamcrest.version>1.3</hamcrest.version>
 		<mockito.version>3.12.4</mockito.version>
 		<assertj.version>3.24.2</assertj.version>
@@ -383,7 +383,7 @@
 						<plugin>
 							<groupId>org.owasp</groupId> <!--scans for vulnerabilities-->
 							<artifactId>dependency-check-maven</artifactId>
-							<version>8.0.2</version>
+							<version>8.1.0</version>
 							<executions>
 								<execution>
 									<goals>
@@ -402,7 +402,7 @@
 						<plugin>
 							<groupId>org.apache.maven.plugins</groupId>
 							<artifactId>maven-javadoc-plugin</artifactId>
-							<version>3.4.1</version>
+							<version>3.5.0</version>
 						</plugin>
 						<plugin>
 							<groupId>net.revelc.code.formatter</groupId>
@@ -472,7 +472,7 @@
 					<plugin>
 						<groupId>org.apache.maven.plugins</groupId>
 						<artifactId>maven-javadoc-plugin</artifactId>
-						<version>3.4.1</version>
+						<version>3.5.0</version>
 						<executions>
 							<execution>
 								<id>attach-javadocs</id>
diff --git a/spring-xsuaa-it/pom.xml b/spring-xsuaa-it/pom.xml
index e6e7f460..3a2bdd56 100644
--- a/spring-xsuaa-it/pom.xml
+++ b/spring-xsuaa-it/pom.xml
@@ -114,7 +114,7 @@
 			<plugin>
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-deploy-plugin</artifactId>
-				<version>3.0.0</version>
+				<version>3.1.0</version>
 				<configuration>
 					<skip>true</skip>
 				</configuration>


Test patch:
diff --git a/env/src/test/java/com/sap/cloud/security/config/EnvironmentsK8sTest.java b/env/src/test/java/com/sap/cloud/security/config/EnvironmentsK8sTest.java
index 1f373e41..29b0e6e4 100644
--- a/env/src/test/java/com/sap/cloud/security/config/EnvironmentsK8sTest.java
+++ b/env/src/test/java/com/sap/cloud/security/config/EnvironmentsK8sTest.java
@@ -5,29 +5,25 @@
  */
 package com.sap.cloud.security.config;
 
-import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingAccessor;
 import com.sap.cloud.environment.servicebinding.SapServiceOperatorLayeredServiceBindingAccessor;
+import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingAccessor;
 import org.assertj.core.api.Assertions;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import uk.org.webcompere.systemstubs.environment.EnvironmentVariables;
-import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;
 
 import java.nio.file.Paths;
 
 import static com.sap.cloud.environment.servicebinding.SapServiceOperatorLayeredServiceBindingAccessor.DEFAULT_PARSING_STRATEGIES;
 import static com.sap.cloud.security.config.k8s.K8sConstants.KUBERNETES_SERVICE_HOST;
 
-@ExtendWith(SystemStubsExtension.class)
 class EnvironmentsK8sTest {
 
 	private static final String K8S_HOST_VALUE = "0.0.0.0";
 
 	@BeforeAll
-	static void beforeAll(EnvironmentVariables environmentVariables) {
-		environmentVariables.set(KUBERNETES_SERVICE_HOST, K8S_HOST_VALUE);
+	static void beforeAll() {
+		Environments.setEnvironmentVariableReader(var -> KUBERNETES_SERVICE_HOST.equals(var) ? K8S_HOST_VALUE : System.getenv(var));
 		DefaultServiceBindingAccessor.setInstance(new SapServiceOperatorLayeredServiceBindingAccessor(
 				Paths.get(EnvironmentsK8sTest.class.getResource("/k8s").getPath()), DEFAULT_PARSING_STRATEGIES));
 	}
diff --git a/env/src/test/java/com/sap/cloud/security/config/cf/CFEnvironmentTest.java b/env/src/test/java/com/sap/cloud/security/config/cf/CFEnvironmentTest.java
index 8f40c124..13ef986c 100644
--- a/env/src/test/java/com/sap/cloud/security/config/cf/CFEnvironmentTest.java
+++ b/env/src/test/java/com/sap/cloud/security/config/cf/CFEnvironmentTest.java
@@ -1,42 +1,35 @@
 /**
  * SPDX-FileCopyrightText: 2018-2022 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors
- *
+ * <p>
  * SPDX-License-Identifier: Apache-2.0
  */
 package com.sap.cloud.security.config.cf;
 
+import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingAccessor;
 import com.sap.cloud.security.config.Environment;
 import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
 import com.sap.cloud.security.config.Service;
 import com.sap.cloud.security.json.DefaultJsonObject;
 import com.sap.cloud.security.json.JsonObject;
-
-import com.sap.cloud.security.json.JsonParsingException;
 import org.apache.commons.io.IOUtils;
-
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.contrib.java.lang.system.EnvironmentVariables;
+import org.mockito.Mockito;
 
 import java.io.IOException;
 import java.util.Map;
+import java.util.function.UnaryOperator;
 
 import static com.sap.cloud.security.config.cf.CFConstants.*;
-import static com.sap.cloud.security.config.cf.CFConstants.SERVICE_PLAN;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 public class CFEnvironmentTest {
 
-	@Rule
-	public final EnvironmentVariables environmentVariables = new EnvironmentVariables();
-
-	private String vcapXsuaa;
-	private String vcapMultipleXsuaa;
-	private String vcapIas;
-	private String vcapXsa;
+	private final String vcapXsuaa;
+	private final String vcapMultipleXsuaa;
+	private final String vcapIas;
+	private final String vcapXsa;
 	private CFEnvironment cut;
 
 	public CFEnvironmentTest() throws IOException {
@@ -48,7 +41,7 @@ public class CFEnvironmentTest {
 
 	@Before
 	public void setUp() {
-		cut = CFEnvironment.getInstance((str) -> vcapXsuaa, (str) -> null);
+		cut = CFEnvironment.getInstance((str) -> vcapXsuaa);
 	}
 
 	@Test
@@ -59,8 +52,7 @@ public class CFEnvironmentTest {
 
 	@Test
 	public void getCFServiceConfigurationAndCredentialsAsMap() {
-		String vcapServices = vcapXsuaa;
-		JsonObject serviceJsonObject = new DefaultJsonObject(vcapServices).getJsonObjects(Service.XSUAA.getCFName())
+		JsonObject serviceJsonObject = new DefaultJsonObject(vcapXsuaa).getJsonObjects(Service.XSUAA.getCFName())
 				.get(0);
 		Map<String, String> xsuaaConfigMap = serviceJsonObject.getKeyValueMap();
 		Map<String, String> credentialsMap = serviceJsonObject.getJsonObject(CREDENTIALS).getKeyValueMap();
@@ -69,19 +61,9 @@ public class CFEnvironmentTest {
 		assertThat(credentialsMap).hasSize(10).containsEntry(CLIENT_SECRET, "secret");
 	}
 
-	@Test
-	public void getCorruptConfiguration_raisesException() {
-		String xsuaaBinding = "{\"xsuaa\": [{ \"credentials\": null }]}";
-
-		assertThatThrownBy(() -> cut = CFEnvironment.getInstance((str) -> xsuaaBinding, (str) -> null))
-				.isInstanceOf(JsonParsingException.class).hasMessageContainingAll(
-						"The credentials of 'VCAP_SERVICES' can not be parsed for service 'XSUAA'",
-						"Please check the service binding.");
-	}
-
 	@Test
 	public void getConfigurationOfOneIasInstance() {
-		cut = CFEnvironment.getInstance((str) -> vcapIas, (str) -> null);
+		cut = CFEnvironment.getInstance((str) -> vcapIas);
 		assertThat(cut.getIasConfiguration()).isSameAs(cut.getIasConfiguration());
 		assertThat(cut.getIasConfiguration().getService()).isEqualTo(Service.IAS);
 		assertThat(cut.getIasConfiguration().getClientId()).isEqualTo("T000310");
@@ -112,16 +94,18 @@ public class CFEnvironmentTest {
 
 	@Test
 	public void getConfigurationOfXsuaaInstanceInXsaSystem() {
-		environmentVariables.set(VCAP_SERVICES, vcapXsa);
-		environmentVariables.set(VCAP_APPLICATION, "{\"xs_api\": \"anyvalue\"}");
-		assertThat(System.getenv(VCAP_APPLICATION)).isNotNull();
-		cut = CFEnvironment.getInstance();
+		UnaryOperator<String> envVarReaderMock = Mockito.mock(UnaryOperator.class);
+		Mockito.when(envVarReaderMock.apply(VCAP_SERVICES)).thenReturn(vcapXsa);
+		Mockito.when(envVarReaderMock.apply(VCAP_APPLICATION)).thenReturn("{\"xs_api\": \"anyvalue\"}");
+
+		cut = CFEnvironment.getInstance(envVarReaderMock);
 
 		assertThat(cut.getXsuaaConfiguration().getService()).isEqualTo(Service.XSUAA);
 		assertThat(Plan.from(cut.getXsuaaConfiguration().getProperty(SERVICE_PLAN))).isEqualTo(Plan.SPACE);
 		assertThat(cut.getXsuaaConfiguration().getClientId()).isEqualTo("sb-java-hello-world!i1");
 		assertThat(cut.getXsuaaConfiguration().getProperty(XSUAA.APP_ID)).isEqualTo("java-hello-world!i1");
-		assertThat(cut.getXsuaaConfiguration().getClientSecret()).startsWith("fxnWLHqLh6KC0Wp/bbv8Gwbu50OEbpS");
+		assertThat(cut.getXsuaaConfiguration().getClientSecret())
+				.startsWith("fxnWLHqLh6KC0Wp/bbv8Gwbu50OEbpS");
 		assertThat(cut.getXsuaaConfiguration().getUrl())
 				.hasToString("https://xsa-test.c.eu-de-2.cloud.sap:30132/uaa-security");
 		assertThat(cut.getXsuaaConfiguration().isLegacyMode()).isTrue();
@@ -132,7 +116,7 @@ public class CFEnvironmentTest {
 
 	@Test
 	public void getConfigurationOfMultipleInstance() {
-		cut = CFEnvironment.getInstance((str) -> vcapMultipleXsuaa, (str) -> null);
+		cut = CFEnvironment.getInstance((str) -> vcapMultipleXsuaa);
 
 		assertThat(cut.getNumberOfXsuaaConfigurations()).isEqualTo(2);
 		OAuth2ServiceConfiguration appServConfig = cut.getXsuaaConfiguration();
@@ -149,7 +133,7 @@ public class CFEnvironmentTest {
 
 	@Test
 	public void getConfigurationByPlan() {
-		cut = CFEnvironment.getInstance((str) -> vcapMultipleXsuaa, (str) -> null);
+		cut = CFEnvironment.getInstance((str) -> vcapMultipleXsuaa);
 
 		OAuth2ServiceConfiguration appServConfig = cut.loadForServicePlan(Service.XSUAA,
 				Plan.APPLICATION);
@@ -165,6 +149,17 @@ public class CFEnvironmentTest {
 
 	@Test
 	public void getXsuaaServiceConfiguration_usesSystemProperties() {
+		System.setProperty(VCAP_SERVICES, vcapMultipleXsuaa);
+
+		cut = CFEnvironment.getInstance();
+		OAuth2ServiceConfiguration serviceConfiguration = cut.getXsuaaConfiguration();
+
+		assertThat(serviceConfiguration).isNotNull();
+		assertThat(cut.getNumberOfXsuaaConfigurations()).isEqualTo(2);
+	}
+
+	@Test
+	public void getXsuaaServiceConfiguration_prioritizesSystemProperties() {
 		cut = CFEnvironment.getInstance((str) -> vcapXsuaa, (str) -> vcapMultipleXsuaa);
 
 		OAuth2ServiceConfiguration serviceConfiguration = cut.getXsuaaConfiguration();
@@ -175,7 +170,7 @@ public class CFEnvironmentTest {
 
 	@Test
 	public void getServiceConfiguration_vcapServicesNotAvailable_returnsNull() {
-		cut = CFEnvironment.getInstance((str) -> null, (str) -> null);
+		cut = CFEnvironment.getInstance((str) -> null);
 
 		assertThat(cut.getXsuaaConfiguration()).isNull();
 		assertThat(cut.getNumberOfXsuaaConfigurations()).isZero();
@@ -190,7 +185,7 @@ public class CFEnvironmentTest {
 		String allBindings = "{\"xsuaa\": ["
 				+ "{\"plan\": \"broker\", \"credentials\": {}},"
 				+ "{\"plan\": \"application\", \"credentials\": {}}]}";
-		cut = CFEnvironment.getInstance((str) -> allBindings, (str) -> null);
+		cut = CFEnvironment.getInstance((str) -> allBindings);
 
 		OAuth2ServiceConfiguration config = cut.getXsuaaConfiguration();
 		assertThat(Plan.from(config.getProperty(SERVICE_PLAN))).isEqualTo(Plan.APPLICATION);
@@ -201,7 +196,7 @@ public class CFEnvironmentTest {
 		String allBindings = "{\"xsuaa\": ["
 				+ "{\"plan\": \"default\", \"credentials\": {}},"
 				+ "{\"plan\": \"space\", \"credentials\": {}}]}";
-		cut = CFEnvironment.getInstance((str) -> allBindings, (str) -> null);
+		cut = CFEnvironment.getInstance((str) -> allBindings);
 
 		OAuth2ServiceConfiguration config = cut.getXsuaaConfiguration();
 		assertThat(Plan.from(config.getProperty(SERVICE_PLAN))).isEqualTo(Plan.SPACE);
@@ -209,7 +204,7 @@ public class CFEnvironmentTest {
 
 	@Test
 	public void getXsuaaConfiguration_noVcapServices_doesNotThrowExceptions() {
-		cut = CFEnvironment.getInstance((any) -> null, (any) -> null);
+		cut = CFEnvironment.getInstance((any) -> null);
 
 		assertThat(cut.getXsuaaConfiguration()).isNull();
 		assertThat(cut.getNumberOfXsuaaConfigurations()).isZero();
@@ -217,7 +212,7 @@ public class CFEnvironmentTest {
 
 	@Test
 	public void getXsuaaConfiguration_vcapServicesEmptyString_doesNotThrowExceptions() {
-		cut = CFEnvironment.getInstance((any) -> "", (any) -> "");
+		cut = CFEnvironment.getInstance((any) -> "");
 
 		assertThat(cut.getXsuaaConfiguration()).isNull();
 		assertThat(cut.getNumberOfXsuaaConfigurations()).isZero();
@@ -225,9 +220,16 @@ public class CFEnvironmentTest {
 
 	@Test
 	public void getXsuaaConfiguration_vcapServicesEmptyJson_doesNotThrowExceptions() {
-		cut = CFEnvironment.getInstance((any) -> "{}", (any) -> "{}");
+		cut = CFEnvironment.getInstance((any) -> "{}");
 
 		assertThat(cut.getXsuaaConfiguration()).isNull();
 		assertThat(cut.getNumberOfXsuaaConfigurations()).isZero();
 	}
+
+	@Test
+	public void getXsuaaConfiguration_vcapServicesNoServiceName_doesNotThrowExceptions() {
+		String xsuaaBinding = "{\"\": [{ \"credentials\": null }]}";
+		cut = CFEnvironment.getInstance((str) -> xsuaaBinding);
+		assertThat(cut.getXsuaaConfiguration()).isNull();
+	}
 }
\ No newline at end of file
diff --git a/env/src/test/resources/vcapXsuaaServiceSingleBinding.json b/env/src/test/resources/vcapXsuaaServiceSingleBinding.json
index 28c0f716..00d79f4d 100644
--- a/env/src/test/resources/vcapXsuaaServiceSingleBinding.json
+++ b/env/src/test/resources/vcapXsuaaServiceSingleBinding.json
@@ -1,29 +1,29 @@
 {
   "xsuaa": [
-   {
-    "binding_name": null,
-    "credentials": {
-     "clientid": "clientId",
-     "clientsecret": "secret",
-     "identityzone": "uaa",
-     "identityzoneid": "uaa",
-     "sburl": "http://localhost/uaa",
-     "tenantmode": "dedicated",
-     "uaadomain": "auth.com",
-     "url": "https://paastenant.auth.com",
-     "verificationkey": "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm1QaZzMjtEfHdimrHP3/2Yr+1z685eiOUlwybRVG9i8wsgOUh+PUGuQL8hgulLZWXU5MbwBLTECAEMQbcRTNVTolkq4i67EP6JesHJIFADbK1Ni0KuMcPuiyOLvDKiDEMnYG1XP3X3WCNfsCVT9YoU+lWIrZr/ZsIvQri8jczr4RkynbTBsPaAOygPUlipqDrpadMO1momNCbea/o6GPn38LxEw609ItfgDGhL6f/yVid5pFzZQWb+9l6mCuJww0hnhO6gt6Rv98OWDty9G0frWAPyEfuIW9B+mR/2vGhyU9IbbWpvFXiy9RVbbsM538TCjd5JF2dJvxy24addC4oQIDAQAB-----END PUBLIC KEY-----",
-     "xsappname": "java-hello-world"
-    },
-    "instance_name": "my-xsuaa",
-    "label": "xsuaa",
-    "name": "example-xsuaa",
-    "plan": "broker",
-    "provider": null,
-    "syslog_drain_url": null,
-    "tags": [
-     "xsuaa"
-    ],
-    "volume_mounts": []
-   }
+    {
+      "name": "example-xsuaa",
+      "label": "xsuaa",
+      "binding_name": null,
+      "instance_name": "my-xsuaa",
+      "plan": "broker",
+      "provider": null,
+      "syslog_drain_url": null,
+      "tags": [
+        "xsuaa"
+      ],
+      "volume_mounts": [],
+      "credentials": {
+        "clientid": "clientId",
+        "clientsecret": "secret",
+        "identityzone": "uaa",
+        "identityzoneid": "uaa",
+        "sburl": "http://localhost/uaa",
+        "tenantmode": "dedicated",
+        "uaadomain": "auth.com",
+        "url": "https://paastenant.auth.com",
+        "verificationkey": "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm1QaZzMjtEfHdimrHP3/2Yr+1z685eiOUlwybRVG9i8wsgOUh+PUGuQL8hgulLZWXU5MbwBLTECAEMQbcRTNVTolkq4i67EP6JesHJIFADbK1Ni0KuMcPuiyOLvDKiDEMnYG1XP3X3WCNfsCVT9YoU+lWIrZr/ZsIvQri8jczr4RkynbTBsPaAOygPUlipqDrpadMO1momNCbea/o6GPn38LxEw609ItfgDGhL6f/yVid5pFzZQWb+9l6mCuJww0hnhO6gt6Rv98OWDty9G0frWAPyEfuIW9B+mR/2vGhyU9IbbWpvFXiy9RVbbsM538TCjd5JF2dJvxy24addC4oQIDAQAB-----END PUBLIC KEY-----",
+        "xsappname": "java-hello-world"
+      }
+    }
   ]
- }
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/env/src/test/resources/vcapXsuaaXsaSingleBinding.json b/env/src/test/resources/vcapXsuaaXsaSingleBinding.json
index 7027bcad..b37a9218 100644
--- a/env/src/test/resources/vcapXsuaaXsaSingleBinding.json
+++ b/env/src/test/resources/vcapXsuaaXsaSingleBinding.json
@@ -1,18 +1,22 @@
 {
-  "xsuaa" : [ {
-    "name" : "java-uaa",
-    "label" : "xsuaa",
-    "tags" : [ "xsuaa" ],
-    "plan" : "space",
-    "credentials" : {
-      "tenantmode" : "dedicated",
-      "clientid" : "sb-java-hello-world!i1",
-      "verificationkey" : "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwOzNGEDK6jNrJcl3L2wstY8SnX9KNo4ic/JtrEQX4U/+749b9y6JlSGOXSKBFJ6alNGHVyPK66SfSgeWHwPjVekdnHYM7bQTyYX1NoSOxqUgvsktIpxeYJLC+J5qJTtnFcjFTRUovKwtCYxlTP4b63b7dWixwc+iSnkuvl4IkZjVTcstK73eTlmVtJDC5DIisPyYOK06qxTPrLC8KlgFA6a0FdPJF9G8s/ElxPHFu7mw6UKn2LTmDlDbADQZLMXGKBw/zg3l5scjxkOCVGTkGvv1ooccqCfYgZAsmsPbQdoZxznSdQTPfaWULnYLo8i5hFXiS1q8agvXHjWpUmWcMwIDAQAB-----END PUBLIC KEY-----",
-      "xsappname" : "java-hello-world!i1",
-      "identityzone" : "uaa",
-      "identityzoneid" : "uaa",
-      "clientsecret" : "fxnWLHqLh6KC0Wp/bbv8Gwbu50OEbpSvVYTiI/kZILsR/dYH8YL2V5tP1buzke9QmqK9AruntTAI\nKu4enzCTNw==",
-      "url" : "https://xsa-test.c.eu-de-2.cloud.sap:30132/uaa-security"
-  }
-  } ]
+  "xsuaa": [
+    {
+      "name": "java-uaa",
+      "label": "xsuaa",
+      "tags": [
+        "xsuaa"
+      ],
+      "plan": "space",
+      "credentials": {
+        "tenantmode": "dedicated",
+        "clientid": "sb-java-hello-world!i1",
+        "verificationkey": "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwOzNGEDK6jNrJcl3L2wstY8SnX9KNo4ic/JtrEQX4U/+749b9y6JlSGOXSKBFJ6alNGHVyPK66SfSgeWHwPjVekdnHYM7bQTyYX1NoSOxqUgvsktIpxeYJLC+J5qJTtnFcjFTRUovKwtCYxlTP4b63b7dWixwc+iSnkuvl4IkZjVTcstK73eTlmVtJDC5DIisPyYOK06qxTPrLC8KlgFA6a0FdPJF9G8s/ElxPHFu7mw6UKn2LTmDlDbADQZLMXGKBw/zg3l5scjxkOCVGTkGvv1ooccqCfYgZAsmsPbQdoZxznSdQTPfaWULnYLo8i5hFXiS1q8agvXHjWpUmWcMwIDAQAB-----END PUBLIC KEY-----",
+        "xsappname": "java-hello-world!i1",
+        "identityzone": "uaa",
+        "identityzoneid": "uaa",
+        "clientsecret": "fxnWLHqLh6KC0Wp/bbv8Gwbu50OEbpSvVYTiI/kZILsR/dYH8YL2V5tP1buzke9QmqK9AruntTAI\nKu4enzCTNw==",
+        "url": "https://xsa-test.c.eu-de-2.cloud.sap:30132/uaa-security"
+      }
+    }
+  ]
 }
\ No newline at end of file
diff --git a/java-security-it/src/test/java/com/sap/cloud/security/test/performance/SpringXsuaaPerformanceIT.java b/java-security-it/src/test/java/com/sap/cloud/security/test/performance/SpringXsuaaPerformanceIT.java
index 450e18bc..2ad69910 100644
--- a/java-security-it/src/test/java/com/sap/cloud/security/test/performance/SpringXsuaaPerformanceIT.java
+++ b/java-security-it/src/test/java/com/sap/cloud/security/test/performance/SpringXsuaaPerformanceIT.java
@@ -8,7 +8,6 @@ package com.sap.cloud.security.test.performance;
 import com.sap.cloud.security.test.SecurityTest;
 import com.sap.cloud.security.test.performance.util.BenchmarkUtil;
 import com.sap.cloud.security.xsuaa.XsuaaCredentials;
-import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
 import com.sap.cloud.security.xsuaa.XsuaaServiceConfigurationCustom;
 import com.sap.cloud.security.xsuaa.token.authentication.XsuaaJwtDecoderBuilder;
 import org.apache.commons.io.IOUtils;
@@ -32,12 +31,16 @@ class SpringXsuaaPerformanceIT {
 
 	private static final Logger LOGGER = LoggerFactory.getLogger(SpringXsuaaPerformanceIT.class);
 	private static SecurityTest securityTest;
+	private static JwtDecoder jwtDecoder;
+	private static String token;
 
 	@BeforeAll
 	static void setUp() throws Exception {
 		LOGGER.debug(BenchmarkUtil.getSystemInfo());
 		securityTest = new SecurityTest(XSUAA).setKeys("/publicKey.txt", "/privateKey.txt");
+		jwtDecoder = createJwtDecoder();
 		securityTest.setup();
+		token = securityTest.createToken().getTokenValue();
 	}
 
 	@AfterAll
@@ -47,48 +50,37 @@ class SpringXsuaaPerformanceIT {
 
 	@Test
 	void onlineValidation() {
-		String token = securityTest.createToken().getTokenValue();
-		JwtDecoder jwtDecoder = createOnlineJwtDecoder();
+		assertThat(securityTest.getWireMockServer().isRunning()).isTrue();
 		assertThat(jwtDecoder.decode(token)).isNotNull();
 
 		BenchmarkUtil.Result result = BenchmarkUtil.execute(() -> jwtDecoder.decode(token));
-		LOGGER.info("Online validation result: {}", result.toString());
+		LOGGER.info("Online validation result: {}", result);
 	}
 
 	@Test
-	void offlineValidation() throws Exception {
-		String token = securityTest.createToken().getTokenValue();
-		JwtDecoder jwtDecoder = createOfflineJwtDecoder();
+	void offlineValidation() {
+		securityTest.tearDown(); // to test offline validation oauth2 server needs to be shut down
+
+		assertThat(securityTest.getWireMockServer().isRunning()).isFalse();
 		assertThat(jwtDecoder.decode(token)).isNotNull();
 
 		BenchmarkUtil.Result result = BenchmarkUtil.execute(() -> jwtDecoder.decode(token));
-		LOGGER.info("Offline validation result: {}", result.toString());
+		LOGGER.info("Offline validation result: {}", result);
 	}
 
-	private JwtDecoder createOnlineJwtDecoder() {
+	private static JwtDecoder createJwtDecoder() throws IOException {
 		XsuaaServiceConfigurationCustom configuration = new XsuaaServiceConfigurationCustom(createXsuaaCredentials());
 		return new XsuaaJwtDecoderBuilder(configuration).build();
 	}
 
-	private JwtDecoder createOfflineJwtDecoder() throws IOException {
-		final XsuaaCredentials xsuaaCredentials = createXsuaaCredentials();
-		// Workaround because RestOperations cannot easily be switched off
-		xsuaaCredentials.setUaaDomain("__nonExistingUaaDomainForOfflineTesting__");
+	private static XsuaaCredentials createXsuaaCredentials() throws IOException {
 		final String publicKey = IOUtils.resourceToString("/publicKey.txt", StandardCharsets.UTF_8);
-		final XsuaaServiceConfiguration xsuaaConfig = new XsuaaServiceConfigurationCustom(xsuaaCredentials) {
-			@Override
-			public String getVerificationKey() {
-				return publicKey.replace("\n", "");
-			}
-		};
-		return new XsuaaJwtDecoderBuilder(xsuaaConfig).build();
-	}
 
-	private XsuaaCredentials createXsuaaCredentials() {
 		XsuaaCredentials xsuaaCredentials = new XsuaaCredentials();
 		xsuaaCredentials.setUaaDomain(SecurityTest.DEFAULT_DOMAIN);
 		xsuaaCredentials.setClientId(SecurityTest.DEFAULT_CLIENT_ID);
 		xsuaaCredentials.setXsAppName(SecurityTest.DEFAULT_APP_ID);
+		xsuaaCredentials.setVerificationKey(publicKey.replace("\n", ""));
 		return xsuaaCredentials;
 	}
 
diff --git a/java-security-it/src/test/resources/vcap_services-multiple.json b/java-security-it/src/test/resources/vcap_services-multiple.json
index 79cab892..5f9a8324 100644
--- a/java-security-it/src/test/resources/vcap_services-multiple.json
+++ b/java-security-it/src/test/resources/vcap_services-multiple.json
@@ -28,7 +28,8 @@
    "credentials": {
     "clientsecret": "secret",
     "clientid": "T000310",
-    "url": "http://localhost"
+    "url": "http://localhost",
+    "domains": ["localhost"]
    },
    "instance_name": "myservice",
    "label": "identity",
diff --git a/java-security/src/test/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapterIntegrationTest.java b/java-security/src/test/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapterIntegrationTest.java
index 1bc93908..dca64b8e 100644
--- a/java-security/src/test/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapterIntegrationTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapterIntegrationTest.java
@@ -5,13 +5,13 @@
  */
 package com.sap.cloud.security.adapter.xs;
 
+import com.sap.cloud.security.config.ClientCredentials;
 import com.sap.cloud.security.config.ClientIdentity;
 import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
 import com.sap.cloud.security.config.cf.CFConstants;
 import com.sap.cloud.security.config.cf.CFEnvironment;
 import com.sap.cloud.security.token.XsuaaScopeConverter;
 import com.sap.cloud.security.token.XsuaaToken;
-import com.sap.cloud.security.config.ClientCredentials;
 import com.sap.cloud.security.xsuaa.client.OAuth2ServiceException;
 import com.sap.cloud.security.xsuaa.client.OAuth2TokenResponse;
 import com.sap.cloud.security.xsuaa.client.OAuth2TokenService;
@@ -23,9 +23,7 @@ import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.junit.*;
-import org.junit.contrib.java.lang.system.RestoreSystemProperties;
 import org.junit.rules.ExpectedException;
-import org.junit.rules.TestRule;
 import org.mockito.Mockito;
 
 import java.io.IOException;
@@ -52,25 +50,23 @@ public class XSUserInfoAdapterIntegrationTest {
 	private XSUserInfo correctEnduserInfo;
 	private XSUserInfo correctEnduserInfoWithUaaUser;
 	private OAuth2TokenService oAuth2TokenService;
+	private OAuth2ServiceConfiguration xsuaaConfiguration;
 
-	@Rule
-	public final TestRule restoreSystemProperties = new RestoreSystemProperties();
 
 	@Before
 	public void setup() throws XSUserInfoException, IOException, JSONException {
 		oAuth2TokenService = Mockito.mock(OAuth2TokenService.class);
 		String vcapServices = buildVcapServices("java-hello-world", "default");
-		infoUser = createToken(readData("/token_user.txt"), vcapServices);
-		infoUserNoAttr = createToken(readData("/token_user_noattr.txt"), vcapServices);
-		infoCc = createToken(readData("/token_cc.txt"), vcapServices);
-		infoCcNoAttr = createToken(readData("/token_cc_noattr.txt"), vcapServices);
-		correctEnduserInfo = createToken(readData("/correctEndUserToken.txt"), vcapServices);
-		correctEnduserInfoWithUaaUser = createToken(readData("/correctEndUserTokenUaaUser.txt"), vcapServices);
+		xsuaaConfiguration = CFEnvironment.getInstance(any -> vcapServices).getXsuaaConfiguration();
+		infoUser = createToken(readData("/token_user.txt"));
+		infoUserNoAttr = createToken(readData("/token_user_noattr.txt"));
+		infoCc = createToken(readData("/token_cc.txt"));
+		infoCcNoAttr = createToken(readData("/token_cc_noattr.txt"));
+		correctEnduserInfo = createToken(readData("/correctEndUserToken.txt"));
+		correctEnduserInfoWithUaaUser = createToken(readData("/correctEndUserTokenUaaUser.txt"));
 	}
 
-	private XSUserInfo createToken(String token, String vcapServices) {
-		System.setProperty(CFConstants.VCAP_SERVICES, vcapServices); // restored by RestoreSystemProperties rule
-		OAuth2ServiceConfiguration xsuaaConfiguration = CFEnvironment.getInstance().getXsuaaConfiguration();
+	private XSUserInfo createToken(String token) {
 		String appId = xsuaaConfiguration.getProperty(CFConstants.XSUAA.APP_ID);
 		XsuaaToken accessToken = new XsuaaToken(token).withScopeConverter(new XsuaaScopeConverter(appId));
 		XSUserInfoAdapter xsUserInfoAdapter = new XSUserInfoAdapter(accessToken, xsuaaConfiguration);
@@ -181,14 +177,14 @@ public class XSUserInfoAdapterIntegrationTest {
 	public void checkLocalScopeVcapServicesOneBinding() throws XSUserInfoException, IOException, JSONException {
 		// test default plan binding with default plan token
 		String token = readData("/token_user.txt");
-		XSUserInfo userInfo = createToken(token, buildVcapServices("cloud_controller", "default"));
+		XSUserInfo userInfo = createToken(token);
 		Assert.assertFalse(userInfo.checkLocalScope("read"));
-		userInfo = createToken(token, buildVcapServices("java-hello-world", "default"));
+		userInfo = createToken(token);
 		Assert.assertTrue(userInfo.checkLocalScope("Display"));
 		// test application plan binding with default plan token
-		userInfo = createToken(token, buildVcapServices("cloud_controller", "application"));
+		userInfo = createToken(token);
 		Assert.assertFalse(userInfo.checkLocalScope("read"));
-		userInfo = createToken(token, buildVcapServices("java-hello-world", "application"));
+		userInfo = createToken(token);
 		Assert.assertTrue(userInfo.checkLocalScope("Display"));
 	}
 
@@ -206,16 +202,16 @@ public class XSUserInfoAdapterIntegrationTest {
 		String vcapServices = buildVcapServices("cloud_controller", "default",
 				"java-hello-world!t5", "application");
 		String token = readData("/token_user.txt");
-		XSUserInfo userInfo = createToken(token, vcapServices);
+		XSUserInfo userInfo = createToken(token);
 		Assert.assertFalse(userInfo.checkLocalScope("read"));
 		vcapServices = buildVcapServices("java-hello-world", "default",
 				"java-hello-world!t5", "application");
-		userInfo = createToken(token, vcapServices);
+		userInfo = createToken(token);
 		Assert.assertTrue(userInfo.checkLocalScope("Display"));
 
 		// test broker & application plan binding with default plan token
 		vcapServices = buildVcapServices("cloud_controller!b4", "broker", "java-hello-world!t5", "application");
-		userInfo = createToken(token, vcapServices);
+		userInfo = createToken(token);
 		try {
 			Assert.assertFalse(userInfo.checkLocalScope("read"));
 			fail();
@@ -224,7 +220,7 @@ public class XSUserInfoAdapterIntegrationTest {
 					e.getMessage());
 		}
 		vcapServices = buildVcapServices("java-hello-world!b4", "broker", "java-hello-world!t5", "application");
-		userInfo = createToken(token, vcapServices);
+		userInfo = createToken(token);
 		try {
 			Assert.assertFalse(userInfo.checkLocalScope("Display"));
 			fail();
@@ -234,7 +230,7 @@ public class XSUserInfoAdapterIntegrationTest {
 		}
 		// test two default plan binding with default plan token
 		vcapServices = buildVcapServices("cloud_controller", "default", "node-hello-world", "default");
-		userInfo = createToken(token, vcapServices);
+		userInfo = createToken(token);
 		try {
 			Assert.assertFalse(userInfo.checkLocalScope("read"));
 			fail();
@@ -243,7 +239,7 @@ public class XSUserInfoAdapterIntegrationTest {
 					e.getMessage());
 		}
 		vcapServices = buildVcapServices("cloud_controller", "default", "java-hello-world", "default");
-		userInfo = createToken(token, vcapServices);
+		userInfo = createToken(token);
 		try {
 			Assert.assertFalse(userInfo.checkLocalScope("Display"));
 			fail();
@@ -253,7 +249,7 @@ public class XSUserInfoAdapterIntegrationTest {
 		}
 		// test two application plan binding with default plan token
 		vcapServices = buildVcapServices("node-hello-world!t5", "application", "java-hello-world!t5", "application");
-		userInfo = createToken(token, vcapServices);
+		userInfo = createToken(token);
 		try {
 			Assert.assertFalse(userInfo.checkLocalScope("read"));
 			fail();
@@ -262,7 +258,7 @@ public class XSUserInfoAdapterIntegrationTest {
 					e.getMessage());
 		}
 		vcapServices = buildVcapServices("node-hello-world!t5", "application", "java-hello-world!t5", "application");
-		userInfo = createToken(token, vcapServices);
+		userInfo = createToken(token);
 		try {
 			Assert.assertFalse(userInfo.checkLocalScope("Display"));
 			fail();
diff --git a/java-security/src/test/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapterTest.java b/java-security/src/test/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapterTest.java
index 6e916d04..05d7985d 100644
--- a/java-security/src/test/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapterTest.java
+++ b/java-security/src/test/java/com/sap/cloud/security/adapter/xs/XSUserInfoAdapterTest.java
@@ -5,13 +5,14 @@
  */
 package com.sap.cloud.security.adapter.xs;
 
+import com.sap.cloud.security.config.ClientCredentials;
 import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
 import com.sap.cloud.security.config.OAuth2ServiceConfigurationBuilder;
 import com.sap.cloud.security.config.Service;
 import com.sap.cloud.security.config.cf.CFConstants;
 import com.sap.cloud.security.json.JsonObject;
+import com.sap.cloud.security.json.JsonParsingException;
 import com.sap.cloud.security.token.*;
-import com.sap.cloud.security.config.ClientCredentials;
 import com.sap.cloud.security.xsuaa.client.OAuth2TokenResponse;
 import com.sap.cloud.security.xsuaa.tokenflows.ClientCredentialsTokenFlow;
 import com.sap.cloud.security.xsuaa.tokenflows.TokenFlowException;
@@ -19,11 +20,19 @@ import com.sap.cloud.security.xsuaa.tokenflows.UserTokenFlow;
 import com.sap.cloud.security.xsuaa.tokenflows.XsuaaTokenFlows;
 import com.sap.xsa.security.container.XSUserInfoException;
 import org.apache.commons.io.IOUtils;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 import org.mockito.Mockito;
+import org.mockito.stubbing.OngoingStubbing;
 
 import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static com.sap.cloud.security.adapter.xs.XSUserInfoAdapter.*;
 import static com.sap.cloud.security.config.cf.CFConstants.XSUAA.IDENTITY_ZONE;
@@ -33,20 +42,20 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.Mockito.*;
 
-public class XSUserInfoAdapterTest {
+class XSUserInfoAdapterTest {
 
 	private static final String TEST_APP_ID = "testApp";
-	private final XsuaaToken token;
+	private XsuaaToken token;
 	private XSUserInfoAdapter cut;
 	private XsuaaToken emptyToken;
 
-	public XSUserInfoAdapterTest() throws IOException {
+	XSUserInfoAdapterTest() throws IOException {
 		emptyToken = new XsuaaToken(IOUtils.resourceToString("/xsuaaEmptyToken.txt", UTF_8));
 		token = new XsuaaToken(IOUtils.resourceToString("/xsuaaUserInfoAdapterToken.txt", UTF_8));
 	}
 
-	@Before
-	public void setUp() throws XSUserInfoException {
+	@BeforeEach
+	void setUp() throws XSUserInfoException {
 		OAuth2ServiceConfiguration configuration = OAuth2ServiceConfigurationBuilder.forService(Service.XSUAA)
 				.withClientId("sb-clone1!b5|LR-master!b5")
 				.withProperty(CFConstants.XSUAA.APP_ID, "brokerplanmasterapp!b123")
@@ -56,7 +65,7 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void constructors() throws XSUserInfoException {
+	void constructors() throws XSUserInfoException {
 		assertThat(new XSUserInfoAdapter((Object) token).getLogonName()).isEqualTo("TestUser");
 		assertThat(new XSUserInfoAdapter((Token) token).getLogonName()).isEqualTo("TestUser");
 		assertThat(new XSUserInfoAdapter((AccessToken) token).getLogonName()).isEqualTo("TestUser");
@@ -69,12 +78,12 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void testGetLogonName() throws XSUserInfoException {
+	void testGetLogonName() throws XSUserInfoException {
 		assertThat(cut.getLogonName()).isEqualTo("TestUser");
 	}
 
 	@Test
-	public void testGetLogonName_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
+	void testGetLogonName_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
 		cut = new XSUserInfoAdapter(createMockToken(GrantType.CLIENT_CREDENTIALS));
 
 		assertThatThrownBy(() -> cut.getLogonName()).isInstanceOf(XSUserInfoException.class)
@@ -83,12 +92,12 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void testGetGivenName() throws XSUserInfoException {
+	void testGetGivenName() throws XSUserInfoException {
 		assertThat(cut.getGivenName()).isEqualTo("TestUser");
 	}
 
 	@Test
-	public void testGetGivenName_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
+	void testGetGivenName_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
 		cut = new XSUserInfoAdapter(createMockToken(GrantType.CLIENT_CREDENTIALS));
 
 		assertThatThrownBy(() -> cut.getGivenName()).isInstanceOf(XSUserInfoException.class)
@@ -97,12 +106,12 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void testGetFamilyName() throws XSUserInfoException {
+	void testGetFamilyName() throws XSUserInfoException {
 		assertThat(cut.getFamilyName()).isEqualTo("unknown.org");
 	}
 
 	@Test
-	public void testGetFamilyName_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
+	void testGetFamilyName_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
 		cut = new XSUserInfoAdapter(createMockToken(GrantType.CLIENT_CREDENTIALS));
 
 		assertThatThrownBy(() -> cut.getFamilyName()).isInstanceOf(XSUserInfoException.class)
@@ -111,32 +120,32 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void testGetIdentityZone() throws XSUserInfoException {
+	void testGetIdentityZone() throws XSUserInfoException {
 		assertThat(cut.getIdentityZone()).isEqualTo("paas");
 	}
 
 	@Test
-	public void testGetSubdomain() throws XSUserInfoException {
+	void testGetSubdomain() throws XSUserInfoException {
 		assertThat(cut.getSubdomain()).isEqualTo("paas");
 	}
 
 	@Test
-	public void testGetClientId() throws XSUserInfoException {
+	void testGetClientId() throws XSUserInfoException {
 		assertThat(cut.getClientId()).isEqualTo("sb-clone1!b5|LR-master!b5");
 	}
 
 	@Test
-	public void testGetJsonValue() throws XSUserInfoException {
+	void testGetJsonValue() throws XSUserInfoException {
 		assertThat(cut.getJsonValue("azp")).isEqualTo("sb-clone1!b5|LR-master!b5");
 	}
 
 	@Test
-	public void testGetEmail() throws XSUserInfoException {
+	void testGetEmail() throws XSUserInfoException {
 		assertThat(cut.getEmail()).isEqualTo("TestUser@uaa.org");
 	}
 
 	@Test
-	public void testGetEmail_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
+	void testGetEmail_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
 		cut = new XSUserInfoAdapter(createMockToken(GrantType.CLIENT_CREDENTIALS));
 
 		assertThatThrownBy(() -> cut.getEmail()).isInstanceOf(XSUserInfoException.class)
@@ -145,22 +154,22 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void testGetAppToken() throws IOException {
+	void testGetAppToken() throws IOException {
 		assertThat(cut.getAppToken()).isEqualTo(IOUtils.resourceToString("/xsuaaUserInfoAdapterToken.txt", UTF_8));
 	}
 
 	@Test
-	public void getToken_namespaceNotSystem_throwsException() {
+	void getToken_namespaceNotSystem_throwsException() {
 		assertThatThrownBy(() -> cut.getToken("any", "any")).isInstanceOf(XSUserInfoException.class);
 	}
 
 	@Test
-	public void testGetDBToken() throws XSUserInfoException {
+	void testGetDBToken() throws XSUserInfoException {
 		assertThat(cut.getDBToken()).isEqualTo(cut.getAppToken());
 	}
 
 	@Test
-	public void testGetDBToken_onEmptyToken_throwsException() throws XSUserInfoException {
+	void testGetDBToken_onEmptyToken_throwsException() throws XSUserInfoException {
 		cut = new XSUserInfoAdapter(emptyToken);
 
 		assertThatThrownBy(() -> cut.getDBToken()).isInstanceOf(XSUserInfoException.class);
@@ -168,17 +177,17 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void testGetHdbToken() throws XSUserInfoException {
+	void testGetHdbToken() throws XSUserInfoException {
 		assertThat(cut.getHdbToken()).isEqualTo(cut.getAppToken());
 	}
 
 	@Test
-	public void getToken_fallbackToTokenValue() throws XSUserInfoException {
+	void getToken_fallbackToTokenValue() throws XSUserInfoException {
 		assertThat(cut.getToken(XSUserInfoAdapter.SYSTEM, XSUserInfoAdapter.HDB)).isEqualTo(token.getTokenValue());
 	}
 
 	@Test
-	public void getToken_fromExternalContext() throws XSUserInfoException {
+	void getToken_fromExternalContext() throws XSUserInfoException {
 		XsuaaToken mockToken = createMockToken("token");
 
 		cut = new XSUserInfoAdapter(mockToken);
@@ -187,7 +196,7 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void getToken_fromHDBNamedUserSaml() throws XSUserInfoException {
+	void getToken_fromHDBNamedUserSaml() throws XSUserInfoException {
 		String internalToken = "token";
 		XsuaaToken mockToken = createMockToken();
 		when(mockToken.getClaimAsString(HDB_NAMEDUSER_SAML)).thenReturn(internalToken);
@@ -200,24 +209,24 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void testGetAttribute() throws XSUserInfoException {
+	void testGetAttribute() throws XSUserInfoException {
 		String[] attribute = cut.getAttribute("usrAttr");
 		assertThat(attribute).contains("test");
 	}
 
 	@Test
-	public void testGetAttribute_emptyArray() {
+	void testGetAttribute_emptyArray() {
 		String[] attribute = cut.getAttribute("emptyArray");
 		assertThat(attribute).isEmpty();
 	}
 
 	@Test
-	public void testGetAttribute_nonExisting() {
+	void testGetAttribute_nonExisting() {
 		assertThatThrownBy(() -> cut.getAttribute("nonExisting")).isInstanceOf(XSUserInfoException.class);
 	}
 
 	@Test
-	public void testGetAttribute_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
+	void testGetAttribute_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
 		cut = new XSUserInfoAdapter(createMockToken(GrantType.CLIENT_CREDENTIALS));
 
 		assertThatThrownBy(() -> cut.getAttribute("any")).isInstanceOf(XSUserInfoException.class)
@@ -226,18 +235,18 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void testHasAttributes_true() throws XSUserInfoException {
+	void testHasAttributes_true() throws XSUserInfoException {
 		assertThat(cut.hasAttributes()).isTrue();
 	}
 
 	@Test
-	public void testHasAttributes_false() throws XSUserInfoException {
+	void testHasAttributes_false() throws XSUserInfoException {
 		cut = new XSUserInfoAdapter(emptyToken);
 		assertThat(cut.hasAttributes()).isFalse();
 	}
 
 	@Test
-	public void testHasAttributes_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
+	void testHasAttributes_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
 		cut = new XSUserInfoAdapter(createMockToken(GrantType.CLIENT_CREDENTIALS));
 
 		assertThatThrownBy(() -> cut.hasAttributes()).isInstanceOf(XSUserInfoException.class)
@@ -246,67 +255,67 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void testGetSystemAttribute() throws XSUserInfoException {
+	void testGetSystemAttribute() throws XSUserInfoException {
 		String[] systemAttributes = cut.getSystemAttribute("xs.saml.groups");
 		assertThat(systemAttributes).contains("g1");
 	}
 
 	@Test
-	public void testCheckScope() throws XSUserInfoException {
+	void testCheckScope() throws XSUserInfoException {
 		assertThat(cut.checkScope("testScope")).isTrue();
 	}
 
 	@Test
-	public void testCheckLocalScope() throws XSUserInfoException {
+	void testCheckLocalScope() throws XSUserInfoException {
 		assertThat(cut.checkLocalScope("localScope")).isTrue();
 	}
 
 	@Test
-	public void testCheckLocalScope_appNameNull_throwsException() throws XSUserInfoException {
+	void testCheckLocalScope_appNameNull_throwsException() throws XSUserInfoException {
 		cut = new XSUserInfoAdapter(token.withScopeConverter(null));
 		assertThatThrownBy(() -> cut.checkLocalScope("localScope")).isInstanceOf(XSUserInfoException.class);
 	}
 
 	@Test
-	public void testGetAdditionalAuthAttribute() throws XSUserInfoException {
+	void testGetAdditionalAuthAttribute() throws XSUserInfoException {
 		assertThat(cut.getAdditionalAuthAttribute("external_id")).isEqualTo("abcd1234");
 	}
 
 	@Test
-	public void testGetCloneServiceInstanceId() throws XSUserInfoException {
+	void testGetCloneServiceInstanceId() throws XSUserInfoException {
 		assertThat(cut.getCloneServiceInstanceId()).isEqualTo("brokerCloneServiceInstanceId");
 	}
 
 	@Test
-	public void testGetGrantType() throws XSUserInfoException {
+	void testGetGrantType() throws XSUserInfoException {
 		assertThat(cut.getGrantType()).isEqualTo("urn:ietf:params:oauth:grant-type:saml2-bearer");
 	}
 
 	@Test
-	public void isForeignModeIsTrue_whenConfigurationIsNotAvailable() throws XSUserInfoException {
+	void isForeignModeIsTrue_whenConfigurationIsNotAvailable() throws XSUserInfoException {
 		cut = new XSUserInfoAdapter(token);
 		assertThat(cut.isInForeignMode()).isTrue();
 	}
 
 	@Test
-	public void testGetSubaccountId() throws XSUserInfoException {
+	void testGetSubaccountId() throws XSUserInfoException {
 		assertThat(cut.getSubaccountId()).isEqualTo("paas");
 	}
 
 	@Test
-	public void testGetSubaccountIdFromExternalAttributes() throws XSUserInfoException {
+	void testGetSubaccountIdFromExternalAttributes() throws XSUserInfoException {
 		String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImprdSI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzMxOTUvdG9rZW5fa2V5cyIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkifQ.ewogICJqdGkiOiAiOGU3YjNiMDAtNzc1MS00YjQ2LTliMWEtNWE0NmEyYTFkNWI4IiwKICAieHMudXNlci5hdHRyaWJ1dGVzIjogewogICAgImNvc3QtY2VudGVyIjogWwogICAgICAiMDgxNSIsCiAgICAgICI0NzExIgogICAgXSwKICAgICJjb3VudHJ5IjogWwogICAgICAiR2VybWFueSIKICAgIF0KICB9LAogICAgInhzLnN5c3RlbS5hdHRyaWJ1dGVzIjogewogICAgInhzLnNhbWwuZ3JvdXBzIjogWwogICAgICAiZzEiCiAgICBdLAogICAgInhzLnJvbGVjb2xsZWN0aW9ucyI6IFsicmMxIl0KICB9LAogICJzdWIiOiAiMTAwMjE5MSIsCiAgInNjb3BlIjogWwogICAgImphdmEtaGVsbG8td29ybGQuRGlzcGxheSIsCiAgICAib3BlbmlkIiwKICAgICJqYXZhLWhlbGxvLXdvcmxkLkRlbGV0ZSIsCiAgICAiamF2YS1oZWxsby13b3JsZC5DcmVhdGUiCiAgXSwKICAiY2xpZW50X2lkIjogInNiLWphdmEtaGVsbG8td29ybGQiLAogICJjaWQiOiAic2ItamF2YS1oZWxsby13b3JsZCIsCiAgImF6cCI6ICJzYi1qYXZhLWhlbGxvLXdvcmxkIiwKICAiZ3JhbnRfdHlwZSI6ICJhdXRob3JpemF0aW9uX2NvZGUiLAogICJ1c2VyX2lkIjogIjEwMDIxOTEiLAogICJ1c2VyX25hbWUiOiAiTXVzdGVybWFubiIsCiAgIm9yaWdpbiI6ICJ1c2VyaWRwIiwKICAiZW1haWwiOiAibWF4QGV4YW1wbGUuY29tIiwKICAiaWF0IjogMTQ0MjkxMjI0NCwKICAiZXhwIjogMTQ0Mjk1NTMyMiwKICAiaXNzIjogImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC91YWEvb2F1dGgvdG9rZW4iLAogICJ6aWQiOiAiMTEtMjItMzMiLAogICJoZGIubmFtZWR1c2VyLnNhbWwiOiAiPD94bWwgdmVyc2lvbj1cIjEuMFwiIGVuY29kaW5nPVwiVVRGLThcIj8-PHNhbWwyOkFzc2VydGlvbiB4bWxuczpzYW1sMj1cInVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb25cIiBJRD1cIl9mOTQ2YmM3Yi05MzM5LTQyMDAtYjdlYi0xYmJhNjU4MDEwNmJcIiBJc3N1ZUluc3RhbnQ9XCIyMDE1LTA5LTIyVDA4OjU1OjIyLjc1NVpcIiBWZXJzaW9uPVwiMi4wXCI-PHNhbWwyOklzc3Vlcj54czItbG9naW4tdGVzdC5jZi5zYXAtY2YuY29tLXNhbWwtbG9naW48L3NhbWwyOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPVwiaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI1wiPjxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT1cImh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuI1wiLz48ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT1cImh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMVwiLz48ZHM6UmVmZXJlbmNlIFVSST1cIiNfZjk0NmJjN2ItOTMzOS00MjAwLWI3ZWItMWJiYTY1ODAxMDZiXCI-PGRzOlRyYW5zZm9ybXM-PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09XCJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZVwiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT1cImh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuI1wiLz48L2RzOlRyYW5zZm9ybXM-PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09XCJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMVwiLz48ZHM6RGlnZXN0VmFsdWU-bkNCRk5hUjR5ZlhIYUlDdHZaaDZUVnBYbUl3PTwvZHM6RGlnZXN0VmFsdWU-PC9kczpSZWZlcmVuY2U-PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5DVkp2SVBnZk9ZNnZtRjg5VGZtcHhZbUlWQWFRM2JtSlRhamVOTnVNRUMxWE50SndPenoyT2VGNnhWVjFvWkk1MzZLQURNeVBmTlZwRG5tdVBaQUVzNnFJRzdLVkZlWCtQMTNHUVgyMzdobURxeDFVZHRuTTk0Q2E4eVd5YnRhSXhjWStPMjBQYkFFa1RaQVpyUCtyczBtTjc4QUlGTk1KYStzSVpOYm5MdVU9PC9kczpTaWduYXR1cmVWYWx1ZT48L2RzOlNpZ25hdHVyZT48c2FtbDI6U3ViamVjdD48c2FtbDI6TmFtZUlEIEZvcm1hdD1cInVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OnVuc3BlY2lmaWVkXCI-V09MRkdBTkc8L3NhbWwyOk5hbWVJRD48c2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9XCJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyXCI-PHNhbWwyOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj1cIjIwMTUtMDktMjJUMTI6NTU6MjIuNzU1WlwiLz48L3NhbWwyOlN1YmplY3RDb25maXJtYXRpb24-PC9zYW1sMjpTdWJqZWN0PjxzYW1sMjpDb25kaXRpb25zIE5vdEJlZm9yZT1cIjIwMTUtMDktMjJUMDg6NTU6MjIuNzU1WlwiIE5vdE9uT3JBZnRlcj1cIjIwMTUtMDktMjJUMTI6NTU6MjIuNzU1WlwiLz48c2FtbDI6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PVwiMjAxNS0wOS0yMlQwODo1NToyMi43NTdaXCIgU2Vzc2lvbk5vdE9uT3JBZnRlcj1cIjIwMTUtMDktMjJUMDk6MDA6MjIuNzU3WlwiPjxzYW1sMjpBdXRobkNvbnRleHQ-PHNhbWwyOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sMjpBdXRobkNvbnRleHRDbGFzc1JlZj48L3NhbWwyOkF1dGhuQ29udGV4dD48L3NhbWwyOkF1dGhuU3RhdGVtZW50Pjwvc2FtbDI6QXNzZXJ0aW9uPiIsCiAgImF1ZCI6IFsKICAgICJzYi1qYXZhLWhlbGxvLXdvcmxkIiwKICAgICJqYXZhLWhlbGxvLXdvcmxkIiwKICAgICJvcGVuaWQiCiAgXSwKICAiYXpfYXR0ciI6IHsKICAgICJleHRlcm5hbF9ncm91cCI6ICJkb21haW5cXGdyb3VwMSIsCiAgICAiZXh0ZXJuYWxfaWQiOiAiYWJjZDEyMzQiCiAgfSwKICAiZXh0X2F0dHIiOiB7CiAgICAic2VydmljZWluc3RhbmNlaWQiOiAiYWJjZDEyMzQiLAogICAgInpkbiI6ICJ0ZXN0c3ViZG9tYWluIiwKICAgICJzdWJhY2NvdW50aWQiOiAidGVzdC1zdWJhY2NvdW50IgogIH0KfQ.yi368lAzQi-mBajDN5oMzzREn3-3WKCrjQIBe7xXVkb8ms_42HAxMiZdrLbCf1mLoJ_CMfAWJkTW0M2GmpXBJPFcdKwsmxahSmP3Ir8nRzrK76FRn1C8biVibs0MZ7cIIq-g2H--tsrml2IkL4-hZ1v5-NCF0Aq-WCcPNsHMdWTrBxpBSRkdEqiViulbfAEXJGSqkXez5VKNX_e2eKYwgu0uJN-BpD8Pqufi3H9M9UKjISkLdsXs5LdpaFN7at6BgMbW2Ce7RVTGsk3ir--rZzQg1oUnWVOKTNEwcTCIJGxm90Smse_aDcb3CBBwnMS_KbQ723ABKhW_m2q-mWeNgg";
 		cut = new XSUserInfoAdapter(new XsuaaToken(token));
 		assertThat(cut.getSubaccountId()).isEqualTo("test-subaccount");
 	}
 
 	@Test
-	public void testGetOrigin() throws XSUserInfoException {
+	void testGetOrigin() throws XSUserInfoException {
 		assertThat(cut.getOrigin()).isEqualTo("useridp");
 	}
 
 	@Test
-	public void requestTokenForClient() throws TokenFlowException {
+	void requestTokenForClient() throws TokenFlowException {
 		String clientId = "theClientId";
 		String clientSecret = "theClientSecret";
 		String uaaBaseUrl = "http://oauth.base.url/oauth/token";
@@ -323,7 +332,7 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void requestTokenForUser() throws TokenFlowException {
+	void requestTokenForUser() throws TokenFlowException {
 		String clientId = "theClientId";
 		String clientSecret = "theClientSecret";
 		String uaaBaseUrl = "http://oauth.base.url/oauth/token";
@@ -345,7 +354,7 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void testGetOrigin_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
+	void testGetOrigin_grantTypeClientCredentials_throwsException() throws XSUserInfoException {
 		cut = new XSUserInfoAdapter(createMockToken(GrantType.CLIENT_CREDENTIALS));
 
 		assertThatThrownBy(() -> cut.getOrigin()).isInstanceOf(XSUserInfoException.class)
@@ -354,7 +363,7 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void accessAttributes_doNotExist_throwsException() throws XSUserInfoException {
+	void accessAttributes_doNotExist_throwsException() throws XSUserInfoException {
 		String nonExistingAttribute = "doesNotExist";
 		cut = new XSUserInfoAdapter(emptyToken.withScopeConverter(new XsuaaScopeConverter(TEST_APP_ID)));
 
@@ -372,7 +381,7 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void getHdbToken_AuthCodeToken_NoAttributes() throws XSUserInfoException, IOException {
+	void getHdbToken_AuthCodeToken_NoAttributes() throws XSUserInfoException, IOException {
 		XsuaaToken token = new XsuaaToken(
 				IOUtils.resourceToString("/xsuaaXsaAccessTokenRSA256_signedWithVerificationKey.txt", UTF_8));
 		OAuth2ServiceConfiguration configuration = OAuth2ServiceConfigurationBuilder.forService(Service.XSUAA)
@@ -389,7 +398,7 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void getHdbToken_AudCodeToken_WithAttributes() throws XSUserInfoException {
+	void getHdbToken_AudCodeToken_WithAttributes() throws XSUserInfoException {
 		XsuaaToken token = mock(XsuaaToken.class);
 		String mockTokenValue = "mock token value";
 
@@ -418,7 +427,7 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void isForeignModeFalse_whenTrustedClientIdSuffixMatches() throws XSUserInfoException {
+	void isForeignModeFalse_whenTrustedClientIdSuffixMatches() throws XSUserInfoException {
 		String tokenClientId = "sb-clone1!b22|brokerplanmasterapp!b123"; // azp
 		String configurationAppId = "brokerplanmasterapp!b123";
 		XsuaaToken token = mock(XsuaaToken.class);
@@ -439,7 +448,7 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void isForeignModeFalse_WhenIdentityZoneDoesNotMatchButCliendIdIsApplicationPlan()
+	void isForeignModeFalse_WhenIdentityZoneDoesNotMatchButCliendIdIsApplicationPlan()
 			throws XSUserInfoException {
 		String tokenClientId = "sb-application!t0123"; // azp
 		String identityZone = "brokerplanmasterapp!b123"; // ext_attr -> zdn
@@ -456,7 +465,7 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void isForeignModeFalse_WhenIdentityZoneDoesNotMatchButCliendIdIsBrokerPlan()
+	void isForeignModeFalse_WhenIdentityZoneDoesNotMatchButCliendIdIsBrokerPlan()
 			throws XSUserInfoException {
 		String tokenClientId = "sb-application!b0123"; // azp
 		String identityZone = "brokerplanmasterapp!b123"; // ext_attr -> zdn
@@ -473,7 +482,7 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void isForeignModeFalse_WhenClientIdAndIdentityZonesMatch() throws XSUserInfoException {
+	void isForeignModeFalse_WhenClientIdAndIdentityZonesMatch() throws XSUserInfoException {
 		String tokenClientId = "sb-application"; // azp
 		String identityZone = "brokerplanmasterapp!b123"; // ext_attr -> zdn
 		OAuth2ServiceConfiguration configuration = OAuth2ServiceConfigurationBuilder.forService(Service.XSUAA)
@@ -489,7 +498,7 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void isForeignModeTrue_whenClientIdDoesNotMatchIdentityZone() throws XSUserInfoException {
+	void isForeignModeTrue_whenClientIdDoesNotMatchIdentityZone() throws XSUserInfoException {
 		OAuth2ServiceConfiguration configuration = OAuth2ServiceConfigurationBuilder.forService(Service.XSUAA)
 				.withProperty(CFConstants.XSUAA.APP_ID, "sb-application")
 				.build();
@@ -502,7 +511,7 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void isForeignModeTrue_whenClientIdIsMissing() throws XSUserInfoException {
+	void isForeignModeTrue_whenClientIdIsMissing() throws XSUserInfoException {
 		cut = createComponentUnderTestSpy();
 
 		doReturn("brokerplanmasterapp!b123").when(cut).getIdentityZone();
@@ -512,7 +521,7 @@ public class XSUserInfoAdapterTest {
 	}
 
 	@Test
-	public void isForeignModeTrue_whenIdentityZoneIsMissing() throws XSUserInfoException {
+	void isForeignModeTrue_whenIdentityZoneIsMissing() throws XSUserInfoException {
 		cut = createComponentUnderTestSpy();
 
 		doReturn("sb-application!t0123").when(cut).getClientId();
@@ -521,6 +530,56 @@ public class XSUserInfoAdapterTest {
 		assertThat(cut.isInForeignMode()).isTrue();
 	}
 
+	@ParameterizedTest
+	@MethodSource
+	void canReadSystemAttributesAsListAndPlainString(Object attributeAsStringResult, Object attributeAsStringListResult, List<String> expectedAttributeResult, Class<?> expectedException) {
+		Token t = setupTokenMockForSystemAttributeTest(attributeAsStringResult, attributeAsStringListResult);
+		cut = new XSUserInfoAdapter(t);
+
+		if (expectedException != null) {
+			assertThatThrownBy(() -> cut.getSystemAttribute("foo")).isExactlyInstanceOf(expectedException);
+		} else {
+			assertThat(cut.getSystemAttribute("foo")).containsAll(expectedAttributeResult);
+		}
+	}
+
+	private Token setupTokenMockForSystemAttributeTest(Object attributeAsStringResult, Object attributeAsStringListResult) {
+		Token t = Mockito.mock(AccessToken.class);
+		cut = new XSUserInfoAdapter(t);
+
+		OngoingStubbing<String> whenAttributeReadAsString = when(t.getAttributeFromClaimAsString(
+				eq(XS_SYSTEM_ATTRIBUTES), anyString()));
+		if (attributeAsStringResult instanceof Throwable) {
+			whenAttributeReadAsString.thenThrow((Throwable) attributeAsStringResult);
+		} else {
+			whenAttributeReadAsString.thenReturn((String) attributeAsStringResult);
+		}
+
+		OngoingStubbing<List<String>> whenAttributeReadAsStringList = when(t.getAttributeFromClaimAsStringList(
+				eq(XS_SYSTEM_ATTRIBUTES), anyString()));
+		if (attributeAsStringListResult instanceof Throwable) {
+			whenAttributeReadAsStringList.thenThrow((Throwable) attributeAsStringListResult);
+		} else {
+			whenAttributeReadAsStringList.thenReturn((List<String>) attributeAsStringListResult);
+		}
+
+		return t;
+	}
+
+	private static Stream<Arguments> canReadSystemAttributesAsListAndPlainString() {
+        String systemAttributeValue = "bar";
+        List<String> systemAttributValuesAsList = Stream.of("bar1", "bar2").collect(Collectors.toList());
+
+        return Stream.of(
+                Arguments.of(null, null, null, XSUserInfoException.class),
+                Arguments.of(new JsonParsingException(""), null, null, XSUserInfoException.class),
+                Arguments.of(null, new JsonParsingException(""), null, XSUserInfoException.class),
+                Arguments.of(new JsonParsingException(""), new JsonParsingException(""), null, XSUserInfoException.class),
+                Arguments.of(systemAttributeValue, new JsonParsingException(""), Collections.singletonList(systemAttributeValue), null),
+                Arguments.of(new JsonParsingException(""), systemAttributValuesAsList, systemAttributValuesAsList, null)
+        );
+    }
+
 	private XSUserInfoAdapter createComponentUnderTestSpy() throws XSUserInfoException {
 		return spy(new XSUserInfoAdapter(mock(XsuaaToken.class), mock(OAuth2ServiceConfiguration.class)));
 	}