using InteractiveUtils # not necessary in a REPL session
supertypes(Int64)
(Int64, Signed, Integer, Real, Number, Any)
Alec Loudenback and MoJuWo Contributors
Installing and setting up your Julia environment. Text editor and REPL editing environments. Setting up your global environment for development. Creating packages. Logging and debugging code.
Before you write any line of code, it’s good to know where to find help. The official help page is a good place to start. In particular, the Julia community is always happy to guide beginners.
As a rule of thumb, the Discourse forum is where you should ask your questions to make the answers discoverable for future users. If you just want to chat with someone, you have a choice between the open source Zulip and the closed source Slack.
The most natural starting point to install Julia onto your system is the Julia downloads page, which will tell you to use juliaup
.
juliaup
together from the Windows Store.curl -fsSL https://install.julialang.org | sh
In both cases, this will make the juliaup
and julia
commands accessible from the terminal (or Windows Powershell). On Windows this will also create an application launcher. All users can start Julia by running
julia
Meanwhile, juliaup
provides various utilities to download, update, organize and switch between different Julia versions. As a bonus, you no longer have to manually specify the path to your executable. This all works thanks to adaptive shortcuts called “channels”, which allow you to access specific Julia versions without giving their exact number.
For instance, the release
channel will always point to the current stable version, and the lts
channel will always point to the long-term support version. Upon installation of juliaup
, the current stable version of Julia is downloaded and selected as the default.
To use other channels, add them to juliaup
and put a +
in front of the channel name when you start Julia:
juliaup add lts
julia +lts
You can get an overview of the channels installed on your computer with
juliaup status
When new versions are tagged, the version associated with a given channel can change, which means a new executable needs to be downloaded. If you want to catch up with the latest developments, just do
juliaup update
The Read-Eval-Print Loop (or REPL) is the most basic way to interact with Julia, check out its documentation for details. You can start a REPL by typing julia
into a terminal, or by clicking on the Julia application in your computer. It will allow you to play around with arbitrary Julia code:
julia> a, b = 1, 2;
julia> a + b
3
This is the standard (Julia) mode of the REPL, but there are three other modes you need to know. Each mode is entered by typing a specific character after the julia>
prompt. Once you’re in a non-Julia mode, you stay there for every command you run. To exit it, hit backspace after the prompt and you’ll get the julia>
prompt back.
?
)By pressing ?
you can obtain information and metadata about Julia objects (functions, types, etc.) or unicode symbols. The query fetches the docstring of the object, which explains how to use it.
help?> println
search: println print sprint pointer printstyled
println([io::IO], xs...)
Print (using print) xs to io followed by a newline. If io is not supplied, prints to the default output stream stdout.
See also printstyled to add colors etc.
Examples
≡≡≡≡≡≡≡≡
julia> println("Hello, world")
Hello, world
julia> io = IOBuffer();
julia> println(io, "Hello", ',', " world.")
julia> String(take!(io))
"Hello, world.\n"
If you don’t know the exact name you are looking for, type a word surrounded by quotes to see in which docstrings it pops up.
]
)By pressing ]
you access Pkg.jl, Julia’s integrated package manager, whose documentation is an absolute must-read. Pkg.jl allows you to:
]activate
different local, shared or temporary environments;]instantiate
them by downloading the necessary packages;]add
, ]update
(or ]up
) and ]remove
(or ]rm
) packages;]status
(or ]st
) of your current environment.As an illustration, we download the package Example.jl inside a new environment we call demo
(which will create an associated folder if it does not exist):
(demo) pkg> activate demo
Activating new project at `~/demo`
(demo) pkg> add Example
Resolving package versions...
Updating `~/demo/Project.toml`
[7876af07] + Example v0.5.5
Updating `~/demo/Manifest.toml`
[7876af07] + Example v0.5.5
(demo) pkg> status
Status `~/demo/Project.toml`
[7876af07] Example v0.5.5
Note that the same keywords are also available in Julia mode:
julia> using Pkg
julia> Pkg.rm("Example")
Updating `~/demo/Project.toml`
[7876af07] - Example v0.5.5
Updating `~/demo/Manifest.toml`
[7876af07] - Example v0.5.5
The package mode itself also has a help mode, accessed with ?
, in case you’re lost among all these new keywords.
;
)By pressing ;
you enter a terminal, where you can execute any command you want, such as changing the working directory to the folder we just created:
shell> cd demo
/Users/myself/demo
In theory, any text editor suffices to write and modify Julia code. In practice, an Integrated Development Environment (or IDE) makes the experience much more pleasant, thanks to code-related utilities and language-specific plugins.
The best IDE for Julia is Visual Studio Code, or VSCode, developed by Microsoft. The Julia VSCode extension is the most feature-rich of all Julia IDE plugins. You can download it from the VSCode Marketplace and read its documentation.
In what follows, we will sometimes mention commands and keyboard shortcuts provided by this extension. But the only shortcut you need to remember is Ctrl + Shift + P
(or Cmd + Shift + P
on Mac): this opens the VSCode command palette, in which you can search for any command. Type julia
in the command palette to see what you can do.
You can execute a Julia script from your terminal, but in most cases that is not what you want to do.
julia myfile.jl # avoid this
Julia has a rather high startup and compilation latency. If you only use scripts, you will pay this cost every time you run a slightly modified version of your code. That is why many Julia developers fire up a REPL at the beginning of the day and run all of their code there, chunk by chunk, in an interactive way. Full files can be run interactively from the REPL with the include
function.
julia> include("myfile.jl")
Alternatively, includet
from the Revise.jl package can be used to “include and track” a file. This will automatically update changes to function definitions in the file in the running REPL session.
Running code is made much easier by the following commands:
Julia: Restart REPL
(shortcut Alt + J
then Alt + R
) - this will open or restart the integrated Julia REPL. It is different from opening a plain VSCode terminal and launching Julia manually from there.Julia: Execute Code in REPL and Move
(shortcut Shift + Enter
) - this will execute the selected code in the integrated Julia REPL, like a notebook.When keeping the same REPL open for a long time, it’s common to end up with a “polluted” workspace where the definitions of certain variables or functions have been overwritten in unexpected ways. This, along with other events like struct
redefinitions, might force you to restart your REPL now and again, and that’s okay.
Notebooks are a popular alternative to IDEs when it comes to short and self-contained code, typically in data science. They are also a good fit for literate programming, where lines of code are interspersed with comments and explanations.
The most well-known notebook ecosystem is Jupyter, which supports Julia, Python and R as its three core languages. To use it with Julia, you will need to install the IJulia.jl backend. Then, if you have also installed Jupyter with pip install jupyterlab
, you can run this command to launch the server:
jupyter lab
If you only have IJulia.jl on your system, you can run this snippet instead:
julia> using IJulia
julia> IJulia.notebook()
Jupyter notebooks can be opened, modified and run directly from VSCode. Thanks to the Julia extension, you don’t even need to install IJulia.jl or Jupyter first.
Pluto.jl
is a newer, pure-Julia tool, adding reactivity and interactivity. It is also more amenable to version control than Jupyter notebooks because notebooks are saved as plain Julia scripts. Pluto is unique to Julia because of the language’s ability to introspect and analyze dependencies in its own code. Pluto also has built-in package/environment management, meaning that Pluto notebooks contains all the code needed to reproduce results (as long as Julia and Pluto are installed).
To try out Pluto, install the package and then run
julia> using Pluto
julia> Pluto.run()
Markdown is a markup language used to add formatting elements to plain text content, such as Julia docstrings. Additionally, other tools such as Quarto (described below) are built using Markdown notation as the basis for their formatting, so it’s useful to know about Markdown and the most essential
Plain text markdown files, which have the .md
extension, are not used for interactive programming, meaning one cannot run code written in the file. As a result, plain text markdown files are usually rendered into a final product by other software.
This is an example of a plain text markdown file, including a code example contained within the `````````` block:
# Title
## Section Header
This is example text.
```julia
println("hello world")
```
Quarto “is an open-source scientific and technical publishing system.” Quarto makes a plain text markdown file (.md
) alternative called Quarto markdown file (.qmd
).
Quarto markdown files like plain text markdown files also integrate with editors, such as VSCode.
Install the Quarto extension for a streamlined experience.
Unlike plain text markdown files, Quarto markdown files have executable code chunks. These code chunks provide a functionality similar to notebooks, thus Quarto markdown files are an alternative to notebooks. Additionally, Quarto markdown files give users additional control over output and styling via the YAML header at the top of the .qmd
file.
As of Quarto version 1.5, users can choose from two Julia engines to execute code - a native Julia engine and IJulia.jl. The primary difference between the native Julia engine and IJulia.jl is that the native Julia engine does not depend on Python and can utilize local environments. For this reason it’s recommended to start with the native Julia engine. Learn more about the native Julia engine in Quarto’s documentation.
This book is built using Quarto documents to create the associated typeset book and website.
Julia comes bundled with Pkg.jl, an environment and package manager. It enables installation of packages from registries, pinning versions for compatibility, and analyzing your dependencies. Environment is meant to mean, in general, the computer you use and software installed in it. When we speak about environments in the Julia context, this means the Julia version and packages available to the current Julia code. For example, from the current code is a given package installed and usable?
If you open a Julia REPL, by default you will be in the global environment. If you hit ]
to enter Pkg mode, you should see:
(@v1.10) pkg>
The (@1.10)
indicates that you are using the global environment for the current Julia version (there is no global environment which applies across all Julia versions installed). You can activate a new environment with activate [environment name]
.
(@v1.10) pkg> activate MyNewEnv
Activating new project at `~/MyNewEnv`
This will… not do anything. Yet! When we add a package to this environment, then it will create a Project.toml
and Manifest.toml
file in that directory. Now that directory is a full fledged Julia project!
Activate a temporary environment with activate --temp
. This will give you a temporary environment with a random name, which is very useful for testing out things in a clean, simplified environment (the global environment, like @1.10
still applies.)
A Project.toml
file defines attributes about the current project and its dependencies. Julia uses this to understand how to reference your current project and what dependencies it should look for from registries when instantiating the project.
TOML (Tom’s Obvious Markup Language) is a modern configuration file format used to store settings and data in a human-readable, plaintext format.
This is a bit abstract, so here is a quick, annotated tour of an example Project.toml file:
1name = "FinanceCore"
2uuid = "b9b1ffdd-6612-4b69-8227-7663be06e089"
authors = ["alecloudenback <[email protected]> and contributors"]
3version = "2.1.0"
4[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
LoopVectorization = "bdcacae8-1622-11e9-2a5c-532679323890"
Roots = "f2b01f46-fcfa-551c-844a-d8ac1e96c665"
5[compat]
Dates = "1"
LoopVectorization = "^0.12"
Roots = "^1.0, 2"
julia = "1.6"
name
is the name of your current project which only matters if you turn your project into a package.
UUIDs
standard library.
deps
section records the name of direct dependencies and their UUIDs so that Julia can know which packages to grab in order to make your project run.
compat
section defines compatibility with packages can be enforced (via SemVer) to clarify which versions are allowed to be installed in case incompatibilities arise.
When you instantiate
a project (see Section 21.9 for more), Julia will essentially add
the packages listed under deps
, and will resolve the compatible versions, generally picking the highest version number for the packages so long as the compat
section rule are note broken.
When adding the dependencies, those packages themselves likely specify their own set of dependencies and Julia must resolve the entire dependency graph or dependency tree to allow your current project to work.
Semantic Versioning (“SemVer”) is a scheme which uses the three-component version code to convey meaning about different versions of a package to both users and computer systems. With the version scheme vMAJOR.MINOR.PATCH
, the meaning is roughly as follows:
As an example, say we are currently using v2.10.4
of a package, and the following theoretical options are available for us to upgrade to:
v2.10.5
- The 4
to 5
indicates that something may have been broken in the prior release and so we should upgrade without fear that we need to make changes to our code (unless we relied on the previously broken code!).v2.11.0
- The 10
to 11
bump suggests that the new release contains some features which should not require us to change any of our previously written code.v3.0.0
- The 2
to 3
indicates that we will potentially have to modify code that we have written that interfaces with this dependency.SemVer cannot distill all possible compatibility and upgrade information about a set of packages (e.g. an author may release an update with a MINOR version which also includes fixes).
The Manifest.toml
file includes a record of all external dependencies used by the project at hand. Unlike Project.toml
, this file gets machine generated when Julia instantiates or updates the environment. The contents are basically a long list of your direct dependencies and the dependencies of those direct dependencies and looks something like this:
julia_version = "1.10.0"
manifest_format = "2.0"
project_hash = "5fea00df4808d89f9c977d15b8ee992bd408081b"
[[deps.AbstractFFTs]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "d92ad398961a3ed262d8bf04a1a2b8340f915fef"
uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c"
version = "1.5.0"
weakdeps = ["ChainRulesCore", "Test"]
[deps.AbstractFFTs.extensions]
AbstractFFTsChainRulesCoreExt = "ChainRulesCore"
AbstractFFTsTestExt = "Test"
... many more lines
Starting in Julia 1.11, Manifest files will include a version indication, making it nicer to work with multiple Julia versions at one time on a single system.
Reproducibility fulfills both practical and principled goals. Practical in that we can record the complex chain of dependencies that is used in modern computing in order to potentially re-create a result or demonstrate an audit trail of the tools used. Principled in that there are circumstances (like science research) in which we want to be able to replicate results. The combination of Project.toml
and Manifest.toml
go a long way towards accomplishing this, as you can share both and with the same hardware and Julia version should be able to get the exact same set of dependencies and therefore run the same code. In practice, this level of reproducibility isn’t usually needed, as most time a set of code can be run accurately without requiring the exact same set of dependencies.
Since dependencies can have variation between systems (Windows/Mac) and architectures (x86 vs x64), you may not be able to recreate the Manifest exactly. Nevertheless, it’s a fairly low bar if you are trying to maintain the utmost level of rigor around the toolchain and Julia is one of the most robust languages regarding tools to support open replication of results.
Julia has a system called artifacts which allows specification of a location and hash (a cryptographic key) for data and binaries. The artifact system used to download and verify the contents of a file match the hash. This is designed for more permanent data and less end-user workflows, but we call it out here as another example where Julia takes steps to promote consistency and reproducibility.
For more on data workflows for the end-user, see Chapter 12.
You can configure the environment in which a VSCode Julia REPL opens. Just click the Julia env: ...
button at the bottom. Note however that the Julia version itself will always be the default one from juliaup
.
Once your code base grows beyond a few scripts, you will want to create a package of your own. The first advantage is that you don’t need to specify the path of every file: using MyPackage
is enough to get access to the names you define and export (or using MyPackage: myfunc1, myfunc2
to use bring non-exported functions into your environment). Furthermore, by structuring your project as a Pacakge, you can specify versions for your package and its dependencies, making your code easier and safer to reuse.
To create a new package locally, the easy way is to use ]generate
(we will discuss a more sophisticated workflow in the next blog post).
Pkg.generate(sitepath("MyPackage")); # ignore sitepath
This command initializes a simple folder with a Project.toml
and a src
subfolder. As we have seen, the Project.toml
specifies the dependencies. Meanwhile, the src
subfolder contains a file MyPackage.jl
, where a module called MyPackage
is defined. It is the heart of your package, and will typically look like this when you’re done:
module MyPackage
# imported dependencies
using OtherPackage1
using OtherPackage2
# files defining functions, types, etc.
include("file1.jl")
include("subfolder/file2.jl")
# names you want to make public
export myfunc # e.g. defined in `file1.jl`
export MyType
end
PkgTemplates.jl is like ]generate
from Pkg.jl but provides a number of options to pre-configure the repository for things such as continuous integration, testing, and compatibiltiy. If you are not yet making use of that more advanced functionality, the ]generate
method will work just fine for you.
This will walk you through an interactive prompt to create a package in the desired folder. ~/.julia/dev
is a suggested location, but technically any folder will make do:
using PkgTemplates
cd("~/.julia/dev")
Template(interactive=true)("MyPkg")
Once you have created a package, your development routine might look like this:
MyPackage
MyPackage
For that to work well, you need code modifications to be taken into account automatically. That is why Revise.jl exists. If you start every REPL session by explicitly Revise.jl (using Revise
), then all the other packages you import after that will have their code tracked. Whenever you edit a source file and hit save, the REPL will update its state accordingly. To automatically do this for every session, see Section 21.12.
The Julia extension imports Revise.jl by default when it starts a REPL.
This is how you get started using your own package once it’s set up:
using Revise, Pkg
Pkg.activate("./MyPackage")
using MyPackage
myfunc() # defined and exported in MyPackage
myfunc2() # defined and *not* exported in MyPackage MyPackage.
If you are working on a set of interrelated packages, you may need to tell those packages to use the development version of the package which you are modifying, instead of using the latest available from a registry. For example, say you are working on revisions to PkgA in the following dependency tree:
PkgB -- depends on -- > PkgA
If you are modifying PkgA, then you might need to tell PkgB to use the development version. For this, then you would need to:
activate @mydevenv
).]dev PkgB
which will download the associated repository into ~./julia/dev/PkgB
~/.julia/dev/PkgB
and tell that environment to use the development version of PkgA with ]dev PkgB
(assuming you are modifying PkgA also in the ~/.julia/dev/
folder)Now, in the @mydevenv
environemnt, when you load PkgB
it will load the version of PkgA
Julia accepts startup flags to handle settings such as the number of threads available or the environment in which it launches. In addition, most Julia developers also have a startup file which is run automatically every time the language is started. It is located at .julia/config/startup.jl
.
The basic component that everyone puts in the startup file is Revise.jl. Users also commonly import packages that affect the REPL experience, as well as esthetic, benchmarking or profiling utilities. A typical example is OhMyREPL.jl which is widely used for syntax highlighting in the REPL. While other packages are often used, we suggest the following as a minimum:
# save as a file in /.julia/config/startup.jl
try
using Revise
using OhMyREPL
catch e
@warn "Error with startup packages"
end
More generally, the startup file allows you to define your own favorite helper functions and have them immediately available in every Julia session. StartupCustomizer.jl can help you set up your startup file.
Here are a few more startup packages that can make your life easier once you know the language better:
The Julia REPL comes bundled with InteractiveUtils.jl, a bunch of very useful functions for interacting with source code.
Here are a few examples:
using InteractiveUtils # not necessary in a REPL session
supertypes(Int64)
(Int64, Signed, Integer, Real, Number, Any)
subtypes(Integer)
3-element Vector{Any}:
Bool
Signed
Unsigned
methodswith(Integer)[1:5] # first five methods that take an integer argument
@which exp(1) # where the currently used function is defined
apropos("matrix exponential") # search docstrings
Base.exp
Base.:^
When you ask for help on a Julia forum, you might want to include your local Julia information:
versioninfo()
Julia Version 1.11.6
Commit 9615af0f269 (2025-07-09 12:58 UTC)
Build Info:
Official https://julialang.org/ release
Platform Info:
OS: macOS (arm64-apple-darwin24.0.0)
CPU: 14 × Apple M4 Max
WORD_SIZE: 64
LLVM: libLLVM-16.0.6 (ORCJIT, apple-m1)
Threads: 10 default, 0 interactive, 5 GC (on 10 virtual cores)
Environment:
JULIA_NUM_THREADS = auto
JULIA_PROJECT = @.
JULIA_LOAD_PATH = @:@stdlib
The following packages can give you even more interactive power:
Testing in Julia primarily revolves around the built-in Test package, which provides a straightforward way to write and run tests using the @test
macro. The basic syntax is simple - you write @test expression
where the expression should evaluate to true
for the test to pass.
To run tests in Julia, navigate to the package directory and run Pkg.test()
, or use the ] test YourPackageName
command in the Julia REPL. This will run the file in the package directory contained in test/runtests.jl
. The test infrastructure automatically handles setting up the correct environment and dependencies for testing. Test coverage reports can be generated to see which lines of code are exercised by your tests.
runtests.jl
is just a normal Julia file and can include
other files to help organize your tests. This structure integrates naturally with Julia’s package manager and testing tools.
Test organization is handled through @testset
blocks, which group related tests together and provide summary statistics when tests are run. For example, extending the introduction to testing from Section 12.3:
using Test
function present_value(discount_rate, cashflows)
= 1.0
v = 0.0
pv for cf in cashflows
= v / (1 + discount_rate)
v = pv + v * cf
pv end
return pv
end
@testset "Scalar Discount" begin
@test present_value(0.05, 10) ≈ 10 / 1.05
@test present_value(0.05, 20) ≈ 20 / 1.05
end
@testset "Vector Discount" begin
@test present_value(0.05, [10]) ≈ 10 / 1.05
@test present_value(0.05, [10, 20]) ≈ 10 / 1.05 + 20 / 1.05^2
end;
Test Summary: | Pass Total Time Scalar Discount | 2 2 0.1s Test Summary: | Pass Total Time Vector Discount | 2 2 0.0s
There are many more related testing facilities described in the Julia Docs, such as combining for
loops with test sets.
For floating-point comparisons, you’ll often want to use isapprox
(as a shorter symol: ≈
, typed as ==
to handle small numerical differences. Here’s some examples:
Here’s the default behavior for isapprox
, excerpted from its docstring:
For real or complex floating-point values, if an
atol
> 0 is not specified,rtol
defaults to the square root ofeps
of the type of x or y, whichever is bigger (least precise). This corresponds to requiring equality of about half of the significant digits. Otherwise, e.g. for integer arguments or if anatol
> 0 is supplied,rtol
defaults to zero.
The testing workflow in Julia supports both test-driven development and continuous integration seamlessly. Tests can be run locally during development, and services like GitHub Actions can automatically run your test suite on multiple Julia versions and operating systems when you push changes.
Good testing practices in Julia involve testing edge cases, using appropriate numerical tolerances, organizing tests logically, and ensuring adequate coverage of your code’s functionality. It’s also important to write tests that are clear and maintainable - each test should have a specific purpose and test one thing well.
When registering a package to a repository, the repository will record the version indicated in the Project.toml
file to the git commit id of the package when it is registered.↩︎