DEV Community

Mayur Bodhare
Mayur Bodhare

Posted on

throw vs throws in Java, and What “Ducking” Means

The first time one of my Java programs crashed, I got a wall of red text that ended with something like Exception in thread "main". I had no idea what it meant, which line to look at or how to stop it from happening again.

Later I learned try and catch, and I thought I understood. Then more questions showed up. What's the difference between throw and throws? What does "ducking an exception" mean? Why does my Scanner crash when the user types a word instead of a number? And why do people say finally isn't enough anymore?

In this post, we'll answer all of that in one place: exceptions, the exception hierarchy, try, catch, finally, throw, throws, ducking, the Scanner class and try-with-resources. I've drawn the key pictures in text so you can see how things flow.

Unless I show a full class, the snippets go inside a main method.

What is an exception?

An exception is an unexpected event that happens while a program is running and disrupts its normal flow. Dividing by zero, reading a file that doesn't exist or calling a method on null are all examples.

Let's see what happens when nobody handles one:

public class Demo {
    public static void main(String[] args) {
        int a = 10, b = 0;
        System.out.println("Before");
        System.out.println(a / b);
        System.out.println("After");
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Before
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at Demo.main(Demo.java:5)
Enter fullscreen mode Exit fullscreen mode

Three things to notice:

  • "Before" printed, but "After" never did. The program stopped at the problem.
  • The first line tells you the type (ArithmeticException) and a message (/ by zero).
  • The at Demo.main(Demo.java:5) line is the stack trace. It tells you where it happened: file Demo.java, line 5. When you read a crash, start at the top and look for the first line that points to your own code.

Behind the scenes: what the JVM does

When something goes wrong, the JVM creates an exception object, fills it with the details (type, message and the call stack) and throws it. The current method stops immediately. Then the JVM goes looking for code that can handle it, first in the current method, then in the method that called it, then the one that called that, moving up the call stack.

   main()  calls  process()  calls  divide()      <-- exception happens here
     ^               ^                 |
     |               |                 |
     +---------------+-----------------+
        the JVM unwinds UP the call stack, looking for a matching catch

   Nobody catches it?  The thread ends and the stack trace is printed.
Enter fullscreen mode Exit fullscreen mode

Exception handling is simply the art of catching it on the way up so the program can continue.

The exception hierarchy

Exceptions are objects, so they're organized into a family tree of classes. Knowing this tree tells you which exceptions you must handle and which you can ignore.

                          Throwable
                         /         \
                    Error           Exception
        (OutOfMemoryError,         /          \
         StackOverflowError)   IOException      RuntimeException
                               SQLException      ArithmeticException
                               FileNotFound-     NullPointerException
                                 Exception       ArrayIndexOutOfBounds-
                               InterruptedException   Exception
                                                 IllegalArgumentException
                               (CHECKED)            NumberFormatException
                                                 InputMismatchException
                                                    (UNCHECKED)
Enter fullscreen mode Exit fullscreen mode

Here's what each branch means:

  • Throwable is the root. Only objects of this class (or its children) can be thrown. It gives you getMessage(), printStackTrace() and getCause().
  • Error means a serious problem in the JVM itself, like running out of memory (OutOfMemoryError) or endless recursion (StackOverflowError). You normally don't catch these, because your program can't repair them.
  • Exception is for problems your program can reasonably handle. It splits into two kinds:

Checked exceptions are everything under Exception except RuntimeException. They're for things outside your program's control, like missing files or network failures (IOException, SQLException). The compiler forces you to handle them or declare them.

new FileReader("data.txt");
Enter fullscreen mode Exit fullscreen mode
unreported exception FileNotFoundException; must be caught or declared to be thrown
Enter fullscreen mode Exit fullscreen mode

Unchecked exceptions are RuntimeException and its children. They usually mean a bug in your code (a bad index, a null reference, a bad argument). The compiler does not force you to handle them, although you still can.

A simple way to remember it: checked means "the compiler checks that you dealt with it," and unchecked means "the compiler leaves it to you."

try and catch

To handle an exception, wrap the risky code in try and write the response in catch:

try {
    int result = 10 / 0;
    System.out.println("Result: " + result);
} catch (ArithmeticException e) {
    System.out.println("Cannot divide by zero: " + e.getMessage());
}
System.out.println("Program continues");
Enter fullscreen mode Exit fullscreen mode

Output:

Cannot divide by zero: / by zero
Program continues
Enter fullscreen mode Exit fullscreen mode

What happened:

  1. Java ran the try block until the exception happened.
  2. It skipped the rest of the try block (so "Result: ..." never printed).
  3. It jumped to the matching catch and ran it.
  4. The program continued normally after the try-catch. No crash.

The variable e is the exception object. Printing it directly gives the type and message together: java.lang.ArithmeticException: / by zero.

Multiple catch blocks

One try can have several catch blocks for different problems. Java runs only the first one that matches:

String[] data = {"10", "abc"};

try {
    int n = Integer.parseInt(data[1]);
    System.out.println(10 / n);
} catch (NumberFormatException e) {
    System.out.println("Not a number: " + e.getMessage());
} catch (ArithmeticException e) {
    System.out.println("Math problem");
} catch (Exception e) {
    System.out.println("Something else: " + e);
}
Enter fullscreen mode Exit fullscreen mode

Output:

Not a number: For input string: "abc"
Enter fullscreen mode Exit fullscreen mode

Order matters. Put the most specific exceptions first and the most general last. If you put Exception first, it would catch everything, and the later blocks could never run. The compiler notices and stops you:

exception ArithmeticException has already been caught
Enter fullscreen mode Exit fullscreen mode

If two exceptions need the same handling, you can combine them with |:

catch (NumberFormatException | ArithmeticException e) {
    System.out.println("Bad input: " + e.getMessage());
}
Enter fullscreen mode Exit fullscreen mode

(The two types can't be parent and child of each other.)

Behind the scenes

The compiler doesn't turn try-catch into extra checks that run on every line. Instead, it writes an exception table next to your bytecode. Each row says: "if an exception of this type happens between instruction A and instruction B, jump to the handler at instruction C." When nothing goes wrong, the table is never used, so a try block costs almost nothing. The real cost comes when an exception is created, because the JVM has to record the stack trace.

finally

Sometimes you need code that runs no matter what, like closing a file or releasing a lock. That's what finally is for:

static void test(int divisor) {
    try {
        System.out.println(10 / divisor);
    } catch (ArithmeticException e) {
        System.out.println("Caught");
    } finally {
        System.out.println("Cleanup");
    }
}
Enter fullscreen mode Exit fullscreen mode
test(2);
test(0);
Enter fullscreen mode Exit fullscreen mode

Output:

5
Cleanup
Caught
Cleanup
Enter fullscreen mode Exit fullscreen mode

The finally block ran in both cases: when everything went fine, and when an exception happened. Here is the flow:

        try block
           |
     exception happened?
      /              \
    no               yes
     |                |
     |         matching catch? -- yes --> run the catch block
     |                |                          |
     |                no                         |
     |       (exception will keep going up)      |
     v                v                          v
   +--------------------------------------------------+
   |        finally block ALWAYS runs here            |
   +--------------------------------------------------+
           |
           v
   continue the program, or keep propagating the exception
Enter fullscreen mode Exit fullscreen mode

try-catch-finally, try-finally

The combinations you can write:

  • try + catch (one or more)
  • try + catch + finally
  • try + finally (no catch at all)

The last one is valid, and it's useful when you want to clean up but let the exception continue to the caller:

try {
    System.out.println("working");
    throw new RuntimeException("boom");
} finally {
    System.out.println("cleanup");
}
Enter fullscreen mode Exit fullscreen mode

Output:

working
cleanup
Exception in thread "main" java.lang.RuntimeException: boom
Enter fullscreen mode Exit fullscreen mode

The cleanup ran first, and then the exception continued upward and crashed the program, because nobody caught it.

When does finally NOT run?

Almost never. It runs even if the try block has a return:

static int f() {
    try {
        return 1;
    } finally {
        System.out.println("finally runs");
    }
}
Enter fullscreen mode Exit fullscreen mode
System.out.println(f());
Enter fullscreen mode Exit fullscreen mode

Output:

finally runs
1
Enter fullscreen mode Exit fullscreen mode

It will not run if the JVM stops (for example System.exit(0)), if the machine crashes or if the try block never ends (an infinite loop).

One warning: never write return or throw inside finally. It can silently replace the real result or hide an exception.

Behind the scenes

The compiler copies the finally code to every exit path: the normal end of try, the end of each catch, and every return. It also adds an "any exception" handler that runs the finally code and then rethrows. That's how Java promises it always runs.

throw vs throws

These two look almost the same and trip up everyone, but they do different jobs.

throw is a statement that actually throws an exception object. You use it inside a method body when you detect a problem.

static void setAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("Age cannot be negative");
    }
    System.out.println("Age set to " + age);
}
Enter fullscreen mode Exit fullscreen mode
try {
    setAge(25);
    setAge(-5);
} catch (IllegalArgumentException e) {
    System.out.println("Error: " + e.getMessage());
}
Enter fullscreen mode Exit fullscreen mode

Output:

Age set to 25
Error: Age cannot be negative
Enter fullscreen mode Exit fullscreen mode

throws is part of a method's signature. It warns callers that this method might throw certain exceptions, so they must be ready.

void readFile(String path) throws IOException, SQLException { ... }
Enter fullscreen mode Exit fullscreen mode

Here's the difference in short:

  • throw throws one exception object, and it's written inside the method body, followed by new SomeException(...).
  • throws declares possible exceptions, and it's written after the parameter list, followed by class names (one or more, separated by commas).
  • throw is the action. throws is the warning label.
  • throws is required for checked exceptions (otherwise the compiler complains). For unchecked ones it's optional.

A trick to remember it: throw, singular, is one throw. throws, plural, can list many.

Custom exceptions

You can write your own exception by extending Exception (checked) or RuntimeException (unchecked). Let's make a checked one for a bank account:

class InsufficientFundsException extends Exception {
    InsufficientFundsException(String message) {
        super(message);
    }
}

class Account {
    private double balance = 500;

    void withdraw(double amount) throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException(
                "Balance " + balance + " is less than " + amount);
        }
        balance -= amount;
        System.out.println("Withdrawn " + amount);
    }
}
Enter fullscreen mode Exit fullscreen mode
Account account = new Account();

try {
    account.withdraw(200);
    account.withdraw(600);
} catch (InsufficientFundsException e) {
    System.out.println("Failed: " + e.getMessage());
}
Enter fullscreen mode Exit fullscreen mode

Output:

Withdrawn 200.0
Failed: Balance 300.0 is less than 600.0
Enter fullscreen mode Exit fullscreen mode

Because InsufficientFundsException is checked, the compiler forced two things: withdraw had to declare throws, and the caller had to catch it (or declare it too).

Ducking an exception

Now the funny word. Ducking an exception means not handling it in this method, and passing it on to the caller by declaring it with throws. The method "ducks" out of the way, and the problem flies over its head to whoever called it.

static void level3() throws Exception {
    throw new Exception("Problem in level3");
}

static void level2() throws Exception {
    level3();                  // no try-catch here: level2 ducks it
}

static void level1() {
    try {
        level2();
    } catch (Exception e) {
        System.out.println("Handled in level1: " + e.getMessage());
    }
}
Enter fullscreen mode Exit fullscreen mode
level1();
Enter fullscreen mode Exit fullscreen mode

Output:

Handled in level1: Problem in level3
Enter fullscreen mode Exit fullscreen mode
   level1()  --calls-->  level2()  --calls-->  level3()
      ^                     ^                     |
      |                     |                     | throw new Exception(...)
      |                     +----- DUCKS ---------+  (just declares "throws")
      |                                           
      +------- CATCHES it and handles the problem
Enter fullscreen mode Exit fullscreen mode

When should you catch, and when should you duck?

  • Catch where you can do something useful: show a message, retry, use a default value or clean up.
  • Duck when this method can't decide what to do, and a higher-level method is in a better position. A low-level file reader doesn't know whether to show a popup or write a log, but the code that called it does.

Be careful with one case: if main ducks (public static void main(String[] args) throws Exception), the exception goes to the JVM, and your program crashes. That's fine for quick experiments, but not for real programs.

The Scanner class

Now let's apply this to something you'll use constantly. The Scanner class (in java.util) reads input: from the keyboard, from a file or from a string. It splits the input into pieces called tokens, separated by whitespace by default.

The most-used methods are:

  • nextInt(), nextDouble(), nextBoolean() read the next token as that type.
  • next() reads the next word (one token).
  • nextLine() reads the whole rest of the line.
  • hasNextInt(), hasNext() check whether the next token exists or has the right type, without reading it.
  • close() releases the resource.
import java.util.Scanner;

public class Greeting {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.print("Enter your name: ");
        String name = sc.nextLine();

        System.out.print("Enter your age: ");
        int age = sc.nextInt();

        System.out.println("Hello " + name + ", next year you will be " + (age + 1));
        sc.close();
    }
}
Enter fullscreen mode Exit fullscreen mode

A sample run (the second part of each line is what the user types):

Enter your name: Asha
Enter your age: 25
Hello Asha, next year you will be 26
Enter fullscreen mode Exit fullscreen mode

Behind the scenes

System.in is a stream of raw bytes from the keyboard. The Scanner wraps it, reads the bytes into a buffer, and hands them to you one token at a time:

   keyboard --> System.in (bytes) --> Scanner's buffer --> tokens --> your variables

   You type:   25 [Enter]
   Buffer:     2 5 \n
               ^^^^
               nextInt() takes the token "25" and checks it looks like an int
               The "\n" (the Enter key) stays behind in the buffer
Enter fullscreen mode Exit fullscreen mode

Two details here matter for everything that follows:

  • nextInt() takes the token, but it leaves the newline (\n) in the buffer.
  • If the token isn't a valid number, nextInt() throws an InputMismatchException and leaves the bad token in the buffer (it does not consume it).

The famous nextLine trap

Let's flip the order of our program and read the age first, then the name:

System.out.print("Enter age: ");
int age = sc.nextInt();

System.out.print("Enter name: ");
String name = sc.nextLine();

System.out.println("Age: " + age + ", Name: [" + name + "]");
Enter fullscreen mode Exit fullscreen mode

A sample run:

Enter age: 25
Enter name: Age: 25, Name: []
Enter fullscreen mode Exit fullscreen mode

The program never waited for the name! After nextInt(), the buffer still held \n. Then nextLine() read from the current position to the end of the line, which is nothing, so it returned an empty string right away.

   Buffer after nextInt():     \n
                               ^^
   nextLine() reads "everything up to the newline", which is an empty text
Enter fullscreen mode Exit fullscreen mode

The fix is to eat the leftover newline with an extra nextLine():

int age = sc.nextInt();
sc.nextLine();                 // consume the leftover newline
String name = sc.nextLine();   // now this waits for the real name
Enter fullscreen mode Exit fullscreen mode

Another safe option is to read everything with nextLine() and convert yourself: int age = Integer.parseInt(sc.nextLine());.

Exceptions that Scanner can throw

  • InputMismatchException (unchecked): the token isn't the right type, like "abc" for nextInt().
  • NoSuchElementException (unchecked): there's no more input to read.
  • IllegalStateException (unchecked): you used the scanner after closing it.
  • FileNotFoundException (checked): when creating a Scanner from a File that doesn't exist.

Scanner with exception handling

Here's the classic job: keep asking until the user types a valid age.

import java.util.InputMismatchException;
import java.util.Scanner;

public class SafeInput {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int age = 0;
        boolean valid = false;

        while (!valid) {
            try {
                System.out.print("Enter your age: ");
                age = sc.nextInt();

                if (age < 0 || age > 120) {
                    throw new IllegalArgumentException("Age must be between 0 and 120");
                }
                valid = true;

            } catch (InputMismatchException e) {
                System.out.println("Please enter digits only.");
                sc.nextLine();                    // throw away the bad input

            } catch (IllegalArgumentException e) {
                System.out.println(e.getMessage());
            }
        }

        System.out.println("Age accepted: " + age);
    }
}
Enter fullscreen mode Exit fullscreen mode

A sample run, where the user types abc, then -5, then 25:

Enter your age: abc
Please enter digits only.
Enter your age: -5
Age must be between 0 and 120
Enter your age: 25
Age accepted: 25
Enter fullscreen mode Exit fullscreen mode

Look at how our new tools work together:

  • nextInt() throws InputMismatchException for abc, and the catch handles it.
  • We used throw ourselves for a value that is a valid number but makes no sense (-5).
  • The sc.nextLine() inside the first catch is essential. Remember: the bad token stays in the buffer. Without that line, the next nextInt() would see abc again, throw again, and the loop would repeat forever.

An alternative without exceptions

When bad input is expected (users make typos all the time), you can check first instead of catching:

Scanner sc = new Scanner(System.in);

System.out.print("Enter a number: ");
while (!sc.hasNextInt()) {
    System.out.println("Digits only, please.");
    sc.next();                      // discard the bad token
    System.out.print("Enter a number: ");
}
int n = sc.nextInt();
System.out.println("You entered " + n);
Enter fullscreen mode Exit fullscreen mode

A sample run:

Enter a number: hello
Digits only, please.
Enter a number: 7
You entered 7
Enter fullscreen mode Exit fullscreen mode

Both approaches are fine. The hasNextInt() style treats bad input as normal, so it needs no exception at all.

try-with-resources

Many things you use must be closed when you're done: a Scanner, files, network connections, database connections. If you forget, they leak. The old way was a finally block:

Scanner sc = null;
try {
    sc = new Scanner(new File("data.txt"));
    System.out.println(sc.nextLine());
} catch (FileNotFoundException e) {
    System.out.println("File not found");
} finally {
    if (sc != null) {
        sc.close();
    }
}
Enter fullscreen mode Exit fullscreen mode

That's a lot of code just to close something, and it's easy to get wrong. try-with-resources does it for you. You open the resource inside parentheses after try, and Java closes it automatically at the end:

try (Scanner sc = new Scanner(new File("data.txt"))) {
    System.out.println(sc.nextLine());
} catch (FileNotFoundException e) {
    System.out.println("File not found");
}
Enter fullscreen mode Exit fullscreen mode

No finally, no null check, no close() call. The rules:

  • The resource's class must implement AutoCloseable (or Closeable, which extends it). Scanner does.
  • The resource variable is implicitly final.
  • You can open several resources, separated by semicolons.
  • catch and finally blocks are optional.
  • Since Java 9, you can use an existing effectively final variable: try (sc) { ... }.

What happens in what order?

Let's use a custom resource to see the exact order:

class Resource implements AutoCloseable {
    private final String name;

    Resource(String name) {
        this.name = name;
        System.out.println("Open " + name);
    }

    void use() {
        System.out.println("Using " + name);
    }

    @Override
    public void close() {
        System.out.println("Close " + name);
    }
}
Enter fullscreen mode Exit fullscreen mode
try (Resource a = new Resource("A"); Resource b = new Resource("B")) {
    a.use();
    b.use();
    throw new RuntimeException("Oops");
} catch (RuntimeException e) {
    System.out.println("Caught " + e.getMessage());
} finally {
    System.out.println("Finally");
}
Enter fullscreen mode Exit fullscreen mode

Output:

Open A
Open B
Using A
Using B
Close B
Close A
Caught Oops
Finally
Enter fullscreen mode Exit fullscreen mode

Two rules are visible here:

  • Resources are closed in reverse order of opening (B before A), like unstacking plates.
  • They're closed before the catch and finally blocks run. So by the time your catch executes, the resources are already safely closed.

Behind the scenes

The compiler rewrites try-with-resources into a try-finally, roughly like this:

   try (Resource r = new Resource()) {          Resource r = new Resource();
       use(r);                          ==>     Throwable primary = null;
   }                                            try {
                                                    use(r);
                                                } catch (Throwable t) {
                                                    primary = t;  throw t;
                                                } finally {
                                                    if (r != null) {
                                                        if (primary != null) {
                                                            try { r.close(); }
                                                            catch (Throwable s) { primary.addSuppressed(s); }
                                                        } else {
                                                            r.close();
                                                        }
                                                    }
                                                }
Enter fullscreen mode Exit fullscreen mode

You don't need to memorize this. The key part is the suppressed exception handling. What if your code throws an exception and close() also throws? With a hand-written finally, the second exception would replace the first one, and you'd lose the real cause. With try-with-resources, the original exception stays as the main one, and the close failure is attached to it as a suppressed exception that you can read with e.getSuppressed(). That's one more reason it's better than a manual finally.

Here's the earlier idea with a Scanner, fully contained, with no files and no keyboard, so you can run it right now:

try (Scanner sc = new Scanner("10 20 abc")) {
    int sum = 0;
    while (sc.hasNext()) {
        sum += sc.nextInt();
    }
    System.out.println(sum);
} catch (InputMismatchException e) {
    System.out.println("Bad number found");
}
Enter fullscreen mode Exit fullscreen mode

Output:

Bad number found
Enter fullscreen mode Exit fullscreen mode

A Scanner can read from a plain String, so "10 20 abc" is our fake input. It added 10 and 20, then hit abc and threw. The scanner was closed automatically, and the catch handled the problem.

One note about the keyboard: if you wrap new Scanner(System.in) in try-with-resources, closing it also closes System.in, and you can't read from the keyboard again later in the same program. For a console program that finishes right after, that's fine. Just don't create and close several scanners on System.in.

Which one should I use?

  • try-catch or just let it crash? Catch an exception when you can do something useful about it (retry, use a default, show a clear message). If you can't, let it travel up, or duck it.
  • Catch or duck? Catch it in the method that knows what to do. Duck it when a higher level is in a better position to decide.
  • try-catch-finally or try-with-resources? If the thing you're closing implements AutoCloseable (files, scanners, connections), use try-with-resources. Use a plain finally only for cleanup that isn't a closeable resource, like resetting a flag or unlocking a lock.
  • Checked or unchecked for my own exceptions? Make it checked when the caller can realistically recover from it (like InsufficientFundsException). Make it unchecked for programming mistakes (like a bad argument). Modern code leans toward unchecked, because checked ones force a lot of boilerplate.
  • Scanner: hasNextInt() or try-catch? Use hasNextInt() when bad input is a normal, expected thing. Use try-catch when you read many values and want a single place to handle failure.

Common mistakes

  1. Empty catch blocks. catch (Exception e) { } hides the problem completely. At the very least, print or log the exception.
  2. Catching Exception or Throwable everywhere. It also swallows bugs you wanted to see, and Throwable even catches serious Errors. Catch the most specific type you can handle.
  3. Putting a general catch before a specific one. The compiler says the specific exception "has already been caught." Specific first, general last.
  4. Forgetting to close resources. Use try-with-resources so you never have to remember.
  5. Falling into the Scanner traps. Using nextLine() right after nextInt() without consuming the newline, and not clearing a bad token after InputMismatchException (which causes an infinite loop).
  6. Mixing up throw and throws. throw goes in the body with an object (throw new X()). throws goes on the method signature with class names. Writing throws new X() or throw X is a compile error.

Another habit to avoid is using exceptions for normal flow, like looping until an exception happens. Exceptions are for exceptional situations, and they're slower than a simple if.

Quick recap

  • An exception is an object that represents a problem at run time. If nobody handles it, the program stops with a stack trace.
  • The tree: Throwable splits into Error (don't catch) and Exception. Under Exception, checked exceptions must be handled or declared, and unchecked ones (RuntimeException and children) are optional.
  • try wraps risky code, catch handles it (specific first, general last), and finally always runs.
  • throw throws one exception object inside a method. throws declares what a method might throw.
  • Ducking means declaring throws instead of catching, so the caller deals with it.
  • Scanner reads tokens from the keyboard, files or strings. nextInt() leaves the newline behind, and a bad token stays in the buffer.
  • Handle bad input with try-catch plus sc.nextLine() to clear it, or check ahead with hasNextInt().
  • try-with-resources closes AutoCloseable resources automatically, in reverse order, before catch and finally, and it keeps the original exception safe.

Try it yourself

Here's a small practice idea: build a console calculator. Ask the user for two numbers and an operator (+, -, *, /) using Scanner. Keep asking until the numbers are valid. Handle division by zero with try-catch, and throw your own InvalidOperatorException (a custom checked exception) if the operator isn't one of the four. Wrap the whole Scanner in try-with-resources.

For a bonus experiment, create a text file with a few numbers and read it using new Scanner(new File("numbers.txt")). Handle the FileNotFoundException, then try renaming the file to see the exception in action.

If this helped, or if you spotted something I should fix, tell me in the comments. I'm still learning too.

Top comments (0)