{"record":{"id":"35ec4859091123c5","repo":"halo-dev/halo","slug":"url-must-not-be-blank","errorCode":null,"errorMessage":"url must not be blank.","messagePattern":"url must not be blank\\.","errorType":"http","errorClass":"ServerWebInputException","httpStatus":400,"severity":"warning","filePath":"application/src/main/java/run/halo/app/core/attachment/AttachmentPermalinkMatcher.java","lineNumber":56,"sourceCode":"        return client.listAll(Attachment.class, listOptions, Sort.unsorted())\n                .mapNotNull(AttachmentPermalinkMatcher::getPermalink)\n                .collect(Collectors.toSet())\n                .map(matchedPermalinks -> candidates.stream()\n                        .map(candidate -> new AttachmentPermalinkMatchResult(\n                                candidate.url(), candidate.matches(matchedPermalinks)))\n                        .toList());\n    }\n\n    private static List<Candidate> createCandidates(List<String> urls, URL siteUrl) {\n        if (urls == null || urls.isEmpty()) {\n            throw new ServerWebInputException(\"urls must not be empty.\");\n        }\n        return urls.stream().map(url -> createCandidate(url, siteUrl)).toList();\n    }\n\n    private static Candidate createCandidate(String url, URL siteUrl) {\n        if (!StringUtils.hasText(url)) {\n            throw new ServerWebInputException(\"url must not be blank.\");\n        }\n        var value = url.strip();\n\n        URI valueUri;\n        try {\n            valueUri = URI.create(value);\n        } catch (IllegalArgumentException e) {\n            var permalinks = new LinkedHashSet<String>();\n            permalinks.add(value);\n            return new Candidate(url, permalinks);\n        }\n        if (isUnsupportedAbsoluteUri(valueUri)) {\n            throw new ServerWebInputException(\"Unsupported URL protocol: \" + value);\n        }\n\n        var permalinks = new LinkedHashSet<String>();\n        permalinks.add(value);\n        var siteUri = URI.create(siteUrl.toString());","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/application/src/main/java/run/halo/app/core/attachment/AttachmentPermalinkMatcher.java#L38-L74","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst urls = raw.split(',') // [\"a\", \"\", \"b\"]\n\n// after\nconst urls = raw.split(',').map(s => s.trim()).filter(Boolean)","handlingStrategy":"validation","validationCode":"List<String> clean = urls.stream()\n    .filter(StringUtils::hasText)\n    .map(String::strip)\n    .distinct()\n    .toList();\nif (clean.isEmpty()) { /* handle */ }\nmatcher.match(clean, siteUrl);","typeGuard":"static boolean noBlankUrls(List<String> urls) {\n    return urls != null && urls.stream().allMatch(StringUtils::hasText);\n}","tryCatchPattern":null,"preventionTips":["Trim and filter blank entries before submitting the urls list.","When splitting comma-joined input, drop empty tokens.","De-duplicate to reduce redundant lookups."],"tags":["validation","attachment","permalink","http-400","blank-input"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}