halo-dev/halo · warning · ServerWebInputException
url must not be blank.
Error message
url must not be blank.
What it means
AttachmentPermalinkMatcher.createCandidate throws ServerWebInputException (HTTP 400) when an individual entry in the urls list is blank (StringUtils.hasText false). Each candidate is validated independently before URI parsing, so a single blank string fails the whole batch.
Source
Thrown at application/src/main/java/run/halo/app/core/attachment/AttachmentPermalinkMatcher.java:56
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);
}
if (isUnsupportedAbsoluteUri(valueUri)) {
throw new ServerWebInputException("Unsupported URL protocol: " + value);
}
var permalinks = new LinkedHashSet<String>();
permalinks.add(value);
var siteUri = URI.create(siteUrl.toString());View on GitHub (pinned to d2f5165f9c)
Solutions
- Filter blank entries out of the urls list before sending: urls.filter(u => u.trim().length > 0).
- Trim and de-duplicate the list client-side.
- If using comma-split input, reject/ignore empty tokens after splitting.
Example fix
// before
const urls = raw.split(',') // ["a", "", "b"]
// after
const urls = raw.split(',').map(s => s.trim()).filter(Boolean) Defensive patterns
Strategy: validation
Validate before calling
List<String> clean = urls.stream()
.filter(StringUtils::hasText)
.map(String::strip)
.distinct()
.toList();
if (clean.isEmpty()) { /* handle */ }
matcher.match(clean, siteUrl); Type guard
static boolean noBlankUrls(List<String> urls) {
return urls != null && urls.stream().allMatch(StringUtils::hasText);
} Prevention
- Trim and filter blank entries before submitting the urls list.
- When splitting comma-joined input, drop empty tokens.
- De-duplicate to reduce redundant lookups.
When it happens
Trigger: Sending a urls list that contains an empty string or a whitespace-only element, e.g. ["https://a.com", "", "https://b.com"], or trailing commas in a comma-separated input that split into blanks.
Common situations: Frontend builds the list from a textarea or multi-select and leaves a blank entry; client splits a comma-joined string and doesn't filter empties; copy-paste introducing stray whitespace.
Related errors
- urls must not be empty.
- Unsupported URL protocol: {}
- Required url is missing.
- Policy name must not be blank
- Invalid part of file
AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14).
Data as JSON: /api/errors/35ec4859091123c5.
Report an issue: GitHub.