{"record":{"id":"1afb2b2f0d5bb164","repo":"halo-dev/halo","slug":"urls-must-not-be-empty","errorCode":null,"errorMessage":"urls must not be empty.","messagePattern":"urls must not be empty\\.","errorType":"http","errorClass":"ServerWebInputException","httpStatus":400,"severity":"warning","filePath":"application/src/main/java/run/halo/app/core/attachment/AttachmentPermalinkMatcher.java","lineNumber":49,"sourceCode":"        var candidates = createCandidates(urls, siteUrl);\n        var uniqueCandidates = candidates.stream()\n                .flatMap(candidate -> candidate.permalinks().stream())\n                .collect(Collectors.toCollection(LinkedHashSet::new));\n        var listOptions = ListOptions.builder()\n                .andQuery(in(\"status.permalink\", uniqueCandidates))\n                .build();\n        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        }","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/application/src/main/java/run/halo/app/core/attachment/AttachmentPermalinkMatcher.java#L31-L67","documentation":"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.","triggerScenarios":"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.","commonSituations":"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().","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."],"exampleFix":"// before\nmatcher.match(List.of(), siteUrl) // 400\n\n// after\nif (urls != null && !urls.isEmpty()) {\n    matcher.match(urls, siteUrl);\n}","handlingStrategy":"validation","validationCode":"if (urls == null || urls.isEmpty()) {\n    return Mono.error(new ServerWebInputException(\"urls must not be empty.\"));\n}\nmatcher.match(urls, siteUrl);","typeGuard":"static boolean hasUrls(List<String> urls) {\n    return urls != null && !urls.isEmpty();\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["validation","attachment","permalink","http-400","required-field"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}