vnu: four routes to one HTML, CSS, and SVG conformance checker
The Nu Html Checker (vnu)
At a glance
- What is it?
- vnu, the Nu Html Checker, decides whether an HTML, CSS, or SVG document matches the specification, from a shell, a build, or a service you host yourself. Its four install routes hide different amounts of Java plumbing, and one of them fetches a runtime before your first command runs.
- Who is it for?
- Adopt vnu when the answer you need is whether a document conforms to the specification, and keep a linter for style. Do not adopt it expecting speed: no throughput or memory figure appears anywhere in the project, so a CI runner has to be sized by measuring your own documents.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 11 days ago.
- What is it written in?
- Mainly Java, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A conformance checker for HTML, CSS, and SVG
The Nu Html Checker decides whether a document matches the specification, and it does that for HTML, CSS, and SVG rather than for one format. It is a Java codebase that reaches you as a jar, a war, and self-contained binaries, so it fits a shell script, a build pipeline, or a server you host yourself. The README points at the public service at validator.w3.org/nu as the model for a deployment of your own.
That is a narrower job than a linter's. A linter tells you a selector is unused; this tells you an element is not allowed where you put it, which is a conformance question with a specification behind it and no room for team taste. The people who need it publish hand-written or generated markup at volume, and so does anyone who has to show a conformance result to an auditor.
The repository layout already hints at the weight of it. Beside the source and build directories sit `htmlparser`, `css-validator`, `schema`, `jing-trang`, `langdetect`, and `galimatias`. Nothing says which of those six can be built or consumed on their own, so treat them as internals until the build instructions say otherwise.
One jar that checks locally or posts to a service
Every route into the checker ends at the same entry point, and the manual page the README names documents the options behind it:
java -jar ~/vnu.jar [OPTIONS]... FILES|DIRECTORY|URL...That single line is the whole contract. You hand it a file, a directory to walk, or a URL to fetch, and it reports what does not conform. The OPTIONS section of `docs/vnu.1.md` holds everything else; no flag is named anywhere in the README itself, so tuning means reading that page first.
The same jar runs two ways. Started as a client it checks what you point it at. Started as a server, with `java nu.validator.servlet.Main 8888`, it becomes an HTTP service exposing a REST API, which lets a build script post a template through it and read the result back as data instead of scraping console text. Port 8888 there is the same one the container image exposes, and the client can be aimed at a remote instance instead of one on your own machine, which is how a shared CI runner avoids starting Java per job.
Four install routes, and the one that fetches Java
Four routes are documented, and they differ in what they ask of your machine.
The Homebrew formula installs a command named vnu:
brew install vnuThe npm route installs a package called `vnu-jar`:
npm install vnu-jarThe Docker route starts a service rather than a command:
docker run -it --rm -p 8888:8888 ghcr.io/validator/validator:latestAnd for Java developers, jbang installs the same checker from an owner and repository path:
jbang app install vnu@validator/validatorWhat sits underneath differs. The pre-compiled Linux, Windows, and macOS binaries carry an embedded Java runtime and need no JVM at all, while `vnu.jar` and `vnu.war` require Java 17 or above, which makes them the awkward pair inside a Node or Python build image. The npm package routes around that. Its manifest declares Node 18 or above and runs `node vnu-java-downloader.js` as a postinstall step, so an install on a machine with no Java still ends up with one. Nothing says where that runtime lands, nor how the download is verified, which is a fair question about a step that runs before your first command.
Where the name latest does double duty across three files
Version identity in this project is a moving target, and the word `latest` carries three different meanings in three places.
The project's numbered builds stopped at 20.6.30 (30 June 2020), and production installs are pointed at a single moving tag instead. The npm manifest that wraps the jar records the string `latest` in its version field, so the package's identity is whatever that tag held at the moment npm fetched it. Then the Docker command asks for the image tag `latest` a third time, in the ordinary container-registry sense of newest build.
The consequences are practical rather than theoretical. A CI job that installs `vnu-jar` twice in a month can run two different checkers, and neither the install log nor the manifest will tell you which one you got. No checksum is published, and no argument for naming a build appears anywhere. If your pipeline has to be reproducible, fetch the jar yourself and control the path, because that is the one route where you can see which file is executing.
The image sets five second timeouts and shows fifteen
The Dockerfile disagrees with itself, and the disagreement lands on the numbers that decide when a slow request dies.
The usage comment at the top of the file shows how to run the image, raising both timeouts to 15:
# -e CONNECTION_TIMEOUT_SECONDS=15 \
# -e SOCKET_TIMEOUT_SECONDS=15 \The image itself then sets both to 5:
ENV CONNECTION_TIMEOUT_SECONDS=5
ENV SOCKET_TIMEOUT_SECONDS=5
ENV BIND_ADDRESS=0.0.0.0So the defaults you inherit are a third of the values the same file advertises. On a checker receiving documents from a slow network that is the difference between a report and a truncated one, and nothing outside the Dockerfile says either variable exists.
Two more lines deserve a second read. `ENV JAVA_TOOL_OPTIONS=""` deliberately blanks a variable the JVM would otherwise announce at every start, keeping the output clean for whatever parses it. The image also installs Debian's `default-jre-headless` and writes a shell wrapper at `/usr/local/bin/vnu` so that vnu behaves like the command-line tool. The default command ignores that wrapper and starts the servlet with the classpath flag instead, passing bind address and both timeouts in as system properties. What you get by default is a service on port 8888, with a CLI sitting next to it in the same image.
Deploying vnu.war means guessing the context root
Running the checker as a shared service inside an existing servlet container takes one file. Drop `vnu.war` into Tomcat's `webapps` directory, the default `appBase` setting, or deploy it through the Manager application.
May 7, 2014 4:42:04 PM org.apache.catalina.startup.HostConfig deployWAR
INFO: Deploying web application archive /var/lib/tomcat7/webapps/vnu.warThat is the log line to look for afterwards. The wrinkle comes next. Tomcat takes the context root from the WAR's filename unless one is set explicitly, so the application answers at a path named after the file rather than one you chose. With HTTP arriving on port 80, that resolves to `http://localhost/vnu/`.
Once it is up, the filter configuration inside the WAR is yours to edit. Open `/WEB-INF/web.xml` with any ZIP-handling program and its example shows how to comment out the inbound size limit outright:
<!--
<filter>
<filter-name>inbound-size-limit-filter</filter-name>
<filter-class>nu.validator.servlet.InboundSizeLimitFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>inbound-size-limit-filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
-->That mapping targets every path the service accepts, so switching the filter off is a small edit that removes the only request-size guard described anywhere.
Build plugins and a review bot move checking off your shell
Running vnu on every commit is one approach. Letting a wrapper or a bot carry the check changes who sees the result and when.
The project maintains three wrappers: a Grunt plugin for HTML validation, a Gulp plugin for HTML validation, and a Maven plugin for HTML validation. Each sits inside a pipeline you already run, so documents get checked during the asset build instead of in a step someone has to remember. The README links all three and stops there. No version, no configuration key and no list of exposed options appears for any of them, so an adopter has to read a plugin's own repository before knowing whether it passes vnu's options through or exposes a fixed subset.
The sharper departure is LMVTFY, which auto-checks JSFiddle and JSBin snippets and turns links in GitHub issue comments into checks. There the report lands in the conversation where the markup was pasted, before anyone merges it. That is a different feedback loop from a red build on a branch, and it trades coverage for immediacy: snippets that never reach a pull request get looked at, while templates that exist only inside a running application do not.
The shell invocation stays the fallback for one-off work, and the REST API covers the case where a custom generator, not a person, is producing the markup.
What the README never states about limits and options
Several things an adopter needs are simply absent, and their absence says more than a summary of what is there.
No performance figure appears anywhere: not a document rate, not a memory footprint, not a recommended concurrency for a batch of pages. Documents can be batch-checked from the command line and automated with a frontend such as the Grunt or Gulp plugins, and that promise is where the text stops. Sizing a CI runner against the checker means measuring it on your own documents, because no published number exists to copy and no note says what sits above the Java 17 floor.
Option coverage is delegated in a single line, to `docs/vnu.1.md`, with a second page, `docs/vnu-server.1.md`, for the server side. Neither is quoted anywhere, so any claim about which flags exist would be guesswork. That matters most for the first two questions every adopter asks: whether a class of message can be ignored, and whether a document can be checked in part rather than whole. Neither is answered.
The code is MIT licensed and the last push landed on 2026-10-06, so the project is neither abandoned nor versioned in a way that records what changed. Keep your own record of which checker a given pipeline ran, because the package manifest will not keep it for you.
Editorial conclusion
Adopt vnu when the answer you need is whether a document conforms to the specification, and keep a linter for style. Do not adopt it expecting speed: no throughput or memory figure appears anywhere in the project, so a CI runner has to be sized by measuring your own documents. Before wiring it into a build, check which checker your installer actually fetched, since the manifest records a moving tag rather than a number, whether Java 17 or above resolves cleanly on that runner, and whether a whole document is the only unit it will accept.
Frequently asked questions
How do I install the Nu Html Checker (vnu) without setting up Java myself?
Use the pre-compiled Linux, Windows, or macOS binaries, which carry an embedded Java runtime, or take one of the packaged routes. The npm package runs a Java downloader at postinstall, and the Docker image installs `default-jre-headless` on its own. Only `vnu.jar` and `vnu.war` need Java 17 or above already present.
What exactly does vnu validate besides HTML?
HTML documents, CSS stylesheets, and SVG images, and it does so through the command line, the REST API of a running service, and the browser-facing checker alike.
Can I run vnu as a service for my own team instead of calling a public one?
Yes. Deploy `vnu.war` into a servlet container such as Tomcat, or start the standalone server with `java nu.validator.servlet.Main 8888`. Every deployment exposes a REST API, and the bundled client can be pointed at a remote instance anywhere on the Web.
Can vnu ignore a particular error code or check only part of a document?
The README answers neither question. All option detail is delegated to `docs/vnu.1.md`, and the server options to `docs/vnu-server.1.md`, and neither page is reproduced, so the filters available cannot be read from the project documentation itself.
Is the vnu-jar npm package tied to a fixed build?
No. Its manifest sets the version field to the string `latest`, and the project's last numbered build was 20.6.30 (30 June 2020). Two installs can therefore produce different checkers, so pin the jar yourself when a pipeline has to be reproducible.
How fast is vnu, and how many documents can one process handle?
The README gives no throughput, memory or timing figure for any of the routes, and names no concurrency guidance. The only numbers available are the ones you produce by measuring your own documents.
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/validator-validator)Was this useful?