0

I am writing a simple console program for Minecraft in javaFX. Sometimes it works fine but sometimes when the console is updating ill randomly get spammed with this error:

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException: Cannot read the array length because "this.runs" is null
    at javafx.graphics@18.0.1/com.sun.javafx.text.PrismTextLayout.addTextRun(PrismTextLayout.java:770)
    at javafx.graphics@18.0.1/com.sun.javafx.text.GlyphLayout.addTextRun(GlyphLayout.java:140)
    at javafx.graphics@18.0.1/com.sun.javafx.text.GlyphLayout.breakRuns(GlyphLayout.java:210)
    at javafx.graphics@18.0.1/com.sun.javafx.text.PrismTextLayout.buildRuns(PrismTextLayout.java:785)
    at javafx.graphics@18.0.1/com.sun.javafx.text.PrismTextLayout.layout(PrismTextLayout.java:1036)
    at javafx.graphics@18.0.1/com.sun.javafx.text.PrismTextLayout.ensureLayout(PrismTextLayout.java:223)
    at javafx.graphics@18.0.1/com.sun.javafx.text.PrismTextLayout.getBounds(PrismTextLayout.java:246)
    at javafx.graphics@18.0.1/javafx.scene.text.Text.getLogicalBounds(Text.java:432)
    at javafx.graphics@18.0.1/javafx.scene.text.Text.doComputeGeomBounds(Text.java:1187)
    at javafx.graphics@18.0.1/javafx.scene.text.Text$1.doComputeGeomBounds(Text.java:149)
    at javafx.graphics@18.0.1/com.sun.javafx.scene.shape.TextHelper.computeGeomBoundsImpl(TextHelper.java:90)
    at javafx.graphics@18.0.1/com.sun.javafx.scene.NodeHelper.computeGeomBounds(NodeHelper.java:116)
    at javafx.graphics@18.0.1/javafx.scene.Node.updateGeomBounds(Node.java:3818)
    at javafx.graphics@18.0.1/javafx.scene.Node.getGeomBounds(Node.java:3780)
    at javafx.graphics@18.0.1/javafx.scene.Node.getLocalBounds(Node.java:3728)
    at javafx.graphics@18.0.1/javafx.scene.Node$MiscProperties$3.computeBounds(Node.java:6818)
    at javafx.graphics@18.0.1/javafx.scene.Node$LazyBoundsProperty.get(Node.java:9712)
    at javafx.graphics@18.0.1/javafx.scene.Node$LazyBoundsProperty.get(Node.java:9682)
    at javafx.graphics@18.0.1/javafx.scene.Node.getBoundsInLocal(Node.java:3408)
    at javafx.controls@18.0.1/javafx.scene.control.skin.TextAreaSkin$ContentView.layoutChildren(TextAreaSkin.java:1324)
    at javafx.graphics@18.0.1/javafx.scene.Parent.layout(Parent.java:1207)
    at javafx.graphics@18.0.1/javafx.scene.Parent.layout(Parent.java:1214)
    at javafx.graphics@18.0.1/javafx.scene.Parent.layout(Parent.java:1214)
    at javafx.graphics@18.0.1/javafx.scene.Parent.layout(Parent.java:1214)
    at javafx.graphics@18.0.1/javafx.scene.Parent.layout(Parent.java:1214)
    at javafx.graphics@18.0.1/javafx.scene.Parent.layout(Parent.java:1214)
    at javafx.graphics@18.0.1/javafx.scene.Scene.doLayoutPass(Scene.java:579)
    at javafx.graphics@18.0.1/javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2499)
    at javafx.graphics@18.0.1/com.sun.javafx.tk.Toolkit.lambda$runPulse$2(Toolkit.java:405)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at javafx.graphics@18.0.1/com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:404)
    at javafx.graphics@18.0.1/com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:434)
    at javafx.graphics@18.0.1/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:575)
    at javafx.graphics@18.0.1/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:555)
    at javafx.graphics@18.0.1/com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:548)
    at javafx.graphics@18.0.1/com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$11(QuantumToolkit.java:352)
    at javafx.graphics@18.0.1/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
    at javafx.graphics@18.0.1/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics@18.0.1/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184)
    at java.base/java.lang.Thread.run(Thread.java:833)

The error message is not even remotely helpful and I cant figure out whats triggering the error. If anyone knows what triggers this error please let me know so I can fix it. Thanks.

Edit: Heres my source code

package com.koolade446.mconsole;

import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventType;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.*;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import org.jetbrains.annotations.Nullable;
import org.json.JSONObject;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

public class MainController {

    @FXML
    public ComboBox<String> typeBox;
    @FXML
    public ComboBox<String> versionBox;
    @FXML
    public TextArea console;
    @FXML
    public Button startStopButton;
    @FXML
    public Button killButton;
    @FXML
    Button changeVersionButton;
    @FXML
    Label pathContainer;
    @FXML
    TextField commandBox;

    private final Map<String, List<String>> typeMap = new HashMap<>();

    private boolean serverIsStarted = false;
    private ExecutorService executorService;

    private Path directoryPath = Path.of(new File(".").getCanonicalPath());
    private final String[] endpoints = new String[]{
            "servers/spigot",
            "vanilla/vanilla",
            "vanilla/snapshot",
            "modded/forge",
            "modded/fabric"
    };
    File serverJar = new File(directoryPath + "/server.jar");
    private PrintWriter outputStreamWriter = null;

    public MainController() throws IOException {
    }


    public void initialize() throws IOException, URISyntaxException {

        startStopButton.setStyle("-fx-background-image: url('power.png')");
        killButton.setStyle("-fx-background-image: url('kill.png')");
        pathContainer.setText(directoryPath.toString());

        Platform.runLater(() -> {
            try {
                for (String endpoint : endpoints) {
                    List<String> l = new ArrayList<>();
                    URL url = new URL("https://serverjars.com/api/fetchAll/" + endpoint);
                    HttpURLConnection con = (HttpURLConnection) url.openConnection();
                    con.setRequestMethod("GET");
                    con.connect();

                    JSONObject responseJson = new JSONObject(new BufferedReader(new InputStreamReader(con.getInputStream())).lines().collect(Collectors.joining()));

                    con.disconnect();

                    for (Object obj : responseJson.getJSONArray("response")) {
                        JSONObject jsonObject = (JSONObject) obj;
                        l.add(jsonObject.getString("version"));
                    }
                    typeMap.put(endpoint.split("/")[1].replace("/", ""), l);
                }
                typeBox.getItems().addAll(typeMap.keySet());
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    public void changeVersionOptions() {
        versionBox.getItems().clear();
        versionBox.setValue(null);
        versionBox.getItems().addAll(typeMap.get(typeBox.getValue()));
    }

    public void updateVersion(ActionEvent actionEvent) throws MalformedURLException {
        printToConsole("INFO", "Attempting to update to " + typeBox.getValue() + " " + versionBox.getValue());
        Platform.runLater(() -> {
            try {
                URL url = new URL("https://serverjars.com/api/fetchJar/" + endpoints[getClosestIndex(endpoints, typeBox.getValue())] + "/" + versionBox.getValue());
                HttpURLConnection con = (HttpURLConnection) url.openConnection();
                con.setRequestMethod("GET");
                printToConsole("INFO", "Connecting to servers...");
                con.connect();

                printToConsole("INFO", "Downloading new jar file...");
                InputStream is = con.getInputStream();
                FileOutputStream fos = new FileOutputStream(serverJar);

                printToConsole("INFO", "Writing new jar file...");
                byte[] jarFileData = is.readAllBytes();

                System.out.println(serverJar.getPath());

                fos.write(jarFileData, 0, jarFileData.length);

                is.close();
                fos.close();
                con.disconnect();

                printToConsole("INFO", "Version successfully updated!");
            } catch (Exception e) {
                printToConsole("ERROR", "Error when updating version. Please make sure you select a type and version");
                e.printStackTrace();
            }
        });
    }

    public void changeFolder(ActionEvent event) throws IOException {
        DirectoryChooser directoryChooser = new DirectoryChooser();
        directoryChooser.setTitle("Select minecraft server folder");
        File file = directoryChooser.showDialog(null);

        directoryPath = Path.of(file.getCanonicalPath());
        pathContainer.setText(directoryPath.toString());
        serverJar = new File(directoryPath + "/server.jar");
    }

    private int getClosestIndex(String[] array, String option) throws Exception {
        int index = -1;
        for (String str : array) {
            index++;
            if (str.contains(option)) return index;
        }
        return 0;
    }

    private void printToConsole(String sender, String message) {
        console.appendText(String.format(
                "[%s] %s\n",
                sender,
                message
        ));
    }

    public void togglePowerState(ActionEvent actionEvent) {
        if (executorService == null || executorService.isTerminated()) {
            //This is the bit that I think is causing it but idk how or how to fix it
            Task<Boolean> runnable = new Task() {
                @Override
                protected Boolean call() {
                    try {
                        Process process;
                        ProcessBuilder pb = new ProcessBuilder("java", "-jar", "-Xmx5G", "-Xms1G", "server.jar", "nogui");
                        pb.directory(new File(directoryPath.toString()));
                        process = pb.start();
                        BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
                        setOutputStreamWriter(new PrintWriter(process.getOutputStream()));
                        String line;
                        while (process.isAlive()) {
                            if ((line = br.readLine()) != null) {
                                printToConsole("MINECRAFT", line);
                            }
                        }
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                    return true;
                }
            };


            executorService = Executors.newFixedThreadPool(1);
            executorService.execute(runnable);
            executorService.shutdown();


        } else if (!executorService.isTerminated()) {
            outputStreamWriter.println("stop");
            outputStreamWriter.flush();
        }
    }

    public void killServer(ActionEvent actionEvent) {
        if (!executorService.isTerminated()) executorService.shutdownNow();
    }

    public void sendCommand(ActionEvent event) throws IOException {
        outputStreamWriter.println(commandBox.getText());
        outputStreamWriter.flush();
        commandBox.setText("");
    }

    public void onKeyPress(KeyEvent keyEvent) throws IOException {
        if (keyEvent.getCode().equals(KeyCode.ENTER)) {
            outputStreamWriter.println(commandBox.getText());
            outputStreamWriter.flush();
            commandBox.setText("");
        }
    }

    public void setOutputStreamWriter(PrintWriter outputStreamWriter) {
        this.outputStreamWriter = outputStreamWriter;
    }
}
  • 2
    It’s unlikely you’ll be able to get help with this without code that can reproduce the problem. This kind of stack trace (with no reference to your code) is sometimes caused by incorrect use of multithreading. – James_D Dec 17 '22 at 15:02
  • This is probably what it is im pretty new to multithreading and I had to run the minecraft server in a seperate thread I'll update the post with my code its long tho do dorry – Kyle Derstein Dec 17 '22 at 15:03
  • 2
    It looks like you may be trying to modify a `TextArea` from a background thread. – James_D Dec 17 '22 at 15:04
  • 1
    See if you can create a simple example which reproduced the error (ie start with a complete new project, instead of posting your actually project here) – James_D Dec 17 '22 at 15:05
  • I am indeed should I use queues or atomics instead? – Kyle Derstein Dec 17 '22 at 15:08
  • 2
    You’re calling `printToConsole` from a background thread in the `Task`. That call updates the UI, so it should be run from the FX Application Thread. Just wrap that line in `Platform.runLater()`. – James_D Dec 17 '22 at 15:13
  • You sir are a legend. I had tried wrapping the whole thread operation in Platform.runLater() but never just that line. Thank you much – Kyle Derstein Dec 17 '22 at 15:15
  • 1
    There’s lots of other code here that doesn’t make sense. You’re using `Platform.runLater()` in `initialize()` and in an event handler, both of which are already called on the FX Application Thread. And you shouldn’t create a new executor service every time you need one. – James_D Dec 17 '22 at 15:15
  • 1
    [mcve] please (make sure to read that help page to understand/learn what __M__ is supposed to be and how to achieve it :) – kleopatra Dec 17 '22 at 15:42

0 Answers0