Optim.jl is Julia's unconstrained optimizer, and the result object is the point
Optimization functions for Julia
At a glance
- What is it?
- Optim.jl provides univariate and multivariate optimization in Julia with a single optimize entry point, a set of methods including BFGS and LBFGS, and a result object that reports convergence measures and work counters rather than just the answer. It is unconstrained only, and the JuMP path exists so that constrained problems can be handled by a solver instead.
- Who is it for?
- Optim.jl fits a Julia project that has an objective function and a gradient, or a derivative-free objective, and wants a well-known method with convergence diagnostics rather than a black box. It does not fit a problem with constraints or integer variables, because it is unconstrained and the README shows you handing that case to JuMP and a solver.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Julia, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One function, a method instance and an initial guess
The whole API is visible in the README's example, which minimises the Rosenbrock function with BFGS.
julia> using Optim
julia> rosenbrock(x) = (1.0 - x[1])^2 + 100.0 * (x[2] - x[1]^2)^2
rosenbrock (generic function with 1 method)
julia> result = optimize(rosenbrock, zeros(2), BFGS())Three arguments. The objective, the starting point, and the method. The method is an instance rather than a type, so BFGS() is a configured algorithm object and its keywords are where the tuning lives. The starting point is an ordinary Julia array, and the objective is a plain function, which means the function you pass does not have to know anything about the optimizer.
That is the design choice worth noticing. Optim.jl is a set of numerical methods with a uniform calling convention, not a modelling layer. There is no expression graph, no problem object, and no constraint machinery of its own. For a problem that is a differentiable function of a vector, that is exactly the right amount of machinery, and for anything else you want a different tool.
The package describes itself as univariate and multivariate optimization in Julia, and it is part of the JuliaNLSolvers family, which is where the related packages live, including the line search implementations the methods refer to.
Installation is the standard package manager call.
import Pkg
Pkg.add("Optim")The online documentation is at julianlsolvers.github.io/Optim.jl/stable, and the repository carries both a docs/ directory and a separate benchmark/ directory.
The result object reports convergence, not just the answer
The printed result is longer than most optimizers output, and that is the most useful thing about the library.
* Status: success
* Candidate solution
Final objective value: 5.471433e-17
* Found with
Algorithm: BFGS
* Convergence measures
|x - x'| = 3.47e-07 ≰ 0.0e+00
|x - x'|/|x'| = 3.47e-07 ≰ 0.0e+00
|f(x) - f(x')| = 6.59e-14 ≰ 0.0e+00
|f(x) - f(x')|/|f(x')| = 1.20e+03 ≰ 0.0e+00
|g(x)| = 2.33e-09 ≤ 1.0e-08
* Work counters
Seconds run: 0 (vs limit Inf)
Iterations: 16
f(x) calls: 53
∇f(x) calls: 53Four things are reported that are usually hidden. There is a status, which is success or a failure reason. There is a candidate solution, with the final objective value. There are convergence measures, which are the actual stopping criteria of the method, including the gradient norm against its tolerance. And there are work counters, which is the number of iterations and the number of objective and gradient evaluations.
The work counters are the part that earns their place in practice. If a solve takes a thousand iterations, the counters tell you whether the cost is in the objective evaluations or in the line search, and the gradient call count matching the function call count tells you whether the method is using your analytic gradient or falling back to finite differences. When the objective is expensive, that distinction is the difference between a run that takes minutes and one that takes hours.
The answer itself is pulled out with two accessors, Optim.minimizer(result) for the vector and Optim.minimum(result) for the objective value, and both work on the result object rather than requiring you to parse the printed output.
Method options live in the REPL help, and the docs are opinionated
The README's second code block is the help text for LBFGS, and it is worth reading because it shows how much of the library's substance is in the method configuration rather than in the wrapper.
help?> LBFGS
LBFGS(; m::Integer = 10,
alphaguess = LineSearches.InitialStatic(),
linesearch = LineSearches.HagerZhang(),
P=nothing,
precondprep = (P, x) -> nothing,
manifold = Flat(),
scaleinvH0::Bool = P === nothing)The keywords are the algorithm's actual degrees of freedom. m is the memory length, which determines how many previous Hessian approximations are stored, and the text explains that when scaleinvH0 is true the initial guess in the two-loop recursion to approximate the inverse Hessian is the scaled identity, citing Nocedal and Wright. Preconditioning is supported through P and precondprep, and the default manifold is Flat, which is the statement that this method assumes an unconstrained space.
That last keyword is the clearest signal of the library's scope. A manifold keyword on a method object implies that other manifolds exist or are planned, and it names the unconstrained case as the default rather than as the only case.
The documentation style is a real feature of the project. Every method carries its own help entry with a description and academic references, including the LBFGS entry citing Wright and Nocedal's Numerical optimization second edition and the original Liu and Nocedal 1989 paper on the limited memory method for large scale optimization. The README's advice is to use the Julia REPL help prompt with a question mark to get information on the keywords used to construct method instances.
So the method set is a menu of well-known algorithms with published defaults, and the documentation tells you which paper to read when a default is wrong for your problem.
JuMP handles the constrained case, not Optim
Optim.jl is unconstrained, and the README is explicit about where constrained problems are supposed to go: JuMP.
julia> using JuMP, Optim
julia> model = Model(Optim.Optimizer);
julia> set_optimizer_attribute(model, "method", BFGS())
julia> @variable(model, x[1:2]);
julia> @objective(model, Min, (1.0 - x[1])^2 + 100.0 * (x[2] - x[1]^2)^2)
julia> optimize!(model)The mechanism is that Optim's methods are exposed as a JuMP solver, so the JuMP layer owns the problem formulation, the objective expression, the variable declarations and the constraint handling, while the optimizer attribute selects which method runs. The result comes back through the JuMP API, with objective_value(model) and value.(x) reading the solution.
The difference in approach between the two paths is worth stating, because it is the main decision a user makes. Optim alone is a direct method applied to a function and a starting point, with no intermediate representation of the problem, which is fast to set up and gives you the raw convergence diagnostics. JuMP is a modelling layer: the variables, the objective and the model object are built in JuMP, and the example passes a BFGS() method through set_optimizer_attribute.
The README's example of setting the method through set_optimizer_attribute with a BFGS() instance shows that the method configuration is not lost when going through JuMP, so the same tuning you would do directly applies through the modelling layer.
For a reader, the practical rule is simple. If the problem is unconstrained and you have a gradient, call optimize directly. If the problem has constraints, model it in JuMP and either attach Optim as the solver for the unconstrained part or use a solver that handles constraints natively.
The repository carries its own QA apparatus
The badges at the top of the README tell you what kind of project this is. There is a build status badge for the CI workflow on the master branch, a codecov branch badge, and an Aqua QA badge.
There is also a JOSS badge, and the citation section explains what that is for. If you use Optim.jl in your work, please cite the paper, and the bibliography entry is given: Mogensen, Patrick Kofod and Riseth, Asbjørn Nilsen, Optim: A mathematical optimization package for Julia, in the Journal of Open Source Software, 2018, volume 3, number 24, pages 615, with the DOI 10.21105/joss.00615. The repository also carries a CITATION.bib file at the root and a joss/ directory, which holds the files for the paper submission.
The top-level entries reinforce the picture. There is a src/ directory for the implementation, test/ for the test suite, benchmark/ for the performance comparison work, docs/ for the manual, ext/ for the package extensions, Project.toml for the package metadata, NEWS.md for the changelog, and CONTRIBUTING.md and LICENSE.md.
Help and support are directed outward rather than to the issue tracker. The README asks people to post on the Optimization (Mathematical) section of the Julia discourse or in the #math-optimization channel of the Julia Slack, which is the normal arrangement for a package that is part of a wider family and a mature language ecosystem.
The two documentation badges, one for the stable version and one for the latest, are a small convention in the Julia ecosystem that maps to the dev and stable documentation builds of the package.
The licence metadata and the licence file disagree
The repository metadata reports the licence as a custom licence that GitHub cannot classify, pointing at the LICENSE file, and there is a LICENSE.md at the repository root. The README itself does not state a licence. For a package that the Julia general registry has carried for years, and that has a JOSS paper, the absence of a machine-readable licence identifier is a genuine gap rather than a stylistic one, because tooling that checks dependency licences reads the metadata.
A reader who wants to use Optim.jl in a product should read LICENSE.md and establish the terms from that file rather than from a badge, and confirm with the maintainers if the file does not name a standard licence. The project also ships a CITATION.bib and the JOSS paper, so the terms and the attribution requirements are both available, just not in the normalised form that a scanner expects.
On maintenance, the picture is healthy. The repository is not archived, the default branch is master, and the last push was on 2026-10-13. The recent releases are v2.3.2 on 2026-09-14, v2.3.1 on 2026-09-02 and v2.3.0 on 2026-09-02, so two patch-level releases and a minor release inside a fortnight, which is a healthy cadence for a numerical library where a bug in a line search or a Hessian approximation is worth shipping quickly.
The version numbering is worth a moment's attention as well. A 2.x line for a package first published in 2018 means the API has had a chance to stabilise, and the presence of a NEWS.md at the root is where you would look for what changed between minor versions, which is the file to read before bumping from 2.3 to the next minor.
Editorial conclusion
Optim.jl fits a Julia project that has an objective function and a gradient, or a derivative-free objective, and wants a well-known method with convergence diagnostics rather than a black box. It does not fit a problem with constraints or integer variables, because it is unconstrained and the README shows you handing that case to JuMP and a solver. Before adopting it, check the method's keyword documentation in the REPL, and compare the work counters in the result against the iteration budget you can afford, since that is where the cost of a bad gradient shows up first.
Frequently asked questions
How do I install Optim.jl in Julia?
Use the Julia package manager with import Pkg followed by Pkg.add("Optim"). The online documentation is published at julianlsolvers.github.io/Optim.jl/stable, and the package is part of the JuliaNLSolvers family.
How do I minimise a function with Optim.jl?
Call optimize with the objective, the starting point and a method instance, for example optimize(rosenbrock, zeros(2), BFGS()). The result object exposes the minimizer through Optim.minimizer(result) and the objective value through Optim.minimum(result).
What does the Optim.jl result object tell me?
It reports the status, the candidate solution and final objective value, the convergence measures of the method including the gradient norm against its tolerance, and work counters with the number of iterations and the number of objective and gradient evaluations. The gradient call count tells you whether an analytic gradient was used.
Can Optim.jl solve constrained optimisation problems?
Optim.jl itself is for unconstrained optimisation, with a Flat manifold as the default in its method keywords. For constrained problems the README shows using JuMP with Model(Optim.Optimizer) and choosing the method with set_optimizer_attribute, so the modelling and the constraints live in JuMP.
Which optimisation methods does Optim.jl provide and how do I read their options?
The package provides univariate and multivariate methods including BFGS and LBFGS, and the README shows the REPL help prompt, question mark followed by the method name, printing the constructor with its keywords. The LBFGS entry documents the memory length m, the scaleinvH0 flag, preconditioning through P and precondprep, and the default Flat manifold.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/julianlsolvers-optim-jl)Was this useful?