Demo: static vs instance, seen in the debugger
On This Lesson 11 topics
Lesson Overview
In Lesson 5 you learned that instance members belong to each object and static members to the class. That is easy to say and easy to forget. In this follow-along lesson you will see it: you will stop the program with a breakpoint, open the debugger's variables view, and look at where each piece of data lives.
You met the debugger in the debugging module: breakpoints, stepping, and the variables view. Here you use those tools on something new, a program with three objects and one static counter. In the blueprint picture, you will see three houses, each with its own paint colour, and one drawing in the architect's office that holds the shared number.
Why this matters: when a backend misbehaves, the fastest way to understand shared state is to look at it. A debugger turns the abstract "this field is shared" into a visible fact, and a field that is shared by mistake is a classic bug.
This lesson builds on Lessons 5 and 6 and on the debugging module. Debugger menus and panel names differ between IDEs, so we describe actions generically and tell you what to look for.
Learning Objectives
By the end of this lesson you will be able to:
- Set a breakpoint and read objects in the debugger's variables view.
- Point out in the debugger which data is per object and which is per class.
- Read
Account.countand explain why it gives the same value through any route. - Recognise the symptom of an accidentally
staticfield and fix it. - Draw a picture of objects and class-level data for a small program.
- Choose between static and instance for a new member and justify it.
Written Explanation
The program
Use the Account class from Lesson 6, with its static count incremented in the full constructor. Replace Main with this:
public class Main {
public static void main(String[] args) {
Account a = new Account("Ana", "A-1", 100);
Account b = new Account("Omar", "A-2", 40);
Account c = new Account("Sara", "A-3", 7);
System.out.println(Account.count);
System.out.println(c.count);
}
}Running it prints (JDK 21):
3
3Both lines print 3. The second line reads count through c, which compiles but is discouraged; it shows that no matter which object you use, you reach the same single variable. We will confirm that in the debugger.
Step 1: add the counter
If your Account has no counter yet, add static int count = 0; at the top of the class and count++; at the end of the full constructor. Compile and run to check that the output above is what you get.
Step 2: breakpoint after the third object
Set a breakpoint on the first System.out.println line (in most IDEs you click next to the line number). Start the program in debug mode, not the normal run mode, which is usually a separate button or menu item. The program runs and pauses at the breakpoint, before that line executes. By now three accounts exist.
Step 3: read the variables view
Find the panel that lists variables (names vary: Variables, Locals, or similar). You should see three local variables a, b, c, and args. Expand a, b, and c. The exact layout depends on your IDE, but you should see, per object, its instance fields:
| Variable | owner | number | balance |
|---|---|---|---|
a | Ana | A-1 | 100.0 |
b | Omar | A-2 | 40.0 |
c | Sara | A-3 | 7.0 |
Three objects, three separate sets of values. Each balance belongs to its own object, as Lesson 1 said.
Now look for count. Depending on the IDE, it appears in one of two ways: in a separate section of the variables view labelled "static" (or similar) when you expand the object or the class, or after you add a watch or evaluate expression for Account.count (a tool from the debugging module). It is not listed inside each object as a separate value, because there is only one count. Its value is 3. Evaluating Account.count and c.count gives the same number, because both mean the one shared variable. If your debugger shows count while expanding an object, the value is the same for all three, and that is the reason: it is not a per-object field.
A mental picture of what you see:
Class Account (one place): count = 3
Object a: owner=Ana number=A-1 balance=100.0
Object b: owner=Omar number=A-2 balance=40.0
Object c: owner=Sara number=A-3 balance=7.0Step 4: step through and watch the counter
Restart in debug mode, and this time put the breakpoint on the first new. Add a watch on Account.count. Use the "step over" command (from the debugging module) one line at a time. The watch shows 0 before the first new, 1 after, then 2, then 3. The constructor changes the shared variable each time. If your IDE lets you step into the constructor, you can also see this listed as the object being built, which makes Lesson 3's idea of "the current object" visible.
Step 5: the planned mistake
Stop the program. In Account, change double balance; to static double balance; and fix the constructor line to Account.balance = balance; so that it compiles. (Writing this.balance for a static field also compiles, but it hides the fact that the field is shared, so we use the class name.) Run the Lesson 6 Main again. The output is:
Ana (A-1): 70.0
Omar (A-2): 70.0
Second withdrawal ok? false
Accounts created: 2Both accounts show 70.0. Debug it as you did before. In the variables view, the objects no longer show a balance of their own. It has moved to the class-level data, next to count: one balance for the whole class. Ana's 100 went in first; Omar's 0 overwrote it; then Ana's 50 deposit and Omar's 20 deposit changed the same variable, so it ended at 70 for everyone.
Remove static from balance and restore this.balance = balance;. The output goes back to 150.0 and 20.0, and the debugger shows a balance per object again.
Step 6: summarize
- One per object means instance: it appears under each object in the debugger.
- Shared across all means static: it appears once, at class level.
- If values that should be independent change together, a field is probably static by mistake. Check the field's declaration first.
Examples
Example 1: the counter in the debugger (simple)
Breakpoint on the println, three accounts created. The variables view shows a, b, and c each with their fields, and an evaluation of Account.count returns 3. Evaluate a.count: also 3. Evaluate a.balance, b.balance, c.balance: 100.0, 40.0, 7.0. The picture is the diagram above. You have verified in two ways that the counter is shared and the balance is per object.
Example 2: debugging an unexpected shared value (practical)
A teammate reports: "Every product shows the same price after I update one." Their Product class contains:
static double price;
String name;Reading this in a debugger, you expand two products and cannot find price inside either object. That is the clue: the field is stored at class level. Add Product.price as a watch, update one product, and see the value change for all. The fix is to remove static. Notice how the debugger tells you the answer without reading the whole program: where a value lives is visible, and that is why this skill matters.
Example 3: choosing correctly for a new member (realistic)
You are asked to add three things to Account. Decide for each, and note what you would expect to see in the debugger:
static final double OVERDRAFT_LIMIT = 0;The same for all accounts, never changes. Static andfinal. In the debugger it appears at class level with one value.String branch;Different accounts can belong to different branches. Instance. It appears inside every object, possibly with different values.static int nextNumber = 1000;used to assign account numbers. It must be shared so each new account gets a different number. Static, and mutable: only acceptable in a simple, single-threaded program like this one. In the debugger, one value at class level that increases by one on everynew.
The rule from Lesson 5 settles each case: depends on one object's state, instance; the same for the whole class, static.
Backend Insight
A debugger is how backend developers look at live state: which objects exist, what they hold, and which values are shared. Shared mutable state, such as a static field, is especially important to spot, because on a server, many requests execute at the same time and all of them see that one field. A bug caused by it may appear only under load and never in a simple test, and it is difficult to reproduce. The habit you practised today, asking "where does this value live, and who else can see it?", is a core skill for reviewing and debugging services.
It also explains a design preference you will see in professional code: instance fields for state, static only for constants and stateless helpers. Constants cannot change, and helpers that use only their parameters share nothing. That keeps the amount of shared mutable state as small as possible.
Common Mistakes
Mistake 1: Making a field static to silence a compiler error
The error says the code is trying to reach an object's data without an object. Adding static makes the error disappear and the objects share data. Read the error as "which object?", and supply one.
Mistake 2: Looking for static data inside an object
In the debugger, a static field is not stored in each object. Look at class-level data or evaluate ClassName.field.
Mistake 3: Testing with only one object
With a single account, static double balance looks correct. You see the problem only with two or more objects. Always test with at least two.
Mistake 4: Debugging in normal run mode
Breakpoints are ignored when you run without the debugger. If the program never pauses, check you started it in debug mode.
Mistake 5: Reading the value one line too early
A breakpoint pauses before the line executes. If Account.count is one lower than you expect, step over the line and look again.
Exercises
Try each exercise yourself before opening the solutions. Where a debugger helps, use it; the questions can also be answered by reasoning.
Level A: Foundation
A1. After new Account("A", "1", 0); has run twice, what does the debugger show for Account.count, and where does it appear: inside each object or at class level?
Show solution A1
A1. It shows 2, at class level (in the static section, or when you evaluate Account.count), not inside the objects.
A2. Name one field of Account that appears separately in every object in the debugger.
Show solution A2
A2. owner, number, or balance. Each object has its own value.
A3. The debugger pauses at a breakpoint on a line. Has that line already executed?
Show solution A3
A3. No. The program pauses before the line runs.
Level B: Application
B1. Predict the output of this program, and then check by running it:
Account x = new Account("Ana", "A-1", 10);
Account y = new Account("Omar", "A-2", 20);
x.deposit(5);
System.out.println(x.balance + " " + y.balance + " " + Account.count);Show solution B1
B1. It prints 15.0 20.0 2. Only x was deposited into; the counter is 2 because two accounts were created.
B2. Draw (on paper or in text) the objects and class-level data after the code in B1, in the style of the diagram in this lesson.
Show solution B2
B2.
Class Account: count = 2
Object x: owner=Ana number=A-1 balance=15.0
Object y: owner=Omar number=A-2 balance=20.0B3. A product class has static String name;. Two products are created: "Tea", then "Mug". What will tea.name print afterwards, and what would you see in the debugger?
Show solution B3
B3. It prints Mug (for both products), because name is one shared variable and the second creation overwrote it. In the debugger, neither product object contains a name; it appears once at class level. The fix is to remove static.
Level C: Challenge
C1. Add static int nextNumber = 1000; and an instance field int serial to Account, so that each new account gets the next number as its serial. Create three accounts and print their serials.
Show solution C1
C1.
static int nextNumber = 1000;
int serial;
Account(String owner, String number, double balance) {
this.serial = nextNumber;
nextNumber++;
// other assignments and count++ as before
}Three accounts get serials 1000, 1001, and 1002. nextNumber is static because it must carry on from one object to the next; serial is an instance field because each account has its own.
C2. Explain why nextNumber would be risky in a server handling many requests at once, even though your program works. Give one safer idea in words (you do not have to implement it).
Show solution C2
C2. On a server many requests may create accounts at the same moment, and they all read and update the same static variable. Two requests could read the same value before either increments it, and receive the same "unique" number. A safer idea: let the database generate the identifier, because it is built to hand out unique values to many clients at once. (Java also offers tools for safe shared counters, which a later module covers; we have not verified a specific recommendation here.)
Mini Challenge
Use your own lab project:
- Put a breakpoint after creating three objects and take a screenshot or write down what the variables view shows: the instance fields per object and the static counter.
- Make one field
staticby mistake, run, and write down the symptom in the output and in the debugger. - Fix it, and write two sentences explaining why the symptom went away.
Check your work: your notes distinguish values stored in each object from values stored once for the class, and the second sentence mentions that the field was shared.
Summary & Next Lesson
Key takeaways
- In the debugger, instance fields appear inside each object, with their own values. A static field appears once, at class level.
Account.countreturns the same value no matter which object you use to reach it, because there is only one copy.- If values that should be independent change together, look for a field that was made
staticby mistake. - A breakpoint stops before the line runs, and only in debug mode.
- Keep static for constants and stateless helpers; keep state in instance fields.
Next lesson
You have finished the lessons of this module, apart from the lab. Do the exercises and the lab, BEG-020-LAB1 (Bank Accounts v1), which combines all of it: fields, constructors, methods, this, objects, and a static counter. The next module in the course, "Encapsulation", builds on this by hiding an object's data and protecting its rules.
Self-check
- Where does a static field appear in the debugger?
- Which fields appear separately in each object?
- What symptom suggests a field is accidentally static?
- Does a breakpoint pause before or after its line runs?
- Which should be static: a shared constant or a per-object balance?
Show answers
- Once, at class level (or when you evaluate it through the class name), not inside each object.
- The instance fields:
owner,number,balance. - Objects that should be independent show the same value, and changing one changes all.
- Before.
- The shared constant.
References
- Oracle, The Java Tutorials, Understanding Class Members. Supports: a class variable has one copy shared by all instances, and a constant is declared with
static final. - Output shown in this lesson (
3and3, and the70.0shared-balance output) was produced by compiling and running the code on JDK 21 (javac 21.0.10). The debugger descriptions are generic because panel names and layouts differ between IDEs, and we did not run an IDE debugger to produce them; check your own IDE's documentation for exact labels. - The advice about databases generating unique identifiers and about thread safety is general engineering knowledge, not verified against a specific source here. The name of the next module ("Encapsulation") comes from the course's own module specs.
Practice
Apply this lesson with the module's lab, exercises and quiz.
Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.