hibernate/hibernate-orm · error · FunctionArgumentException

Argument '%s' of 'fk()' function is not a single-valued asso

Error message

Argument '%s' of 'fk()' function is not a single-valued association

What it means

The Hibernate-specific fk(Path) criteria function extracts the raw foreign-key column(s) of an association path, and is only defined for single-valued associations (@ManyToOne/@OneToOne). The builder validates that the path's referenced path source is a SINGULAR_ATTRIBUTE and an EntitySqmPathSource; anything else (a basic attribute, a plural attribute, a component/embeddable, a computed expression) throws FunctionArgumentException.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java:931

	@Override @SuppressWarnings("unchecked")
	public <T extends HibernateCriteriaBuilder> T unwrap(Class<T> clazz) {
		final T result = (T) extensions.get( clazz );
		if ( result == null ) {
			throw new IllegalArgumentException( "Unable to unwrap to " + clazz.getName() );
		}
		return result;
	}

	@Override
	public SqmPath<?> fk(Path<?> path) {
		final var sqmPath = (SqmPath<?>) path;
		final var toOneReference = sqmPath.getReferencedPathSource();
		final boolean validToOneRef =
				toOneReference.getBindableType() == Bindable.BindableType.SINGULAR_ATTRIBUTE
						&& toOneReference instanceof EntitySqmPathSource;
		if ( !validToOneRef ) {
			throw new FunctionArgumentException(
					String.format(
							Locale.ROOT,
							"Argument '%s' of 'fk()' function is not a single-valued association",
							sqmPath.getNavigablePath()
					)
			);
		}
		return new SqmFkExpression<>( sqmPath );
	}

	@Nonnull
	@Override
	public <X, T extends X> SqmPath<T> treat(@Nonnull Path<X> path, @Nonnull Class<T> type) {
		return ( (SqmPath<X>) path ).treatAs( type );
	}

	@Nonnull
	@Override

View on GitHub (pinned to fad1729dce)

Solutions

  1. Point fk() at the association attribute itself, e.g. cb.fk(orderRoot.get("customer")) for @ManyToOne Customer customer.
  2. For basic/composite values you wanted the value, not the FK: use the plain path (root.get("status")) or root.get("address").get("city").
  3. For collections, first join them (orderRoot.join("lineItems")) and then navigate from the joined entity.

Example fix

// before
Expression<Long> fk = cb.fk(orderRoot.get("warehouseCode")); // basic String attribute -> FunctionArgumentException

// after
// 'customer' is @ManyToOne Customer
Expression<Long> fk = cb.fk(orderRoot.get("customer")); // FK of the association
Defensive patterns

Strategy: type-guard

Validate before calling

boolean isSingleValuedAssociation(Path<?> path) {
    return path.getModel() instanceof SingularAttribute<?, ?> sa
            && (sa.getType() instanceof EntityType<?> || sa.getPersistentAttributeType()
                 == Attribute.PersistentAttributeType.MANY_TO_ONE
                 || sa.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_ONE);
}

Type guard

static boolean fkApplicable(Path<?> p) {
    if (!(p.getModel() instanceof SingularAttribute<?, ?> sa)) return false;
    return switch (sa.getPersistentAttributeType()) {
        case MANY_TO_ONE, ONE_TO_ONE -> true;
        default -> false;
    };
}

Try / catch

try {
    Expression<Long> fk = cb.fk(orderRoot.get("customer"));
} catch (FunctionArgumentException e) {
    // attribute is not an association: use the plain value path instead
    Expression<?> v = orderRoot.get("customerRef");
}

Prevention

When it happens

Trigger: cb.fk(root.get("status")) where status is a String/@Enumerated basic attribute; cb.fk(root.get("lineItems")) on an @OneToMany collection; cb.fk(orderRoot) on the entity root itself; cb.fk(root.get("address")) where address is an @Embedded component rather than an association.

Common situations: Optimizing joins on FK columns without a join (the typical use: cb.fk(order.get("customer").get("id")) style comparisons) and mistyping the attribute; assuming fk() works on embeddables; copying HQL 'fk(...)' snippets into criteria code against the wrong attribute.

Related errors


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/796adf142af49ebf. Report an issue: GitHub.