# dnsjava implements DNS in Java with DNSSEC validation and TSIG

> dnsjava is a BSD-3-Clause Java implementation of the DNS protocol supporting almost all record types, zone transfers, dynamic updates, a cache, an authoritative server, TSIG, DNSSEC and EDNS0. It suits JVM teams that need DNS as a library rather than a resolver process, and it is a protocol library with no built-in policy.

**dnsjava/dnsjava** — dnsjava - an implementation of the DNS protocol in Java

- Repository: https://github.com/dnsjava/dnsjava
- Stars: 1,087 · Forks: 269
- Language: Java
- License: BSD-3-Clause
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/dnsjava-dnsjava

## A protocol library, not a resolver with opinions

dnsjava is an implementation of the DNS protocol in Java. The README lists what that covers: it supports almost all defined record types, including the DNSSEC types, and unknown types; it can be used for queries, zone transfers and dynamic updates; it includes a cache which can be used by clients and an authoritative only server; it supports TSIG authenticated messages, DNSSEC verification and EDNS0; and it is fully thread safe.

That list describes a protocol toolkit rather than a resolver. There is no recursive resolver with a root hints file and no caching policy, because the cache here is something a client uses, not something a server hands out. A consumer of this library decides what to query, which types to request, and whether to trust the answer.

The record type coverage is the part that distinguishes it from a general purpose HTTP style client. Almost all defined types are supported, and unknown types are handled too, which means a record type added to the DNS after the library was written still round-trips rather than throwing. For a tool doing zone transfers or debugging a delegation, that matters more than query speed.

The supporting features are the ones a serious deployment needs. TSIG gives you transaction signatures for authenticated zone transfers and updates. DNSSEC verification is a first-class feature rather than a plugin. EDNS0 is supported, which is where things like the client subnet option and the larger UDP payload size live. And the thread safety claim matters because the cache and the resolver are the parts most likely to be shared between request threads in a server.

The library is on Maven Central as dnsjava/dnsjava, with Javadocs published on javadoc.io, and the README points to an EXAMPLES.md file for the basics.

## System properties configure resolvers, ports, timeouts and hardening

Much of dnsjava's behaviour is set through Java system properties, and the README documents them in a long table with defaults and examples. A few are worth picking out because they change observable behaviour.

The resolver configuration comes in a base and a fallback variant, with dns.server for resolving and dns.fallback.server for a comma separated list of DNS servers to use for resolving. The value can be IPv4 or IPv6 addresses or hostnames, which are resolved using Java's built in DNS support. The example given is 8.8.8.8,[2001:4860:4860::8888]:853,dns.google, which shows the bracketed IPv6 form and a port. Alongside it, dns.search sets comma separated search paths and dns.ndots sets the threshold for the number of dots which must appear in a name given to resolve before an initial absolute query is made, defaulting to 1.

The UDP ephemeral port range is configurable, with dnsjava.udp.ephemeral.start and dnsjava.udp.ephemeral.end, defaulting to 49152 on most systems and 32768 on Linux, and 65535 or 60999 on Linux respectively. There is also dnsjava.udp.ephemeral.use_ephemeral_port, which enables an OS-assigned ephemeral port for UDP queries, and the README flags it in three words: enabling this option is insecure, do not use it. The reason is that a fixed source port range lets an off-path attacker observe the port and guess the DNS message ID.

The secure_random_algorithm property names the SecureRandom algorithm used to generate UDP source ports and message IDs, with SHA1PRNG as the example. That is the other half of the same defence, and setting it explicitly is a reasonable hardening step for a resolver that talks to the public internet.

Two options control how much the library trusts what it is told. dnsjava.lookup.use_hosts_file decides whether the system's hosts file is consulted before resorting to a resolver, and dnsjava.harden_unknown_additional hardens against unknown records in the authority and additional sections, where disabling it causes such records to be copied from the upstream and presented to the client together with the answer.

## The DNSSEC limits are deliberately set to blunt KeyTrap

The DNSSEC options section of the README is the most security-relevant part of the documentation, and two of its defaults are there to answer a specific attack.

The first is dnsjava.dnssec.max_validate_rrsigs, the maximum number of RRSig records to validate until the response is considered bogus, defaulting to 8. The second is dnsjava.dnssec.max_ds_match_failures, the maximum number of DS records to validate until the response is considered bogus, defaulting to 4. The README says both are limited to avoid the KeyTrap vulnerability, CVE-2023-50387.

The logic of that limit is worth understanding rather than just accepting. Without a cap, a DNSSEC validating resolver can be forced to perform a large number of signature validations on a single response, each of which is a public key operation, so a crafted zone can turn one query into a lot of CPU work. The fix is to treat a response that requires more validations than the cap as bogus, which bounds the work per response at the cost of occasionally rejecting a legitimately large signed response.

The other DNSSEC properties control algorithm policy. dnsjava.dnssec.algorithm.ID enables or disables a DS or DNSKEY algorithm, and dnsjava.dnssec.digest.ID enables or disables a DS digest algorithm, referencing RFC 8624 for recommended values, and the README notes that algorithm number 1, RSAMD5, is disabled and cannot be enabled with this property. dnsjava.dnssec.algorithm_rsa_min_key_size sets a minimum RSA key size in bits, defaulting to 1024, and dnsjava.dnssec.harden_algo_downgrade prevents algorithm downgrade when a zone's DS records advertise several algorithms.

The key cache is bounded too, with dnsjava.dnssec.keycache.max_ttl defaulting to 900 seconds and dnsjava.dnssec.keycache.max_size defaulting to 1000 entries. The trust anchor comes from dnsjava.dnssec.trust_anchor_file, a file formatted like a DNS zone master file that can only contain DS or DNSKEY records, with /etc/dnssec-root-anchors as the example.

## The resolver internals have their own knobs and a hosts file loader

Beyond the DNSSEC and resolver settings, there are properties that govern how lookups are performed and how the library reads the system.

dnsjava.lookup.max_iterations is the maximum number of CNAMEs to follow in a chain, defaulting to 16, with 20 given as an example. That is a loop guard: a misconfigured zone with a CNAME cycle would otherwise be followed until the JVM ran out of stack or time.

dnsjava.hostsfile.max_size_bytes sets the size of the hosts file to be loaded at a time, in bytes, defaulting to 16384, with 1000000 as the example. The wording, loaded at a time, suggests the file is read in chunks rather than as a single document, which is a sensible approach for a file that a container's hosts file can grow into.

The NIO layer has two properties. dnsjava.nio.selector_timeout sets the selector timeout in milliseconds, with a default and maximum of 1000 and a minimum of 1, and dnsjava.nio.register_shutdown_hook registers a shutdown hook for automatic termination of NIO. The documentation for the second one is worth reading twice: if it is disabled, the nio selector thread will not automatically clean up on JVM termination, which means a JVM that exits without it leaves a thread and its resources in an unknown state. That is a real production concern in a service that reloads configuration or is embedded in a container.

The configuration provider itself can be tuned. dnsjava.configprovider.skipinit disables static ResolverConfig initialisation, and dnsjava.configprovider.sunjvm.enabled enables a reflection based DNS server lookup, which the README points to a limitations section for. The existence of a switch for reflection based lookup is a signal about how the library finds the system resolver on different JVMs, and the fact that it is off by default suggests it is a fallback rather than the normal path.

## The dnsjava.options list is a second, programmatic configuration surface

Some settings are not system properties but option pairs, set through the dnsjava.options system property as a comma separated key-value list, or programmatically through the Options class, where the README refers you to the Javadoc for details.

The example value given is BINDTTL,tsigfudge=1, and the table lists the keys with their types and defaults. BINDTTL is a boolean that prints TTLs in BIND format, multiline is a boolean that prints records in multiline format, and noPrintIN is a boolean that suppresses printing the class of a record.

This is a debugging and diagnostics surface rather than a runtime behaviour one. The options affect how records are printed, which matters when you are reading a zone dump or a packet trace and want the output in a form you can paste into a bug report. The tsigfudge option adjusts the tolerance window for TSIG timestamps, which is a runtime behaviour change and the one in this list that affects protocol semantics rather than formatting.

The two-configuration-surface design is worth noting as a pattern. System properties for the things an operator needs to set without touching code, option pairs for the things a developer sets in a debugger or a test. A library that put everything in one place would make the operational knobs as awkward to reach as the diagnostic ones.

## BSD-3-Clause, regular releases, and a Changelog in the repository

The licence is BSD-3-Clause, with the LICENSE file in the repository as the authoritative text, and this is a licensing statement rather than legal advice. A permissive licence with no copyleft obligations matters here because dnsjava tends to be embedded in larger server distributions.

The release record is steady. The recent versions are v3.6.5-1 on 2026-05-23, v3.6.4 on 2026-01-18 and v3.6.3 on 2025-01-26. The repository is not archived and the last push was on 2026-10-15, so the work is ongoing even though the last tagged release is a few months old.

The repository layout is a Maven project with a src/ directory and a pom.xml, plus a checkstyle/ directory, which means style is enforced as part of the build rather than left to review. There is a Changelog file at the root rather than only a GitHub releases page, and USAGE.md and EXAMPLES.md as separate documents from the README. The README itself is README.adoc, in AsciiDoc, with the system property tables written in the AsciiDoc table syntax, and there is a TODO.dnssec.md file that lists the remaining DNSSEC work.

That TODO file is a good honesty signal for a security-relevant library, since it tells you which parts of DNSSEC are not finished rather than leaving you to discover them. There is also a sonar-project.properties file for SonarQube analysis, and a .github directory with a build workflow.

The build has a codecov badge for the master branch, so coverage is measured continuously rather than asserted, which for a library whose job is to parse a protocol that attackers also parse is the metric that matters most.

## The alternative is the platform resolver, and it is the right answer most of the time

The comparison that matters is with what the JVM already gives you, because dnsjava's list of capabilities is long and the platform's is short but sufficient for most applications.

The JVM ships a resolver. InetAddress.getByName consults the system configuration, honours the hosts file, uses the platform cache on most operating systems, and requires no dependency. If your service needs to turn a hostname into an address and nothing more, using dnsjava to do it is a more complex solution to a solved problem.

The difference in approach shows up in the cases dnsjava covers that the platform does not. The platform resolver is a stub that forwards to the operating system's configured nameserver, so you cannot ask it for a specific record type, you cannot ask it for a DNSSEC validated answer with the validation chain exposed, you cannot perform a zone transfer, you cannot send a dynamic update, and you cannot act as an authoritative server. dnsjava gives you the protocol, and with it the ability to be a server, a secondary, or a validator.

The trade-off is that the protocol is where the complexity lives. A stub resolver hands you an address or an exception. A protocol library hands you a message you have to interpret, including the case where the answer is a CNAME chain, an NXDOMAIN, a truncated response that needs a TCP retry, or a signed response whose validation failed. dnsjava handles the retry and the chain following, and its configuration properties are the surface where that behaviour is tuned, but you are still the one deciding what to do with a validation failure.

The security comparison is the sharpest one. A stub resolver trusts whatever the operating system's resolver returns, and on a typical deployment that means trusting the network path unless the operator has configured DNSSEC validation at the OS level. dnsjava can validate in process, which is the point of the DNSSEC options and of the KeyTrap limits. If validation in your own process is a requirement, that is a reason to use this library rather than the platform one, and it is the only reason that reliably justifies the dependency.

## Conclusion

Adopt dnsjava if your Java service has to speak DNS directly, for zone transfers, dynamic updates, DNSSEC validation, or a resolver you control rather than the JVM's own stub resolver. Do not adopt it if you only need hostname lookups, because the platform resolver is simpler and already integrated with the JVM's caching and the operating system. Before you deploy, read the DNSSEC options table carefully, in particular dnsjava.dnssec.max_validate_rrsigs and dnsjava.dnssec.max_ds_match_failures, since both default values exist to bound the KeyTrap vulnerability, and raise them only if you understand what you are reopening.

## FAQ

### What can dnsjava do that a normal DNS lookup cannot?

It can perform zone transfers, dynamic updates, TSIG authenticated messages, DNSSEC verification and EDNS0, it can run an authoritative only server, and it supports almost all defined record types including the DNSSEC types and unknown types. It is fully thread safe and includes a cache for client use.

### How do I configure the DNS servers dnsjava uses?

The dns.server and dns.fallback.server system properties take a comma separated list of servers, which can be IPv4 or IPv6 addresses or hostnames. The README gives the example 8.8.8.8,[2001:4860:4860::8888]:853,dns.google, and dns.search and dns.ndots configure the search path list and the dots threshold.

### Why are the DNSSEC validation limits set low by default?

dnsjava.dnssec.max_validate_rrsigs defaults to 8 and dnsjava.dnssec.max_ds_match_failures defaults to 4 because the README says both are limited to avoid the KeyTrap vulnerability, CVE-2023-50387, which lets a crafted zone force excessive signature validations on a resolver.

### Which UDP source port range does dnsjava use?

The dnsjava.udp.ephemeral.start and dnsjava.udp.ephemeral.end properties set the range, defaulting to 49152 to 65535 and to 32768 to 60999 on Linux. The dnsjava.udp.ephemeral.use_ephemeral_port option exists but the README marks it insecure and says not to use it.

### How do I set DNSSEC trust anchors in dnsjava?

The dnsjava.dnssec.trust_anchor_file property names a file formatted like a DNS zone master file that can contain only DS or DNSKEY records, with /etc/dnssec-root-anchors given as an example. The key cache size and TTL are set with dnsjava.dnssec.keycache.max_size and dnsjava.dnssec.keycache.max_ttl.

## Sources

- [dnsjava/dnsjava on GitHub](https://github.com/dnsjava/dnsjava)
- [Issues](https://github.com/dnsjava/dnsjava/issues)
- [License: BSD-3-Clause](https://github.com/dnsjava/dnsjava/blob/master/LICENSE)
- [README](https://github.com/dnsjava/dnsjava/blob/master/README.md)
- [Releases](https://github.com/dnsjava/dnsjava/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/dnsjava-dnsjava
