halo-dev/halo · warning · ServerWebInputException
urls must not be empty.
Error message
urls must not be empty.
What it means
AttachmentPermalinkMatcher.match(List<String> urls, URL siteUrl) validates its input via createCandidates, which throws ServerWebInputException (HTTP 400) if the urls list is null or empty. The matcher needs at least one candidate URL to do any permalink resolution against stored attachments.
Source
Thrown at application/src/main/java/run/halo/app/core/attachment/AttachmentPermalinkMatcher.java:49
var candidates = createCandidates(urls, siteUrl);
var uniqueCandidates = candidates.stream()
.flatMap(candidate -> candidate.permalinks().stream())
.collect(Collectors.toCollection(LinkedHashSet::new));
var listOptions = ListOptions.builder()
.andQuery(in("status.permalink", uniqueCandidates))
.build();
return client.listAll(Attachment.class, listOptions, Sort.unsorted())
.mapNotNull(AttachmentPermalinkMatcher::getPermalink)
.collect(Collectors.toSet())
.map(matchedPermalinks -> candidates.stream()
.map(candidate -> new AttachmentPermalinkMatchResult(
candidate.url(), candidate.matches(matchedPermalinks)))
.toList());
}
private static List<Candidate> createCandidates(List<String> urls, URL siteUrl) {
if (urls == null || urls.isEmpty()) {
throw new ServerWebInputException("urls must not be empty.");
}
return urls.stream().map(url -> createCandidate(url, siteUrl)).toList();
}
private static Candidate createCandidate(String url, URL siteUrl) {
if (!StringUtils.hasText(url)) {
throw new ServerWebInputException("url must not be blank.");
}
var value = url.strip();
URI valueUri;
try {
valueUri = URI.create(value);
} catch (IllegalArgumentException e) {
var permalinks = new LinkedHashSet<String>();
permalinks.add(value);
return new Candidate(url, permalinks);
}View on GitHub (pinned to d2f5165f9c)
Solutions
- Send at least one URL in the urls field of the request body.
- On the client, disable the match action until the user has selected at least one URL.
- If invoking match() in code, guard with if (urls == null || urls.isEmpty()) before calling.
Example fix
// before
matcher.match(List.of(), siteUrl) // 400
// after
if (urls != null && !urls.isEmpty()) {
matcher.match(urls, siteUrl);
} Defensive patterns
Strategy: validation
Validate before calling
if (urls == null || urls.isEmpty()) {
return Mono.error(new ServerWebInputException("urls must not be empty."));
}
matcher.match(urls, siteUrl); Type guard
static boolean hasUrls(List<String> urls) {
return urls != null && !urls.isEmpty();
} Prevention
- Disable the match action client-side until at least one URL is selected.
- Treat an empty selection as a no-op instead of sending the request.
- Document that the endpoint requires a non-empty urls array.
When it happens
Trigger: Calling the attachment permalink match endpoint (or match() directly) with an empty urls array, e.g. POST { "urls": [] } or omitting the field so it deserializes to null.
Common situations: Frontend 'check links' batch action invoked with no selection; a client sending the request body but with an empty urls list due to a mapping bug; programmatic caller passing Collections.emptyList().
Related errors
- url must not be blank.
- Unsupported URL protocol: {}
- Required url is missing.
- Policy name must not be blank
- Invalid part of policyName
AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14).
Data as JSON: /api/errors/1afb2b2f0d5bb164.
Report an issue: GitHub.