Liferay DXP Bug Report: Account Group Association Endpoint Parameter Swapping¶
Status: DRAFT (PENDING SUBMISSION)
LPD-DRAFT - (Not yet submitted to Liferay JIRA)
Component¶
- Headless Admin User
- Account Groups / User Segments API
Environment¶
- Liferay Product Version: Liferay DXP 2026.q1 or 7.4 U80+
- API Endpoint:
POST /o/headless-admin-user/v1.0/account-groups/by-external-reference-code/{accountExternalReferenceCode}/accounts/by-external-reference-code/{externalReferenceCode}
Summary¶
The REST endpoint used to assign an account to an account group via external reference codes (ERCs) has its path parameters swapped in Liferay's internal JAX-RS Java backend implementation. The first path parameter (which visually represents the account group's ERC in the URL path) is mapped to the account's ERC, and the second path parameter (which visually represents the account's ERC in the URL path) is mapped to the account group's ERC.
Consequently:
- Submitting a standard request matching the URL template (Group ERC first, Account ERC second) fails with
404 Not Found. - Submitting a swapped request (Account ERC first, Group ERC second) succeeds with
204 No Content.
Description & Technical Analysis¶
The OpenAPI specification defines the path as:
"/v1.0/account-groups/by-external-reference-code/{accountExternalReferenceCode}/accounts/by-external-reference-code/{externalReferenceCode}"
And the parameters as:
accountExternalReferenceCode(representing the placeholder afteraccount-groups/by-external-reference-code/)externalReferenceCode(representing the placeholder afteraccounts/by-external-reference-code/)
In Liferay's JAX-RS resource implementation class, the Java method signature is annotated as follows:
@POST
@Path("/account-groups/by-external-reference-code/{accountExternalReferenceCode}/accounts/by-external-reference-code/{externalReferenceCode}")
public Response postAccountGroupByExternalReferenceCodeAccountByExternalReferenceCode(
@PathParam("accountExternalReferenceCode") String accountExternalReferenceCode,
@PathParam("externalReferenceCode") String externalReferenceCode) {
...
}
Confused by the name accountExternalReferenceCode (which is used for the Account Group's ERC because of an upstream copy-paste error from another endpoint), the internal Java service implementation swaps the arguments:
- It looks up the
AccountEntryusing the first parameter (accountExternalReferenceCode). - It looks up the
AccountGroupusing the second parameter (externalReferenceCode).
If the request is sent with the placeholders in their correct positions (e.g. /account-groups/by-external-reference-code/GROUP-ERC/accounts/by-external-reference-code/ACCOUNT-ERC), Liferay tries to query the database/index for an AccountEntry with ERC GROUP-ERC and an AccountGroup with ERC ACCOUNT-ERC. Since neither exists in those tables, it returns a silent 404 Not Found.
Steps to Reproduce¶
- Create an Account Group with
externalReferenceCode = SEG-TEST-GROUP. - Create a Business Account with
externalReferenceCode = ACC-TEST-ACCOUNT. - Try to associate them by invoking the REST API in standard order:
curl -i -u test@liferay.com:test -X POST http://localhost:8080/o/headless-admin-user/v1.0/account-groups/by-external-reference-code/SEG-TEST-GROUP/accounts/by-external-reference-code/ACC-TEST-ACCOUNT
- Note that Liferay returns
404 Not Found:
- Invoke the REST API with the swapped path parameter values:
curl -i -u test@liferay.com:test -X POST http://localhost:8080/o/headless-admin-user/v1.0/account-groups/by-external-reference-code/ACC-TEST-ACCOUNT/accounts/by-external-reference-code/SEG-TEST-GROUP
- Note that Liferay returns
204 No Content(Success) and the association is successfully saved in theaccountgroupreltable.
Expected Results¶
The REST endpoint should correctly map parameters to their matching path templates:
- The first placeholder
/account-groups/by-external-reference-code/{accountGroupERC}should map to the Account Group. - The second placeholder
/accounts/by-external-reference-code/{accountERC}should map to the Account.
Workaround¶
In our client extension SDK, we swap the variables when constructing the URL:
async assignAccountToGroup(config, groupERC, accountERC) {
// WORKAROUND: Liferay's REST endpoint internally swaps the parameters:
// the first placeholder maps to account ERC, and the second maps to group ERC.
return await this._post(
config,
`/o/headless-admin-user/v1.0/account-groups/by-external-reference-code/${encodeURIComponent(accountERC)}/accounts/by-external-reference-code/${encodeURIComponent(groupERC)}`,
null,
'assign-account-to-group',
'Failed to assign account to group'
);
}
Last Reviewed: 2026-09-13