Python Tutor is a free tool that has been used by tens of millions of people since 2010 to visualize and debug code step-by-step. Despite its name, it also visualizes Java code (in addition to C, C++, and JavaScript) to help students understand critical concepts and debug homework assignments.
These Java visualizations are well-aligned with the curricula of university-level introductory programming courses (e.g., CS1 and CS2), high school AP Computer Science A, and object-oriented programming courses taught in Java. (If you teach AP CS A, also see my newer companion article on how this visualizer covers the AP CS A course framework.)
This article shows instructors how Python Tutor can automatically illustrate key concepts from a wide range of Java-based courses. If you think this tool may be helpful for your staff or students, please share this direct link in relevant course materials, chat groups, mailing lists, discussion forums, or social media:
(Also, if you teach in C or C++, check out what the C/C++ visualizer can do as well.)
Credits: The original Java visualizer was created in 2013 by David Pritchard and Will Gwozdz. It has since been enhanced in various ways over the years. Most notably, in 2023 the server infrastructure was upgraded so it now runs faster and more reliably. And in 2026, it gained support for visualizing Java collections (e.g., ArrayList, HashMap), 2-D array grids, wrapped primitive objects, and keyboard input via Scanner – all shown below.
The Java visualizer can illustrate most object-oriented programming concepts that are taught in introductory and intermediate-level classes (no pun intended), including:
super() callsthis referenceThe tool generates visual representations of classes, objects, methods, and fields (a.k.a. attributes), helping students to grasp the relationships and interactions among these elements. Students can observe the instantiation of objects, the calling of methods, and the changing states of object fields, thus making these abstract concepts more concrete.
Here's an example showing inheritance, which I derived (no pun intended) from code in the CSAwesome e-textbook:
This program instantiates three instances of the Student class,
assigns them to local variables within main(), and adds them to the
arrayOfPeople array. The array contains references (arrows) pointing
to each instance.
Step through execution using the slider below the code to see each
Student constructor being called, which in turn uses super() to call
the Person superclass's constructor. Note how the static field
Student.nextId increments after each constructor call.
(A practical note: Python Tutor doesn't support creating multiple .java files, so to visualize a program with multiple classes, put all of the classes in the code editor like in this example, and make only one of them public.)
Here's an example of polymorphism using dogs and cats as subclasses of
Animal:
In the current step (Step 24), the stack contains main(), which calls
System.out.println, which in turn calls the toString() method of the
Cat instance, which calls the getName() method of its superclass,
which returns "Whiskers". Note how the this reference for all the
methods refers to the Cat instance.
Step back and forth using the slider to see polymorphism in action
– either the Dog or Cat toString() method is called based on
the run-time type of each Animal instance in the for loop.
It can be hard for beginners to understand how parameters of various types are passed to Java method calls. Step-by-step visualizations can help clarify:
In this example, here at Step 9 the swapCarYears() method is being
called with the toyota and ford objects as parameters. The
visualization shows how references to these objects are passed into
the method as c1 and c2, respectively. The objects themselves are
not copied (unlike in, say, C++) ... the references to them
are.
In contrast, step forward to Step 14 when updateNumCars() gets called.
Here the current value of numCars is passed into the method as
myNumCars (effectively running myNumCars = numCars). So incrementing
myNumCars++ within that method does not alter the original
numCars.
You may have noticed from the prior example that string values are
displayed inline within the two Car instances. Here are those same
object instances again, with an added desc local variable that's also
a string:
This inline visualization matches how we intuitively think about
strings, but strings in Java are actually objects just like Car. So
that means it's technically more accurate to draw them standalone with
references (arrows) pointing to them, like this:
Note how each String object is now standalone. Users can activate this
display mode by checking "show Strings and wrappers (e.g., Integer) as
objects" below the code editor:

However, it's not on by default since visualizations tend to look too cluttered when this mode is on due to too many objects and arrows appearing on-screen.
Relatedly, wrapped
objects
(a.k.a. boxed primitives, e.g., instances of Boolean, Integer, or
Float) display as visually distinct boxes to distinguish them from
primitives such as boolean, int, and float, which appear as plain
values within the stack frame:
And when the "show as objects" display mode from above is on, those wrapped objects render standalone on the heap with references pointing to them, just like strings do:
We've already seen one-dimensional arrays in some of the above examples.
Here's a multi-dimensional array, which displays as a compact grid
table (see a2 below):
This grid display is on by default via the "show array-of-arrays as 2D
array" option below the code editor. Java actually represents a
multi-dimensional array as an array of arrays, so if you uncheck that
option, a2 renders as an outer array with arrows pointing to separate
inner arrays, which is technically more accurate but harder to read at
a glance. Ragged arrays (where rows have different lengths) display as
grids too, with shorter rows padded by gray cells.
The visualizer renders eight Java Collections
classes
from java.util as clean, readable data structures: ArrayList,
LinkedList, HashMap, TreeMap, LinkedHashMap, HashSet,
TreeSet, and LinkedHashSet. (It used to display something like
"ArrayList object" without showing any of its internal contents.)
Here's an ArrayList of Integer objects:
Step through execution to see how add(1, 99) inserts an element into
the middle of the list by shifting later elements to the right, and how
remove(0) shifts them back to the left. Also note how each Integer
element appears as a wrapped (box) object rather than simply 10, 15, or
30, to distinguish it from a primitive int like the local variable
first.
And here's a HashMap, which displays its key-value pairs:
Step to the end to see how ages.put("Alice", 26) updates the value
for an existing key instead of adding a new pair.
Java programs that read text input using java.util.Scanner now work.
On the live site, whenever a Scanner read (e.g., nextInt() or
nextLine()) needs input, the visualizer prompts the user to type a
value. Then a "User input processed so far" display above the program
output shows which portion of the input has been consumed at each
execution step: consumed text appears in gray with strikethrough, and
remaining text is in black. Inputs are saved so users can edit and
re-use them without retyping, and they're preserved in shareable links.
(Note that taking Scanner input from a File doesn't work yet, but
it's coming soon – file input is a natural extension of this
standard-input support, and it's currently under active development
and testing.)
These step-by-step visualizations can demystify Scanner behaviors that
often confuse students. Here is the classic nextInt()/nextLine()
bug:
Here after the user enters "25", they never get a chance to enter their
name. Why? Step through to see how sc.nextInt() consumes only the
digits "25" from the input, so the following sc.nextLine() call
consumes the leftover newline character and immediately returns an
empty string for name instead of pausing to ask for more input. The
"User input processed so far" display shows exactly what the program
has consumed from stdin at every step.
Scanner's hasNext family of lookahead methods works too, which is
how many courses teach reading a sequence of values when you don't
know in advance how many will arrive. This loop keeps reading ints
until the next token isn't one, then averages what it read:
At the current step (Step 11), the loop is in its second iteration:
sc.nextInt() just consumed "20", so n is 20 and sum is about to
grow from 10 to 30. Meanwhile "done" sits unconsumed at the end of the
input display, and it stays that way to the very end of execution:
hasNextInt() peeks at the next token to decide whether the loop
should keep going, but never consumes it. When the loop finally
reaches that non-numeric token, hasNextInt() returns false and
execution moves on to compute the average. Sentinel-terminated input,
type-checking lookahead, and Scanner's token-by-token consumption
model are all visible in this one small example. (nextDouble(),
next(), and mixed-type reads all work as well.)
Stack frame visualizations are also useful for demonstrating recursion. Here is the classic factorial example:
You can also visualize more complex recursion, such as these examples in the CSAwesome e-textbook. Here is their binary search example:
Visualizations can also help students understand unusual control flow
during exception handling. This is especially useful when exceptions
occur across different method calls and when there are try, catch,
and finally blocks. Here's a small example:
The Java visualizer in Python Tutor can help your students understand and debug a variety of code that they encounter in introductory or intermediate-level courses.
Feel free to share this direct link in relevant course materials, chat groups, mailing lists, discussion forums, social media, or anywhere else: