Annotations, docs, stack traces, and the IDE
Reading and using annotations, understanding a stack trace, and working effectively in a real editor.
Finished reading?
Mark this session so you can track where you are.
Reading and using annotations, understanding a stack trace, and working effectively in a real editor.
Finished reading?
Mark this session so you can track where you are.
Up to now we have written code that the computer runs. This session is about the layer around that code: small notes you leave for the compiler, the documentation you read instead of guessing, the crash report Java hands you when something goes wrong, and the editor that ties it all together. None of this is new Java syntax to memorize. It is the craft of working like an engineer.
@Override, @Deprecated, @SuppressWarnings.An annotation is a small label, written with an @ sign, that you attach to a piece of code: a method, a field, a class. It does not change what the code doesat runtime on its own. It is metadata, which means “data about the code”. Some annotations are read by the compiler to check your work. Some are read by tools. Some are just documentation for the next human.
You have actually seen one already without us naming it. When you override a method, it is good practice to write @Override on the line above. Here is what it looks like in context.
public class Main { public static void main(String[] args) { Animal a = new Dog(); a.speak(); }} class Animal { void speak() { System.out.println("..."); }} class Dog extends Animal { @Override void speak() { System.out.println("Woof"); }}Woof
Read @Overrideas a promise to the compiler: “I intend for this method to replace one from the parent class.” The compiler then checks that the promise is true. If a method with that exact name and parameters really does exist in Animal, all is well. If it does not, the compiler stops you with an error instead of letting a silent bug through.
@Overridedoes not make overriding happen. Overriding already happens by matching the method's name and parameters. The annotation just asks the compiler to confirm you really did what you meant to.
speak() but typed speek() by accident, or gave it a wrong parameter. Without @Override, Java happily treats it as a brand new method, your real override never runs, and you waste an afternoon. With @Override, the compiler says “this does not override anything” immediately. Put it on every method you intend as an override.@Deprecated marks something as old and best avoided. The code still works, but you are warning future readers: there is a newer, better way, so do not reach for this in new code. When you call something marked @Deprecated, the compiler nudges you with a warning. You see this a lot in the real Java library, where a method from an old version stays around so it does not break existing programs, but is gently labelled “not this one anymore”.
public class Main { public static void main(String[] args) { Thermostat t = new Thermostat(); // The compiler warns here: oldReading() is deprecated. System.out.println(t.oldReading()); // Preferred replacement, no warning. System.out.println(t.reading()); }} class Thermostat { @Deprecated int oldReading() { return 13; } String reading() { return "13 C"; }}13 13 C
@SuppressWarningsgoes the other way. It tells the compiler “I know you want to warn me about this, and I have a good reason, so stay quiet here.” You name the specific warning you are silencing, for example @SuppressWarnings("deprecation") to silence the nudge about calling a deprecated method on purpose. Use it sparingly and locally. A warning is the compiler trying to help; silence it only when you have genuinely understood it and decided it does not apply.
@Override@Deprecated@SuppressWarnings@ labels as notes, not magic.The Java standard library is enormous. Nobody memorizes it. The skill that separates a confident programmer from a stuck one is not knowing every method, it is knowing how to look one up quickly and read what it says. That reference is called the Javadoc: official, generated documentation for every class and method in the library.
When you look up a method, four things are worth finding every time. Read them in this order and a method that looked mysterious becomes a small contract you can trust.
String substring(int beginIndex, int endIndex). This tells you what to pass and in what order.beginIndex is where the slice starts and endIndex is one past where it ends. The docs spell out the subtle cases so you do not have to discover them by crashing.substring returns a new String; it does not change the original. The docs say so explicitly.substring throws an IndexOutOfBoundsException if you ask for a range outside the string. Knowing this ahead of time is how you write the guard before the crash, not after.Here is the documented behavior put to work. We trust the docs, then confirm them by running the code and reading the output.
public class Main { public static void main(String[] args) { String word = "atelier"; // From the docs: substring(begin, end) takes chars // from begin up to but NOT including end. String part = word.substring(0, 4); System.out.println(part); System.out.println(part.length()); System.out.println(word); }}atel 4 atelier
The output is atel, then 4, then atelier. Every line matches what the docs promised: the slice runs from index 0 up to but not including 4, its length is 4, and the original word is untouched because substring returns a new string. We did not have to guess any of that. We read the contract and the code obeyed it.
When a Java program hits an error it cannot handle, it stops and prints a stack trace: a report of what went wrong and where. Beginners often glance at the wall of text, feel a flash of panic, and scroll away. That is the wrong instinct. A stack trace is the single most useful gift the program can give you. It tells you almost exactly where the bug is, if you read it in the right order.
Read it in two passes.
NullPointerException means you used something that was null as if it were a real object. The type and message are your headline.at, are the call chain, newest first. The topmost at line is exactly where the program was when it crashed. Each line under it is the method that called the one above. You read down the chain until you find a line in your own code, and that is almost always your bug.Here is a real-looking trace. Read the headline, then find the first line that names a file we wrote.
public class Main { public static void main(String[] args) { Greeter g = new Greeter(); System.out.println(g.greet(null)); // line 4 }} class Greeter { String greet(String name) { int n = name.length(); // line 7: name is null here return "Hello, " + name + " (" + n + ")"; }}Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "name" is null at Greeter.greet(Greeter.java:7) at Main.main(Main.java:4)
Walk the trace. The headline says NullPointerException and even tells us the cause: name was null when we called length() on it. The first at line points at Greeter.java:7, which is the line int n = name.length();. That is the crash site. The next line, Main.java:4, is who called greet, and there we see the culprit: we passed null in. The trace handed us both the symptom and its origin in three lines.
Top line: what went wrong. First
atline: where it broke. Walk down to the first line in your own files: that is the bug to fix.
null was passed in. A stack trace shows you the crash site at the top and the path that led there below it. Read down the chain. The real fix is often a few frames lower than where the smoke is.The Atelier visualizer cannot print Java's exact multi-line trace, but it does the same job in a friendlier voice. Run the snippet below. It uses an object that was never built, so calling a method on it fails the same way a NullPointerException does. Watch where the run stops and which line is marked.
public class Main { public static void main(String[] args) { Account a = new Account(); a.balance = 100; Account b = null; // never built b.deposit(50); // using a null object: this is the crash System.out.println(a.balance); }} class Account { int balance; void deposit(int amount) { balance = balance + amount; }}NullPointerException: tried to use an object that is null
The run stops at b.deposit(50) and reports that you tried to use an object that is null. b was set to null and never pointed at a real Account, so there is no object to deposit into. Compare the two reports. The terminal stack trace and the visualizer say the same thing in different words: the headline is “you used a null as if it were an object”, and the marked line is where you did it. Reading either one, your job is identical: find the null, then walk back to where it should have been a real object.
An IDE, an Integrated Development Environment, is an editor built for one language with tools woven in: it understands your code as you type, not just the letters but the meaning. The popular ones for Java are IntelliJ IDEA, Eclipse, and VS Code with a Java extension. You do not need to master one today. You do need to know the four moves that make you fast, because each one removes a kind of guessing.
Type a variable, then a dot, and the editor lists every method and field available on it. Instead of remembering whether a string's method is toUpper() or toUpperCase(), you type word. and pick from the list. Auto-complete is the docs brought into the exact spot you are typing. It also quietly teaches you what a type can do.
Hold the right key and click a method or class name, and the editor jumps to where it is defined. When you see g.greet(null) and wonder what greet actually does, you do not search the project by hand. You jump straight to its source. This turns a large unfamiliar codebase from a maze into something you can navigate.
This is the big one, and it should feel familiar, because it is exactly what the Atelier visualizer has been doing for you all along. A debugger lets you pause a running program and look inside it. The vocabulary is small.
A debugger is a slow-motion, pausable view of your program with every variable on display. It turns “I think it does this” into “I can see it does this”.
trace snippet and stepped through it watching the values change, you were using a debugger. The step buttons are step over and step into. The state panel is inspect variables. The marked line is the breakpoint you are paused on. A real IDE debugger is the same idea pointed at your own running programs, with the difference that you choose where to pause.Try each one yourself first, then open the answer.
tostring() in a subclass meaning to override toString() from the parent, and you add @Override above it. What happens, and why is that good?word.indexOf("e") and you are not sure what it returns when the letter is not found. What is the right first move, and what are the four things to read?java.lang.NullPointerException ... at Greeter.greet(Greeter.java:7) at Main.main(Main.java:4)Take these away. They continue exactly what we just did.
@Override to each method you intended as an override. Then deliberately misspell one method name and confirm the compiler complains. Restore it afterwards. Write one sentence on what the annotation bought you.String.replace in the Java docs. In your own words, write down its signature, what its two parameters mean, what it returns, and whether it changes the original string. Then write a tiny program that confirms your reading.Account for b before depositing. Write two or three sentences explaining, using the words from the stack-trace section, what was wrong before and which line the report pointed at.