OTP 25.0-rc3 (release Candidate 3) is released

OTP 25-rc3

Erlang/OTP 25-rc3 is the third and final release candidate before the OTP 25.0 release.

The intention with this release is to get feedback from our users. All feedback is welcome, even if it is only to say that it works for you. We encourage users to try it out and give us feedback either by creating an issue here Issues · erlang/otp · GitHub or by posting to Erlangforums or the mailing list erlang-questions@erlang.org.

All artifacts for the release can be downloaded from the Erlang/OTP Github release and you can view the new documentation at Erlang/OTP 25.0 Release Candidate 3. You can also install the latest release using kerl like this: kerl build 25.0-rc3 25.0-rc3.

Erlang/OTP 25 is a new major release with new features, improvements as well as a few incompatibilities. Some of the new features are highlighted below.

Many thanks to all contributors!

Below are some highlights of the release:

Highlights rc3


Change format of feature options and directives for better consistency. Options to erlc and the -compile(..) directive now has the format {feature, feature-name, enable | disable}. The -feature(..) now has the format -feature(feature-name, enable | disable).


Introducing a new (still experimental) option {certs_keys,[cert_key_conf()]}. With this a list of a certificates with their associated key may be used to authenticate the client or the server. The certificate key pair that is considered best and matches negotiated parameters for the connection will be selected.

Highlights rc2


  • New function filelib:ensure_path/1 will ensure that all directories for the given path exists
  • New functions groups_from_list/2 and groups_from_list/3 in the maps module
  • New functions uniq/1 uniq/2 in the lists module

compiler, kernel, stdlib, syntax_tools

  • Added support for selectable features as described in EEP-60. Features can be enabled/disabled during compilation with options (ordinary and +term) to erlc as well as with directives in the file. Similar options can be used to erl for enabling/disabling features allowed at runtime. The new maybe expression EEP-49 is fully supported as the feature maybe_expr.

Highlights rc1

erts & jit

  • The JIT now works for 64-bit ARM processors.
  • The JIT now does type-based optimizations based on type information in the BEAM files.
  • Improved the JIT’s support for external tools like perf and gdb, allowing them to show line numbers and even the original Erlang source code when that can be found.

erts, stdlib, kernel

  • Users can now configure ETS tables with the {write_concurrency, auto} option. This option forces tables to automatically change the number of locks that are used at run-time depending on how much concurrency is detected. The {decentralized_counters, true} option is enabled by default when {write_concurrency, auto} is active.Benchmark results comparing this option with the other ETS optimization options are available here: benchmarks.
  • To enable more optimizations, BEAM files compiled with OTP 21 and earlier cannot be loaded in OTP 25.
  • The signal queue of a process with the process flag message_queue_data=off_heap has been optimized to allow parallel reception of signals from multiple processes. This can improve performance when many processes are sending in parallel to one process. See benchmark.
  • The Erlang installation directory is now relocatable on the file system given that the paths in the installation’s RELEASES file are paths that are relative to the installations root directory.
  • A new option called short has been added to the functions erlang:float_to_list/2 and erlang:float_to_binary/2. This option creates the shortest correctly rounded string representation of the given float that can be converted back to the same float again.
  • Introduction of quote/1 and unquote/1 functions in the uri_string module - a replacement for the deprecated functions http_uri:encode and http_uri:decode.
  • The new module peer supersedes the slave module. The slave module is now deprecated and will be removed in OTP 27.
  • global will now by default prevent overlapping partitions due to network issues. This is done by actively disconnecting from nodes that reports that they have lost connections to other nodes. This will cause fully connected partitions to form instead of leaving the network in a state with overlapping partitions.It is possible to turn off the new behavior by setting the the kernel configuration parameter prevent_overlapping_partitions to false. Doing this will retain the same behavior as in OTP 24 and earlier.
  • The format_status/2 callback for gen_server, gen_statem and gen_event has been deprecated in favor of the new format_status/1 callback.The new callback adds the possibility to limit and change many more things than the just the state.
  • The timer module has been modernized and made more efficient, which makes the timer server less susceptible to being overloaded. The timer:sleep/1 function now accepts an arbitrarily large integer.


  • The maybe ... end construction as proposed in EEP-49 has been implemented. It can simplify complex code where otherwise deeply nested cases would have to be used.To enable maybe, give the option {enable_feature,maybe_expr} to the compiler. The exact option to use will change in a coming release candidate and then it will also be possible to use from inside the module being compiled.
  • When a record matching or record update fails, a {badrecord, ExpectedRecordTag} exception used to be raised. In this release, the exception has been changed to {badrecord, ActualValue}, where ActualValue is the value that was found instead of the expected record.
  • Add compile attribute -nifs() to empower compiler and loader with information about which functions may be overridden as NIFs by erlang:load_nif/2.
  • Improved and more detailed error messages when binary construction with the binary syntax fails. This applies both for error messages in the shell and for erl_error:format_exception/3,4.


  • Add crypto:hash_equals/2 which is a constant time comparision of hashvalues.


  • Optimize operations in the erl_types module. Parallelize the Dialyzer pass remote.
  • Added the missing_return and extra_return options to raise warnings when specifications differ from inferred types. These are similar to, but not quite as verbose as overspecs and underspecs.
  • Dialyzer now better understands the types for min/2, max/2, and erlang:raise/3. Because of that, Dialyzer can potentially generate new warnings. In particular, functions that use erlang:raise/3 could now need a spec with a no_return() return type to avoid an unwanted warning.


  • A new DEVELOPMENT HOWTO guide has been added that describes how to build and test Erlang/OTP when fixing bugs or developing new functionality.
  • Testing has been added to the Github actions run for each opened PR so that more bugs are caught earlier when bug fixes and new features are proposed.

For more details about new features and potential incompatibilities see


I’ve verified that Yaws works correctly with 25.0-rc3.


rc3 works for me, looking forward for the final release!

btw I found out that the unexpected behaviour which I experienced with RC2 and peer, had nothing to do with both.
In my first post, when I said that I have a different result if I do it manually, that was because I introduced a delay.
If I call erlang:nodes() on B and C immediately afterwards with erpc:call/4, I get the same behaviour as with peer.
But if I wait ~250ms, then each node sees all the other nodes.
So there is a chance for a race condition between pinging other nodes and getting the final, connected

For my work I currently do not need it, but in larger networks, when could one safely assume that
all nodes are connected? Or is there a method to be certain?
Assuming something is a certain cause for errors…


For those using peer in common test…


-define(ERROR_REL, "Common test requires 'peer' module, available starting OTP-25.").
%% Requires 'peer' module available starting OTP-25
  %% OTP 25 or higher
  -if(?OTP_RELEASE >= 25).
     onload() -> ok.
     onload() -> erlang:display(?ERROR_REL), error.
  %% OTP 20 or lower.
     onload() -> erlang:display(?ERROR_REL), error.

Has there been any change in handling of -behaviour()? I have some legacy code which essentially extends a gen_server with some bespoke functionality (I wouldn’t necessarily do it this way again). It compiles with 24.3.3, but with the 25.0 release candidates, it fails to rebar3 compile with:

===> Compiling <app>
===> Compiling apps/<app>/src/<module>.erl failed
apps/<app>/src/<module>.erl:none: internal error in pass parse_module:
exception error: bad argument
  in operator  --/2
     called as {'EXIT',#Port<0.10>,normal} -- []
  in call from compile:metadata_add_features/2 (compile.erl, line 1068)
  in call from compile:do_parse_module/2 (compile.erl, line 1037)
  in call from compile:parse_module/2 (compile.erl, line 994)
  in call from compile:fold_comp/4 (compile.erl, line 405)
  in call from compile:internal_comp/5 (compile.erl, line 389)
  in call from compile:'-internal_fun/2-anonymous-0-'/2 (compile.erl, line 227)
  in call from rebar_compiler_erl:compile_and_track/4 (/home/runner/work/rebar3/rebar3/src/rebar_compiler_erl.erl, line 157)

If I comment out the -behaviour(<module>). entry in the dependent files, it compiles with 25.0-rc3.


Thanks for reporting this. Considering the stacktrace, this has to do with the addition of feature support. Do you perhaps have a small example that reproduces this?


I’ll try. One thing to note that I can compile using erlc without error:

erlc -pa . -v <behaviour_module>.erl
erlc -pa . -v <dependent_module>.erl

Which led me to check my rebar.config options. However, even with an empty rebar.config I get the same effect.


Thanks, but this does not help me in reproducing the issue. I made a minimal setup (rebar3 new lib) with just two modules as above. Using a rebar3 built with rc3 rebar3 compile had no problems. More details needed, it seems.


That’s the approach I’m also taking (rebar3 new release in my case, so as to reproduce the directory structure), but as yet I’ve had no luck in reproducing the error either :frowning:


Hmm, that is both good and bad, I guess :wink: The first argument to --/2 is very strange as it looks like the exit reason for a port. How it has ended up there is a mystery since in the code it is the result of getting a value from a proplist. I’ll spend some additional time on it, but rihght now I have nothing to go on. If you manage to reproduce it on your side, I’ll be happy to take any details you have.


Actually, looking a bit deeper and discussing with @jhogberg, we found some lax code that could explain this behaviour as well as making it rather difficult to reproduce. I’ll tighten the code, and then we hope we do not see it again.


Progress (of a sort).

Going back to the original, failing code and with liberal use of a blunt hatchet, I have managed to get to the point where the problem is evident even when using the minimal files:

-callback bar() -> ok.


bar() -> ok.

However, if I chop too much of the surrounding application code out, it mysteriously disappears back into the magic hat.


Modulo names, that is exactly identical to what I tested with :slight_smile: The difficulty of reproduction most likely comes down to a combination of timing and sending/receiving messages. In the current code, an untagged message is received, which might grab the wrong message. Adding a tag should fix that potential problem.


@cons You might be interested that I am encountering the same error with the repo linked from Ram - an in-memory distributed KV store - #9 by dischoen using 25.0-rc3.

rebar3 compile
===> Verifying dependencies...
===> Fetching ram v0.5.0
===> Fetching ra v2.0.3
===> Fetching aten v0.5.7
===> Fetching gen_batch_server v0.8.6
===> Analyzing applications...
===> Compiling gen_batch_server
===> Compiling _build/default/lib/gen_batch_server/src/gen_batch_server.erl failed
_build/default/lib/gen_batch_server/src/gen_batch_server.erl:none: internal error in pass parse_module:
exception error: bad argument
  in operator  --/2
     called as {'EXIT',#Port<0.8>,normal} -- []
  in call from compile:metadata_add_features/2 (compile.erl, line 1068)
  in call from compile:do_parse_module/2 (compile.erl, line 1037)
  in call from compile:parse_module/2 (compile.erl, line 994)
  in call from compile:fold_comp/4 (compile.erl, line 405)
  in call from compile:internal_comp/5 (compile.erl, line 389)
  in call from compile:'-internal_fun/2-anonymous-0-'/2 (compile.erl, line 227)
  in call from rebar_compiler_erl:compile_and_track/4 (/home/runner/work/rebar3/rebar3/src/rebar_compiler_erl.erl, line 157)

The error occurs in the dependency gen_batch_server.erl, which utilises -callback declarations - I assume that is the common ground where the issue is triggered.


Thanks. Using this I can reproduce the behaviour and also verify that the fix I have works. The common ground, however, is not the use of -callback, but rather using rebar3. We have some 768 instances of -callback in the OTP code base and those compile without problems with RC3. Just to be clear, there is no problem with rebar3 – the fault was fully in the new code implementing the features support.


may I ask which configuration you are using?
I also tried my rampeer repo with rc3 and it is working over here.
My config is:

  • Ubuntu 20.04LTS
  • RC3 via asdf
  • rebar3 directly installed and via asdf, both working.

The problem has more to do with timing than configuration. The code in RC3 does a receive on an untagged message, which is fine as long as no other process manages to send message before it is expected. While I have not debugged the issue in depth, with the repo above I managed to reproduce it faithfully on my machine with an unpatched RC3 and see no problems with fix in there.
I might take some time later today and really dive into the issue for the sake of curiosity.


Great news that you have been able to verify your fix.


Since rc2, I’m getting segfaults when trying to build on Aarch64 (in qemu) under Alpine 3.15.
For a sample see: Images · travelping/docker-erlang-otp@a57e64e · GitHub


Thanks, it’s a bit difficult to see what’s happening though. Can you try the following in an interactive session?

# Build the system without JIT to dodge the error
$ ./otp_build setup -a --disable-jit
$ make TYPE=debug

# Build the JIT alone
$ ./configure --enable-jit
$ make emulator TYPE=debug

# Start Erlang under GDB, then run `r` and hope it crashes. You
# might need to compile something to provoke the crash.
# Once it crashes, run `bt`, `x/8i $pc-16`, and post the result here.
$ cerl -debug -rgdb