This guide is for people who would like to be involved in building http4s.

Find something that belongs in http4s

Looking for a way that you can help out? Check out our issue tracker. Choose a ticket that looks interesting to you. Before you start working on it, make sure that it's not already assigned to someone and that nobody has left a comment saying that they are working on it! Of course, you can also comment on an issue someone is already working on and offer to collaborate.

Have an idea for something new? That's great! We recommend that you make sure it belongs in http4s before you put effort into creating a pull request. The preferred ways to do that are to either:

Let us know you are working on it

If there is already a GitHub issue for the task you are working on, leave a comment to let people know that you are working on it. If there isn't already an issue and it is a non-trivial task, it's a good idea to create one (and note that you're working on it). This prevents contributors from duplicating effort.

Build the Project

First you'll need to checkout a local copy of the code base:

git clone

To build http4s, you should have SBT and Hugo installed. To test http4s you will also need Node.js v16 and yarn. Run sbt, then use one of the following commands:

Coding Standard


The Github Actions CI workflow verifies that code is formatted correctly according to the Scalafmt config and will fail if a diff is found.

You can run scalafmtCheckAll to test the formatting before opening a PR. If your PR fails due to formatting, run scalafmtAll.

IntelliJ IDEA specific settings

To setup IntelliJ IDEA to conform with the formatting used in this project, the following settings should be changed from the default.

Under Settings > Editor > Code Style > Scala:



Prefer a parameterized effect type and cats-effect type classes over specializing on a task.

// Good
def apply[F[_]](service: HttpApp[F])(implicit F: Monad[F]): HttpApp[F]

// Bad
def apply(service: HttpApp[IO]): HttpService[IO]

For examples and tutorials, use cats.effect.IO wherever a concrete effect is needed.


Prefer standard library types such as Option and List to invariant replacements from libraries such as Scalaz or Dogs.

When a list must not be empty, use


Many parts of the HTTP spec require case-insensitive semantics. Use to represent these. This is important to get correct matching semantics when using case class extractors.

Case classes


The apply method of a case class companion should be total. If this is impossible for the product type, create a sealed abstract class and define alternate constructors in the companion object. Make the implementation of the sealed abstract class private.

Consider a macro for the apply method if it is partial, but literal arguments can be validated at compile time.

Safe Constructors

Constructors that take an alternate type A should be named fromA. This includes constructors that return a value as a ParseResult.

case class Foo(seconds: Long)

object Foo {
  def fromFiniteDuration(d: FiniteDuration): Foo =

  def fromString(s: String): ParseResult[Foo] =
    try s.toLong
    catch { case e: NumberFormatException =>
      new ParseFailure("not a long")

Prefer fromString to parse.

Unsafe Constructors

All constructors that are partial on their input should be prefixed with unsafe.

// Good
def fromLong(l: Long): ParseResult[Foo] =
  if (l < 0) Left(ParseFailure("l must be non-negative"))
  else Right(new Foo(l))
def unsafeFromLong(l: Long): Foo =
  fromLong(l).fold(throw _, identity)

// Bad
def fromLong(l: Long): ParseResult[Foo] =
  if (l < 0) throw new ParseFailure("crash boom bang")
  else Right(new Foo(l))

Constructors prefixed with from may return either a ParseResult[A] or, if total, A.


If your contribution has been derived from or inspired by other work, please state this in its scaladoc comment and provide proper attribution. When possible, include the original authors' names and a link to the original work.

Grant of License

http4s is licensed under the Apache License 2.0. Opening a pull request signifies your consent to license your contributions under the Apache License 2.0.



The common area of (i.e., directories not beginning with /v#.#) is generated from the docs/ directory and is published only from the main branch. This module is intended to contain general info about the project that applies to all versions.

Each branch, main and series/X.Y, publishes documentation per minor version into the /vX.Y directory of
The mdoc content lives in docs/docs.
mdoc is used to typecheck our documentation as part of the build.

Running the Site Locally

For generating a static site locally, run from within sbt:


For starting a preview server with live updates, run from within sbt:


Now you can open a browser at http://localhost:4242/ to see the local version of the site. At http://localhost:4242/v{currentVersion}/ would be the local version of the versioned part of the site (e.g. http://localhost:4242/v0.23/).

When you update any input sources, mdoc will detect this and compile the Scala code and write the modified Markdown sources to its output directory which in turn Laika is watching. Note that when running tlSitePreview Laika does not write any output to disk, it serves the site entirely from memory. And btw: it uses http4s for that.

Submit a Pull Request

Before you open a pull request, you should run sbt quicklint and commit any results.

If your pull request addresses an existing issue, please tag that issue number in the body of your pull request or commit message. For example, if your pull request addresses issue number 52, please include "fixes #52".

If you make changes after you have opened your pull request, please add them as separate commits and avoid squashing or rebasing. Squashing and rebasing can lead to a tidier git history, but they can also be a hassle if somebody else has done work based on your branch.

This guide borrows heavily from the Cats' contributors guide.