Skip to content

DATAREST-846-ruthes00. Fixed bug in DefaultSelfLinkProvider.entityIdentifierOrNull() preventing use of composite key in resource URI - #2593

Open
ruthst00 wants to merge 1 commit into
spring-projects:mainfrom
ruthst00:DATAREST-846-ruthes00
Open

DATAREST-846-ruthes00. Fixed bug in DefaultSelfLinkProvider.entityIdentifierOrNull() preventing use of composite key in resource URI#2593
ruthst00 wants to merge 1 commit into
spring-projects:mainfrom
ruthst00:DATAREST-846-ruthes00

Conversation

@ruthst00

@ruthst00 ruthst00 commented Sep 4, 2026

Copy link
Copy Markdown
  • You have read the Spring Data contribution guidelines.
  • You use the code formatters provided here and have them applied to your changes. Don’t submit any formatting related changes.
  • You submit test cases (unit or integration tests) that back your changes.
  • You added yourself as author in the headers of the classes you touched. Amend the date range in the Apache license header if needed. For new types, add the license header (copy from another file and set the current year only).

Root Cause

When Spring Data REST serializes a collection of entities with @EmbeddedId composite keys, it calls PersistentEntity.getIdentifierAccessor(instance).getIdentifier() to extract the ID for building the self link. For @EmbeddedId entities in Spring Data JPA, this can return null when the JPA mapping layer cannot resolve the composite key through its standard path (e.g., when the key type contains non-primitive fields like Joda-Time DateTime that aren't registered in the mapping context). When null is returned, the self link generation fails silently and falls back to the collection root URI — which is exactly the broken behavior reported.

As described in DATAREST-846, the user's BackendIdConverter was correctly wired and would have been called (it works for direct item lookups), but it's never reached because the identifier extraction step returns null first.

The Fix — DefaultSelfLinkProvider.java

In entityIdentifierOrNull(), after the standard IdentifierAccessor path returns null, fall back to reading the ID property directly via PersistentPropertyAccessor:

private @Nullable Object entityIdentifierOrNull(Object instance) {

    PersistentEntity<?, ?> entity = entities.getRequiredPersistentEntity(instance.getClass());

    // First try the standard IdentifierAccessor path (works for simple @Id)
    Object identifier = entity.getIdentifierAccessor(instance).getIdentifier();

    if (identifier != null) {
        return identifier;
    }

    // Fall back to reading the ID property directly via PersistentPropertyAccessor.
    // This handles composite keys (@EmbeddedId / @IdClass) where the IdentifierAccessor
    // may return null because the mapping layer cannot resolve the composite key through
    // its standard path (e.g. when the key type contains non-primitive fields such as
    // Joda-Time DateTime that are not registered in the mapping context).
    PersistentProperty<?> idProperty = entity.getIdProperty();

    if (idProperty == null) {
        return null;
    }

    return entity.getPropertyAccessor(instance).getProperty(idProperty);
}

This returns the raw SiteStatId composite key object, which is then passed to RepositoryEntityLinks.linkForItemResource(), which calls the user's BackendIdConverter.toRequestId() to produce the correct URI string (e.g., 1_2016-06-26T12:20:51.954Z). The self links in the collection response will then correctly point to /api/siteStats/1_2016-06-26T12:20:51.954Z instead of the collection root.

A new test fallsBackToIdPropertyAccessorWhenIdentifierAccessorReturnsNullForCompositeKey was added to DefaultSelfLinkProviderUnitTests to cover this scenario. All 7 tests pass.

…entifierOrNull()` preventing use of composite key in resource URI.

Signed-off-by: ruthes00 <ruthes00@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: waiting-for-triage An issue we've not yet triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants