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
@OverrideView on GitHub (pinned to fad1729dce)
Solutions
- Point fk() at the association attribute itself, e.g. cb.fk(orderRoot.get("customer")) for @ManyToOne Customer customer.
- For basic/composite values you wanted the value, not the FK: use the plain path (root.get("status")) or root.get("address").get("city").
- 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
- Only call fk() on @ManyToOne/@OneToOne attribute paths.
- For basic values use the attribute path directly; for embeddables navigate into their fields.
- Double-check the attribute name string — fk() failures are frequently just wrong attribute names.
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
- Invalid temporal field [{}]
- Informix does not support binary literals
- Association '${path}' targets the type '${type}' which does
- Association '${path}' is 'mappedBy' a property named '${mapp
- Association '${path}' is 'mappedBy' a property named '${mapp
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/796adf142af49ebf.
Report an issue: GitHub.