Introduction
As developers, we often find ourselves juggling multiple projects, each requiring different versions of Python or Java. Maybe you’re maintaining a legacy application that runs on Python 3.8 while building a new microservice on Python 3.12. Or perhaps you’re working with Java 11 for one client and Java 21 for another. Manually managing these versions can quickly become a nightmare of PATH variables, symlinks, and “it works on my machine” debugging sessions.
This is where version managers come to the rescue. In this guide, we’ll explore two powerful tools that will transform how you handle language versions: pyenv for Python and SDKMAN for Java.
Why Version Managers Matter
Before diving into the tools, let’s understand why version managers are essential:
Isolation: Each project can use its specific language version without conflicts.
Easy switching: Change versions with a single command instead of modifying system configurations.
Reproducibility: Your team can easily replicate the exact development environment.
Safety: Experiment with new versions without risking your system’s stability.
Convenience: Install and manage multiple versions from a single interface.
Part 1: Managing Python Versions with pyenv
pyenv is a simple, powerful tool that lets you easily switch between multiple versions of Python. Unlike system-level installations, pyenv builds Python versions in your home directory, giving you complete control without requiring root access.
Installing pyenv
The installation process varies by operating system, but the most reliable method is using the official installer script.
On macOS and Linux:
curl https://pyenv.run | bash
After installation, add pyenv to your shell configuration. For bash, add these lines to your ~/.bashrc:
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
For zsh users, add the same lines to ~/.zshrc. If you’re using fish shell, add to ~/.config/fish/config.fish:
set -Ux PYENV_ROOT $HOME/.pyenv
set -U fish_user_paths $PYENV_ROOT/bin $fish_user_paths
pyenv init - | source
Restart your shell or run source ~/.bashrc (or your shell’s config file) to apply the changes.
Essential pyenv Commands
List available Python versions:
pyenv install --list
This shows all Python versions available for installation, including CPython, PyPy, and other implementations.
Install a specific Python version:
pyenv install 3.12.0
pyenv install 3.11.5
pyenv install 3.8.18
pyenv will download, compile, and install the Python version in ~/.pyenv/versions/.
List installed versions:
pyenv versions
The asterisk indicates your currently active version.
Set global Python version:
pyenv global 3.12.0
This sets the default Python version for your entire system (user-level, not system-wide).
Set local Python version for a project:
cd ~/projects/my-legacy-app
pyenv local 3.8.18
This creates a .python-version file in your project directory. Whenever you enter this directory, pyenv automatically switches to that version.
Set Python version for current shell session:
pyenv shell 3.11.5
This temporarily overrides the global and local settings for your current terminal session.
Real-World pyenv Workflow
Here’s how you might use pyenv in practice:
# Install multiple Python versions
pyenv install 3.12.0
pyenv install 3.8.18
# Set Python 3.12 as your default
pyenv global 3.12.0
# Create a new project directory
mkdir ~/projects/legacy-client-app
cd ~/projects/legacy-client-app
# This project needs Python 3.8
pyenv local 3.8.18
# Verify you're using the correct version
python --version # Output: Python 3.8.18
# Create a virtual environment with this Python version
python -m venv venv
source venv/bin/activate
# Work on your project...
pip install -r requirements.txt
When you leave this directory and enter another project, pyenv automatically switches to that project’s Python version based on its .python-version file.
pyenv with Virtual Environments
pyenv works seamlessly with Python’s built-in venv module and third-party tools like virtualenv. A common pattern is:
- Use pyenv to set the Python version for your project
- Create a virtual environment using that Python version
- Install project dependencies in the virtual environment
This gives you both version isolation (via pyenv) and dependency isolation (via virtual environments).
Troubleshooting Common pyenv Issues
Build dependencies missing: If Python installation fails, you likely need development libraries. On Ubuntu/Debian:
sudo apt-get update
sudo apt-get install -y build-essential libssl-dev zlib1g-dev \
libbz2-dev libreadline-dev libsqlite3-dev curl \
libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev
On macOS, ensure Xcode Command Line Tools are installed:
xcode-select --install
Python version not switching: Make sure pyenv init is in your shell configuration and that you’ve restarted your shell.
Slow Python installation: pyenv compiles Python from source. On macOS, you can use pre-compiled versions with pyenv install --patch:
PYTHON_BUILD_CACHE_PATH=$HOME/.pyenv/cache pyenv install 3.12.0
Part 2: Managing Java Versions with SDKMAN
SDKMAN (Software Development Kit Manager) is a versatile tool for managing parallel versions of multiple Software Development Kits on Unix-based systems. While it supports many SDKs (Gradle, Maven, Kotlin, Scala), we’ll focus on its Java management capabilities.
Installing SDKMAN
Installation is straightforward and works on any Unix-based system:
curl -s "https://get.sdkman.io" | bash
After installation, open a new terminal or run:
source "$HOME/.sdkman/bin/sdkman-init.sh"
Verify the installation:
sdk version
SDKMAN automatically integrates with bash, zsh, and other shells by adding initialization code to your shell’s configuration file.
Essential SDKMAN Commands
List available Java versions:
sdk list java
This displays all available Java distributions, including Oracle JDK, OpenJDK, Amazon Corretto, Temurin (AdoptOpenJDK), GraalVM, and more. You’ll see vendor identifiers and version numbers.
Install a specific Java version:
sdk install java 21.0.1-tem # Temurin JDK 21
sdk install java 11.0.21-amzn # Amazon Corretto 11
sdk install java 17.0.9-graal # GraalVM 17
SDKMAN downloads and installs the JDK in ~/.sdkman/candidates/java/.
List installed Java versions:
sdk list java | grep installed
Or see currently installed versions with:
ls ~/.sdkman/candidates/java/
Set default Java version:
sdk default java 21.0.1-tem
This sets your system-wide default Java version.
Use a specific Java version in current shell:
sdk use java 11.0.21-amzn
This temporarily switches to Java 11 for your current terminal session without changing the default.
Check current Java version:
sdk current java
java -version
Project-Specific Java Versions with .sdkmanrc
SDKMAN supports project-specific SDK versions through a .sdkmanrc file. Create this file in your project root:
cd ~/projects/my-java-app
sdk env init
This creates a .sdkmanrc file. Edit it to specify your Java version:
java=17.0.9-tem
Now, whenever you enter this directory and run:
sdk env
SDKMAN automatically switches to Java 17 for that shell session.
For automatic switching, you can enable the SDKMAN hook in your shell configuration. Add to your ~/.bashrc or ~/.zshrc:
# This loads the sdkman init script and enables auto-env
export SDKMAN_DIR="$HOME/.sdkman"
[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"
# Auto-switch Java version when entering a directory with .sdkmanrc
sdk_auto_env() {
if [[ -f ".sdkmanrc" ]]; then
sdk env
fi
}
cd() {
builtin cd "$@" && sdk_auto_env
}
For fish shell, first install fisher, then
fisher install edc/bassAdd this to fish config file at ~/.config/fish/config.fish
# SDKMAN initialization
set -gx SDKMAN_DIR $HOME/.sdkman
function sdk
bass source "$HOME/.sdkman/bin/sdkman-init.sh" ';' sdk $argv
endReal-World SDKMAN Workflow
Here’s a practical example of using SDKMAN:
# Install multiple Java versions
sdk install java 21.0.1-tem
sdk install java 17.0.9-tem
sdk install java 11.0.21-amzn
# Set Java 21 as default
sdk default java 21.0.1-tem
# Create a project that needs Java 17
mkdir ~/projects/spring-legacy-service
cd ~/projects/spring-legacy-service
# Initialize SDKMAN config for this project
sdk env init
# Edit .sdkmanrc to specify Java 17
echo "java=17.0.9-tem" > .sdkmanrc
# Apply the configuration
sdk env
# Verify you're using Java 17
java -version # Output: openjdk version "17.0.9"
# Build your project with Maven or Gradle
./gradlew build
When you navigate to different projects, you can quickly switch Java versions:
cd ~/projects/new-microservice
sdk use java 21.0.1-tem # Use Java 21 for new features
cd ~/projects/legacy-client
sdk use java 11.0.21-amzn # Switch to Java 11 for compatibility
Managing Other JVM Tools with SDKMAN
SDKMAN isn’t just for Java. It can also manage related tools:
Install Gradle:
sdk install gradle 8.5
Install Maven:
sdk install maven 3.9.6
Install Kotlin:
sdk install kotlin 1.9.21
You can use the same commands (list, install, use, default) for these tools.
Uninstalling Versions
To remove versions you no longer need:
sdk uninstall java 11.0.21-amzn
This helps keep your system clean and saves disk space.
Best Practices and Tips
Commit version files to Git: Both .python-version and .sdkmanrc files should be committed to your repository. This ensures all team members use the same language versions.
Document version requirements: Add a README section explaining which tool versions are required and how to set them up using pyenv and SDKMAN.
Use version ranges carefully: Specify exact versions in production environments but consider more flexible versions for development.
Keep tools updated: Periodically update pyenv and SDKMAN themselves:
# Update pyenv
cd ~/.pyenv && git pull
# SDKMAN updates itself automatically, but you can force it
sdk update
Clean up old versions: Remove unused Python and Java versions to save disk space:
pyenv uninstall 3.7.12
sdk uninstall java 8.0.392-tem
Shell integration matters: Ensure both tools are properly initialized in your shell configuration for the best experience.
Conclusion
Version managers like pyenv and SDKMAN transform the chaos of managing multiple language versions into a streamlined, predictable process. Whether you’re maintaining legacy applications, experimenting with new language features, or simply working across multiple projects, these tools provide the flexibility and control you need.
With pyenv, you can effortlessly switch between Python versions and ensure every project runs on its intended interpreter. With SDKMAN, Java version management becomes trivial, letting you work with different JDKs, distributions, and even other JVM languages without headaches.
The initial setup takes just a few minutes, but the time saved and frustration avoided over the course of your development career is immeasurable. Install these tools today, and never worry about version conflicts again.
Quick Reference Card
pyenv:
- Install version:
pyenv install 3.12.0 - List versions:
pyenv versions - Set global:
pyenv global 3.12.0 - Set local:
pyenv local 3.11.5 - Set for shell:
pyenv shell 3.10.0
SDKMAN:
- Install version:
sdk install java 21.0.1-tem - List versions:
sdk list java - Set default:
sdk default java 21.0.1-tem - Use temporarily:
sdk use java 17.0.9-tem - Check current:
sdk current java - Project config: Create
.sdkmanrcand runsdk env
References: https://github.com/pyenv/pyenv