Preamble

This month starts the Java side of the blog with tooling parity, not syntax trivia. If you already know venv, pip, timeit, and small reproducible Python scripts, Java’s equivalents are jshell, javac/java, Maven/Gradle, and reading stack traces without IDE training wheels—at least on day one.

The sections below add notebook-shaped iteration, environment mental models, and JDK version managers so the Python-to-Java map stays concrete.


jshell and single-file experiments

jshell is the REPL for poking at APIs: try List.of, Stream, Optional without a main method ceremony. For slightly larger spikes, src/Main.java + javac + java mirrors python3 script.py before Spring or IDEs enter the picture.


Iterating like a notebook: what exists in Java?

In Python, Jupyter gives you ordered cells, rich output, and saved narrative. Java has no single blessed clone, but you can stack tools by how “notebook-like” you need the loop to be.

1. jshell — closest built-in to a REPL session

Install nothing extra beyond a JDK. Start a session:

jshell

Inside jshell, run snippets line by line (no class / main). Useful commands:

  • /help — list slash-commands.
  • /edit — open a buffer (or configure an external editor) for a longer snippet.
  • /save scratch.jsh — persist what you typed; /open scratch.jsh reloads it.

Example: after jshell starts, paste:

import java.util.*;
import java.util.stream.Collectors;
var xs = List.of(1, 2, 3);
xs.stream().map(x -> x * 2).collect(Collectors.toList())

On Java 16+, xs.stream().map(x -> x * 2).toList() is a shorter alternative.

You get a $1-style result value you can reuse, similar to notebook outputs.

2. Single-file source programs (Java 11+) — “one cell, one file”

From Java 11, you can run a single .java file that declares main without a separate javac step (as long as it stays a single compilation unit—no sibling .java in the same trick for this mode).

Create Hello.java:

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, " + (args.length > 0 ? args[0] : "world"));
    }
}

Run:

java Hello.java Alice

Edit, save, java Hello.java again—this is the tightest file-based loop if you avoid a build tool.

3. JBang — scripts with dependencies (pip-like ergonomics)

JBang lets you write .java (or .jsh) files with //DEPS headers, then run them like python script.py. Install JBang (see jbang.dev); with SDKMAN, for example:

sdk install jbang

Create plotish.java (filename matches the public class name):

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS com.google.code.gson:gson:2.10.1

import com.google.gson.Gson;
import java.util.Map;

public class plotish {
    public static void main(String[] args) {
        var g = new Gson();
        System.out.println(g.toJson(Map.of("ok", true)));
    }
}

Run:

chmod +x plotish.java   # optional, for shebang-style
./plotish.java
# or: jbang plotish.java

First run may resolve artifacts (like pip install on demand); later runs are cached.

4. **Real Jupyter notebooks — IJava and friends

If you need the actual Jupyter UI, use a Java kernel:

  • IJava — common choice; install the kernel, pick Java in JupyterLab/Notebook.
  • Your JDK must match what the kernel expects; kernels often document a minimum version.

Typical flow (high level—follow the project’s README for your OS):

# after installing a JDK and pip-installing jupyter
python3 -m pip install jupyter
# then install IJava per its instructions (download release, run python3 install.py --sys-prefix)
jupyter notebook

You trade “batteries included” for notebook fidelity; for library exploration, jshell or JBang is often faster to wire up.

5. IDE scratch files (IntelliJ, VS Code)

IntelliJ scratch files and lightweight main in a temp buffer give refactor and navigation Jupyter does not. That is not CLI-first, but it is how many teams spike Java.


“Environments” in Python vs Java

Rough map:

Python idea Java analogue
pyenv Pick which JDK is on PATH (SDKMAN, jenv, asdf, manual install).
venv Per-project dependencies live in Maven/Gradle metadata + local cache (~/.m2, Gradle caches)—not a single directory you source activate.
pip install -r mvn dependency:resolve or gradle dependencies; versions are pinned in pom.xml / build.gradle.

You do not usually create a “Java venv” folder. Instead:

  1. JDK version — global or per-shell (see below), sometimes per-project via Gradle toolchains / Maven toolchains so CI and IDE agree.
  2. Librariesper repository (or per module), resolved by the build tool.

So “environment management” splits into JDK selection and dependency resolution; Python often bundles both feelings into venv + pyenv.


Managing JDK versions: SDKMAN, jenv, asdf

SDKMAN (very common on macOS/Linux)

Install from sdkman.io; then:

sdk list java
sdk install java 21.0.5-tem      # example: Temurin 21
sdk use java 21.0.5-tem          # current shell only
sdk default java 21.0.5-tem      # new shells

In a project directory you can commit .sdkmanrc ( sdk env init ) so teammates run sdk env and align versions.

jenv ( pyenv-shaped for Java)

jenv switches JAVA_HOME / shims like rbenv:

brew install jenv   # on macOS with Homebrew
jenv add /Library/Java/JavaVirtualMachines/temurin-21.jdk/Contents/Home
jenv global 21
# or per directory:
cd ~/work/my-app && jenv local 21

A .java-version file appears—familiar if you know .python-version.

asdf with the Java plugin

asdf is a multi-language version manager; one tool can manage Node, Python, Java, etc.

asdf plugin add java
asdf list all java | tail   # see available versions
asdf install java temurin-21.0.5+11
asdf global java temurin-21.0.5+11
java -version

Per project, asdf local java temurin-17.0.9+9 writes .tool-versions.


Is a pyenv-style manager as necessary for Java?

Often less mandatory than pyenv is for Python, for a few reasons:

  • Many teams standardize on one LTS (17, 21) per service; Docker and CI enforce it, so your laptop matches one JDK most days.
  • Gradle (and Maven with toolchains) can download and pin a JDK for compilation even if your shell uses another—reducing “wrong Java” mistakes for builds.
  • You still want SDKMAN / jenv / asdf when you maintain libraries across versions, migrate legacy apps, or compare behavior between LTS releases.

So: not always as central as pyenv, but very useful the moment you juggle multiple JDKs.


Build tools when libraries appear

Maven or Gradle arrive the moment you need HTTP clients, logging, or test frameworks. Map pom.xml to requirements.txt plus a resolver: coordinates are pins; plugins are dev tools. jdeps surfaces package coupling—similar to catching accidental imports across Python layers.

Quick “what did I pull in?” checks:

mvn dependency:tree
./gradlew dependencies --configuration runtimeClasspath

Mental model cheat sheet

ClasspathPYTHONPATH (imperfect but useful). Modulespackages. java -jar ≈ running an installed console script. Checked exceptions change flow compared to Python—read cause chains the same way you unwrap __cause__.


Conclusion

Map concepts, not keywords. For fast iteration, chain jshell, java File.java, and JBang before you assume you need a full Spring app; for JDKs, pick one version manager and let Maven/Gradle own libraries. 2020 dives into collections, Maven basics, and graph algorithms—the bilingual arc expands from here.