Seven conceptual hurdles you might face when learning a new programming language
Confession: my personal experience is almost the complete opposite of the title of this article. I actually started with C++ in college, moved to Java to teach AP Computer Science A, and then entered Python territory to work with all of the snazzy data science libraries available. Now that I’m working on a Java project again (a neural network package that is really pushing my understanding of the underlying math to its limits), I’m really paying attention to the little differences in how the languages work. In particular, I’ve been making notes about the places where standard Python habits can become huge roadblocks when traveling through Java.

This post is directed at Python programmers (in particular, data scientists) traveling in the opposite lane; I’ll detail some of the initial conceptual hurdles you may face when learning to adapt your Python instincts to Java territory. In particular, I’m going to be avoiding superficial differences like snake_case
vs camelCase
or how Java requires you to end a statement with a semicolon while Python makes it optional. I’m also going to avoid diving too deeply into OOP vs. functional programming. The focus of this post is the way in which Java requires you to think differently about how to solve whatever problem you’re working on.
Although it may seem daunting, just remember that many programmers before you have successfully learned Java, so it absolutely can be done.
1. Java has more overhead than you’re used to.
In Python, you can write an entire “Hello World” program with console output in a single line of code:
print('Oh hi there, globe')
To accomplish this in Java, you need to create an entire class, complete with a main
method to serve as the entry point.
public class Room {
public static void main(String[] args) {
System.out.println("Oh hi there, globe");
}
}
At its heart, every Java program is essentially some class’s main
method working with variables and telling other classes what to do (and sometimes, that main
method is hidden deep within automated files, like with Android projects). Although there are some ways to run Java outside of a complete class––I was a big fan of the interactions
tab in jgrasp while teaching––there are some hoops to jump through to make this happen.
In short, while Python allows you to feed a few commands directly into the interpreter to test out an algorithm, working with Java requires you to place that code in context.
And then there’s the elephant in the Room
above: System.out.println()
. This seems like way more work than it should be just to produce console output, right? That’s because we’re being very, very particular about how we want our output to be displayed; out of all of the different places we could put that text, we want it to go to the console, as opposed to a log file, a pop-up window, or a server off in the ether. Python, by default assumes that by print
you want the text displayed on the console, but Java does not generally make assumptions like that.
This leads to our next point…
2. Java requires you to be way more specific than Python.
Python lets you get away with a lot. Think about how many times you’ve written code like:
that_value_i_need = some_object.mystery_method()
only to have to follow it up with:
type(that_value_i_need)
in order to figure out what, exactly, mystery_method()
is returning.
This isn’t necessarily the best way to go about things; obviously, the docs should explain what you’re going to get from mystery_method()
, so it’s definitely possible that you already know what you’re getting before you call it. But Python lets you get away with this. You can create a variable for that_value_i_need
and know that it can handle whatever mystery_method()
spits out.
Furthermore, you can be pretty confident that you can pull off a high-wire act like this, without even having to think about the data types involved:
important_value = some_method(a, bunch, of, arguments)
important_value = unrelated_class.another_method()
After all, important_value
is just a variable, which can hold any data type it needs to… right?
In Java, this is absolutely not the case. When declaring a variable, you need to specify its data type from the get-go, and then you’re locked in throughout its lifetime.
While this might sound daunting and restrictive at first, I find that reading somebody else’s code is so much easier in Java. I can confidently point to a variable and say, “x
is an int
, because it was declared as an int
on line 72. If it’s holding a value returned by someUsefulMethod
, that tells mesomeUsefulMethod
returns an int
(or something that can be promoted to an int
, like a short
).
Although this initially presents you with what feels like an impenetrable wall of reserved words floating around in everyone else’s code and error: cannot find symbol
messages when you try to run yours, the fact that Java requires you to be so specific leads to code being significantly more self-documenting, predictable, and unambiguous than Python. And although I need to be way more meticulous when writing my Java code, I feel significantly more confident in understanding how it works.

Speaking of documentation, javadoc
is a powerful tool for generating clean documentation––even if you haven’t explicitly written comments (which you should). javadoc
will read your code and generate organized html files indicating exactly what data types you need to pass a method and what value you can get in return. Most IDEs even have a button that will do this for you.
And in order to keep things as unambiguous as possible…
3. Java is more strict than Python.
Let’s revisit our Hello World program in Python. How many different ways could we write it?
# version 1 - single quotes
print('Oh hi there, globe')# version 2 - double quotes
print("Oh hi there, globe")# version 3 - triple single quotes
print('''Oh hi there, globe''')# version 4 - uh, triple double quotes, because why not?
print("""Oh hi there, globe""")
Python gives you a lot of options for accomplishing the same task––in the above example, we can enclose our string with four distinct syntaxes. This legitimately threw me for a loop when I was learning Python; how could I know which one to use, and when? Is there an elegant rule of thumb to remember?
If you’re a Python programmer with even a tiny amount of experience, you already know what’s coming: “Most people just go with single quotes. Unless there’s an apostrophe in the string, in which case you can enclose it with double quotes. Unless there’s both an apostrophe and a double quote in the string, in which case you can enclose it in triple single quotes. And I guess if you have single, double, triple single and triple double quotes inside your string, then you can always just go back to single quotes and use an escape character wherever you need it.”
Because Java is so specific and so strict, learning to work with String
s is significantly more straightforward: Always enclose them in double quotes. If you have a double quote inside your String
, put an escape character before it:
”
. If you have a inside your
String
, put an escape character before it: \
. If you ever see single quotes, like with 'a'
, that means you’re dealing with a char
, not a String
.
And if you’ve gotten used to being able to throw every value you can think of––from an int
to a KitchenSink
––into a list in Python, you’ll find that working with Java arrays require a very different mindset. All elements in a Java array have to have the same data type, similar to an ndarray
or a Series
, but with careful design, you can get around this limitation with polymorphic references.

Although this specificity can be annoying, it makes your code significantly more predictable, which is my gold standard for readability. Blocks are grouped together with curly braces rather than indents, so you can easily tell where they start and end. While this isn’t a huge issue on your computer, I’ve found that grouping by indent in a printed Python book (where the code is split over multiple pages) can create problems for people new to that language.
Additionally, this strictness means variable scope is significantly easier to wrap your head around in Java than it is in Python, and if you respect the curly braces, it often boils down to, “Defined in the curly braces? Only exists in the curly braces.” You actually have to put in work to declare global
variables available to all class methods in Java, whereas in Python you might accidentally wind up using a syntactically valid (yet perfectly inappropriate) value from a completely different part of your program if you ever recycle a variable name. On top of that, you can actually have private
fields in your class, instead of hoping everyone respects your underscored variable name.
And if you ever make a mistake? Well, rather than letting your code run for 90% of its runtime then aborting when it encounters its first error…
4. Java is compiled, so many errors resulting from misunderstanding the previous points are caught before runtime.
Obviously, Python will parse your code before running it and generate a SyntaxError
if it can’t figure out what you’re going for, but I’m sure all of you (especially data scientists) have been annoyed at one point or another when your code screeches to a halt over a simple error, after accomplishing half of what you wanted it to do.
Oftentimes (in my experience, anyway), this happens because you’ve attempted to perform some sort of an operation — say, calling a method — on an object of the wrong type. It’s not a syntax error (you remembered the .
!), so the parser doesn’t catch it, and the traceback takes you down a rabbit hole into the core libraries, so you can see exactly what Python assumed you wanted to do with the mystery object you gave it.
In Java, this step happens before you run your code. Because you’ve locked your variable into a specific data type when you declared it, the Java compiler can verify that every operation you attempt to perform on that object actually exists before runtime, and if the compiler detects an operation that isn’t defined for that data type, your program will not compile and run at all.
That’s not to say that you’ll never have your Java program terminate unexpectedly — there is a dynamic aspect to the language, so you’ll become very good friends withNullPointerException
and ArrayIndexOutOfBoundsException
––but this added layer of verification, combined with the strict and specific nature of Java, tends to funnel these runtime errors down predictable alleys.
5. Designing Java methods is almost nothing like designing Python functions, so you need to think about your problem differently.
A downside to this strictness is that Java method calls can become unruly pretty quickly. I absolutely love the functional side of Python, and have started relying not only on unordered keyword arguments, but also passing functions and having default values for optional parameters.
This is totally valid Python code…
def multiply(a, b, c=1):
return a * b * cmultiply('hello', 3)
…which would not fly in Java. Because Java is static-typed, you lock in the data type when you declare the variable. This means you have to pass values that have exactly that data type in exactly the same order as they appear in the method signature. While this reduces flexibility, it also reduces misuse of your code.
While you can have methods with the same name handle different lists of parameters, Java handles this via overloading, which requires you to write a different version of the method to handle each sequence of data types you want to support as a parameter list. Java checks the data types you passed, sees if there is a method defined for handling that sequence of data types, and calls that method––methods with the same name and different parameters aren’t really linked in any other way.
And if you want to provide a default value for one of your parameters? You have to overload that method and drop the default-value parameter from the list.
The fact that signatures are defined by the data types in the parameter list instead of the name of the variable also creates issues when you’re used to thinking in Python. For example, if we wanted to provide default arguments for overloaded versions of this method:
public static double getArea(double length, double width){
return length * width;
}
We would run into an issue by including two versions with the same data types in their parameter list:
public static double getArea(double length) {
return length * 10;
}public static double getArea(double width) {
return width * 5;
}
Both of these methods are called getArea
and both expect a double
, so when you call getArea(12.3);
, Java does not know which path to go down.
In addition to that, while Java does have some support for lambda expressions, they’re a far cry from functional programming, and attempting to think through a functional solution in an object-oriented language is begging for disaster.
Oh, and return types? Java limits you to one––and you have to specify the data type when you write the method, which requires you to know what you want to return well in advance. You may be used to thinking about Python as being able to return multiple values…
def get_stuff():
return 1, 2, 3x, y, z = get_stuff()
…but it actually returns a single object––all of the values bundled into a tuple:
>>> type(get_stuff())
<class 'tuple'>
You can be surprisingly productive in Python without ever knowing this fact, but in Java you always have to know what your methods are returning.
6. In addition to data types, Java requires you think about lower-level concepts like memory management.
This one might be a bit of a hot take (especially for people coming from a language like C that don’t have automatic garbage collection), but there are certain pieces of Java that require you to think about what’s going on, memory-wise. I’m talking about reference variables!
For example, what happens when you try to run the following code in Python?
my_list = [1, 2, 3]
print(my_list)
If you’re coming from a totally-Python background, you’ll expect the following output:
[1, 2, 3]
On the flip side, what do you get from the following Java code?
public class JavaFeatures {
public static void main(String[] args) {
int[] myList = {1, 2, 3};
System.out.println(myList);
}
}
That’s right! It will print the memory address. Here’s where my computer stored that:[I@6d06d69c
Again, Python makes the assumption that when you print a variable referencing a list, you want to see the list’s contents, while Java, by default, prints exactly what is stored in that reference variable: the location of the array in memory. To display the contents, you would need to loop through them:
for (int i = 0; i < myList.length; i++) {
System.out.println(myList[i]);
}
While this takes a little bit of effort to wrap your brain around, it forces you to think about what’s actually being passed when you throw around reference variables (the memory address of that object), which gives you a deeper understanding of how your computer is actually carrying out its tasks.
While Java is not as low-level as other languages, it’s much easier to make the leap from Java reference variables to C pointers than it is to leap from Python references, because you’re used to encountering problems like the one above that force you to consider the memory address.
But the lack of Python’s “set it and forget it” mentality isn’t limited to reference variables.
7. Java doesn’t really have a great equivalent for pip
or conda
.
When I think about what makes Python a useful language, I don’t generally think about language features or syntax. I think about the Python ecosystem. With a quick trip to the command line, you can use something like
pip install snazzy_new_library
to download a snazzy_new_library
for use whenever you need it.
Although you will find people on message who will insist that package tools like maven
or gradle
are just as powerful as pip
or conda
, the reality is that while these tools are, indeed, powerful, they aren’t really equivalent. Java programs are generally structured differently––rather than making a package globally available to any program in that environment, packages tend to be bundled directly with the application that needs them, and often need to be either added manually by dropping a jar file into the right place, or included with a tool that manages your dependences (which is what tools likemaven
and gradle
are actually great at).
This can be inconvenient for Python programmers, because it requires you to jump through additional hoops to include a useful existing library on your project, and the size of the project files can get bloated as a result. On the other hand, bundling the included library with the project ensures that the necessary code is actually available when it’s needed.
Conclusion: You can learn Java, and it will make you a better programmer.
Having to adapt your problem-solving skills to fit the quirks of a new programming language can be a scary task, but picking up a new language really does expand the arsenal of tools in your tool kit––even in your first language. C pointers confused me until I got used to working with Java reference variables, and lambda expressions in Java baffled me until I saw how they were used in functional programming with Python… which in turn wraps back around to clarifying why we need function pointers in C.
Although it may seem daunting, just remember that many programmers before you have successfully learned Java, so it absolutely can be done.