spring-projects/spring-security · critical · CycleInRoleHierarchyException

Exception thrown because of a cycle in the role hierarchy…

Error message

Exception thrown because of a cycle in the role hierarchy definition!

What it means

RoleHierarchyImpl.buildRolesReachableInOneOrMoreStepsMap expands each role through the hierarchy; while walking reachable roles it detects when a role reaches itself, meaning the hierarchy definition contains a cycle (e.g. ROLE_A > ROLE_B > ROLE_A). It throws CycleInRoleHierarchyException at construction time, so the application fails to start.

Solutions

  1. Remove the cyclic mapping from the hierarchy string so roles form a DAG (each role reachable only 'downward')
  2. Log/print the hierarchy at startup and trace the loop among the roles named in the exception
  3. Refactor mutually dependent roles into a common parent role instead of circular references
  4. Add a unit test calling new RoleHierarchyImpl(hierarchyString) for every shipped hierarchy to fail fast on cycles

Example fix

// before
String hierarchy = "ROLE_A > ROLE_B\nROLE_B > ROLE_C\nROLE_C > ROLE_A"; // cycle
// after
String hierarchy = "ROLE_A > ROLE_B\nROLE_A > ROLE_C";
Defensive patterns

Strategy: validation

Validate before calling

Set<String> seen = new HashSet<>();
for (String[] line : mappings) { String from = line[0], to = line[1];
  if (from.equals(to) || createsBackEdge(from, to)) throw new IllegalArgumentException("cycle in role hierarchy: " + from + " -> " + to); }

Type guard

null

Try / catch

try { new RoleHierarchyImpl(hierarchyString); }
catch (CycleInRoleHierarchyException e) { failFast("Fix role hierarchy cycle before startup"); }

Prevention

When it happens

Trigger: Defining a role hierarchy string where roles reference each other in a loop, directly (ROLE_A > ROLE_A) or transitively (ROLE_A > ROLE_B > ROLE_C > ROLE_A), typically via setHierarchy(...) or the role-hierarchy configuration property.

Common situations: Copy-pasted or merged hierarchy definitions in application.yml/security config; programmatically built hierarchies appending mappings that close a loop; multi-module setups where two modules each add inverse mappings.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/2a72944297d02528. Report an issue: GitHub.

Appendix: source

Thrown at core/src/main/java/org/springframework/security/access/hierarchicalroles/RoleHierarchyImpl.java:221

	 * CycleInRoleHierarchyException if a cycle in the role hierarchy definition is
	 * detected)
	 */
	private static Map<String, Set<GrantedAuthority>> buildRolesReachableInOneOrMoreStepsMap(
			Map<String, Set<GrantedAuthority>> hierarchy) {
		Map<String, Set<GrantedAuthority>> rolesReachableInOneOrMoreStepsMap = new HashMap<>();
		// iterate over all higher roles from rolesReachableInOneStepMap
		for (String roleName : hierarchy.keySet()) {
			Set<GrantedAuthority> rolesToVisitSet = new HashSet<>(hierarchy.get(roleName));
			Set<GrantedAuthority> visitedRolesSet = new HashSet<>();
			while (!rolesToVisitSet.isEmpty()) {
				// take a role from the rolesToVisit set
				GrantedAuthority lowerRole = rolesToVisitSet.iterator().next();
				rolesToVisitSet.remove(lowerRole);
				if (!visitedRolesSet.add(lowerRole) || !hierarchy.containsKey(lowerRole.getAuthority())) {
					continue; // Already visited role or role with missing hierarchy
				}
				else if (roleName.equals(lowerRole.getAuthority())) {
					throw new CycleInRoleHierarchyException();
				}
				rolesToVisitSet.addAll(hierarchy.get(lowerRole.getAuthority()));
			}
			rolesReachableInOneOrMoreStepsMap.put(roleName, visitedRolesSet);
			logger.debug(LogMessage.format(
					"buildRolesReachableInOneOrMoreStepsMap() - From role %s one can reach %s in one or more steps.",
					roleName, visitedRolesSet));
		}
		return rolesReachableInOneOrMoreStepsMap;
	}

	/**
	 * Builder class for constructing a {@link RoleHierarchyImpl} based on a hierarchical
	 * role structure.
	 *
	 * @author Federico Herrera
	 * @since 6.3
	 */

View on GitHub (pinned to 96852e8860)