HMCL-dev/HMCL · error · IOException

Unable to parse server manifest.json from

Error message

Unable to parse server manifest.json from 

What it means

McbbsModpackCompletionTask.getFuture() throws IOException when the remote manifest.json fetched from the modpack server fails to parse as McbbsModpackManifest. It wraps the JsonParseException and includes the file API URL, indicating the server returned content that is not a valid MCBBS manifest.

Solutions

  1. Verify the fileApi URL returns a valid MCBBS manifest.json (open it in a browser / curl)
  2. Fix or repoint the modpack server so it serves the correct manifest.json
  3. Reinstall the modpack if the server is permanently gone, or disable the update source
Defensive patterns

Strategy: try-catch

Validate before calling

String body = fetch(manifest.getFileApi());
if (body == null || !body.trim().startsWith("{")) { /* server did not return JSON - abort update */ }

Try / catch

try { task.getFuture().get(); } catch (ExecutionException e) { if (e.getCause() instanceof IOException && e.getCause().getMessage().startsWith("Unable to parse server manifest.json")) { /* check the fileApi URL / server */ } }

Prevention

When it happens

Trigger: Downloading remoteManifestJson from manifest.getFileApi() and JsonUtils.fromNonNullJson throws JsonParseException - e.g. server returned HTML error page, empty body, or JSON in a different schema.

Common situations: Update server misconfigured (404/500 page instead of JSON), CDN serving stale or wrong file, manifest schema version mismatch between server and client expectations.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10). Data as JSON: /api/errors/f1b52a0ec22d4e9c. Report an issue: GitHub.

Appendix: source

Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/mcbbs/McbbsModpackCompletionTask.java:129

            }
            manifest = configuration.getManifest();
            if (manifest == null) throw new CustomException();
        })).thenComposeAsync(unused -> {
            // we first download latest manifest
            return breakable(CompletableFuture.runAsync(wrap(() -> {
                if (StringUtils.isBlank(manifest.getFileApi())) {
                    // skip this phase
                    throw new CustomException();
                }
            })).thenComposeAsync(wrap(unused1 -> {
                return executor.one(new GetTask(manifest.getFileApi() + "/manifest.json"));
            })).thenComposeAsync(wrap(remoteManifestJson -> {
                McbbsModpackManifest remoteManifest;
                // We needs to update modpack from online server.
                try {
                    remoteManifest = JsonUtils.fromNonNullJson(remoteManifestJson, McbbsModpackManifest.class);
                } catch (JsonParseException e) {
                    throw new IOException("Unable to parse server manifest.json from " + manifest.getFileApi(), e);
                }

                Path rootPath = instance.getInstanceRoot();
                Files.createDirectories(rootPath);

                Map<McbbsModpackManifest.File, McbbsModpackManifest.File> localFiles = manifest.getFiles().stream().collect(Collectors.toMap(Function.identity(), Function.identity()));

                // for files in new modpack
                List<McbbsModpackManifest.File> newFiles = new ArrayList<>(remoteManifest.getFiles().size());
                List<Task<?>> tasks = new ArrayList<>();
                for (McbbsModpackManifest.File file : remoteManifest.getFiles()) {
                    Path actualPath = getFilePath(file);
                    McbbsModpackManifest.File oldFile = localFiles.remove(file);
                    boolean download = false;
                    if (oldFile == null) {
                        // If old modpack does not have this entry, download it
                        download = true;
                    } else if (actualPath != null) {

View on GitHub (pinned to 24702dc5a0)