clojure/clojure · error · RuntimeException
No reader function for tag
Error message
No reader function for tag ${tag} What it means
When reading a tagged literal `#tag value`, the reader looks up `tag` first in the `data_readers` map (from *data-readers* and data_readers.clj on the classpath), then in `*default-data-reader-fn*`. If neither provides an IFn, it throws RuntimeException "No reader function for tag <tag>". This keeps unknown tags from being silently ignored, since the value cannot be constructed.
Solutions
- Bind *data-readers* with a mapping for the tag: `(binding [*data-readers* {'mytag my-fn}] (read-string s))`.
- Add a data_readers.clj (or data_readers.cljc) resource on the classpath mapping the tag symbol to a reader function.
- Set *default-data-reader-fn* to a fallback handler for unknown tags.
- Require the namespace that registers the tag before reading (e.g. via :require in your ns).
Example fix
// before
(read-string "#mytag {:a 1}")
;; after
(binding [*data-readers* {'mytag (fn [v] (->MyTag v))}]
(read-string "#mytag {:a 1}")) Defensive patterns
Strategy: try-catch
Validate before calling
(defn tag-known? [tag]
(or (contains? *data-readers* tag)
(some? *default-data-reader-fn*))) Try / catch
(try
(read-string s)
(catch RuntimeException e
(if (re-find #"No reader function for tag" (.getMessage e))
(do (require '[my.ns.readers]) (read-string s))
(throw e)))) Prevention
- Register all custom tags in data_readers.clj on the classpath
- Require the namespace that defines data readers before reading external data
- Provide a *default-data-reader-fn* fallback when reading third-party edn
- Document tag schemas alongside data files
When it happens
Trigger: Reading `#mytag {...}` where `mytag` has no registered data reader: not in *data-readers*, no data_readers.clj on classpath, and *default-data-reader-fn* is nil; also loading an edn/serialized file produced by a different project with custom tags.
Common situations: Sharing edn/data files across projects where the consumer lacks the producer's data readers; forgetting to require the namespace that registers the reader; tag renamed between library versions.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
AI-assisted analysis of clojure/clojure@f3b143341d (2026-09-09).
Data as JSON: /api/errors/72c6be3d79cb0245.
Report an issue: GitHub.
Appendix: source
Thrown at src/jvm/clojure/lang/LispReader.java:1447
} else {
return sym.getName().contains(".") ? readRecord(form, sym, opts, pendingForms) : readTagged(form, sym, opts, pendingForms);
}
}
private Object readTagged(Object o, Symbol tag, Object opts, Object pendingForms){
ILookup data_readers = (ILookup)RT.DATA_READERS.deref();
IFn data_reader = (IFn)RT.get(data_readers, tag);
if(data_reader == null){
data_readers = (ILookup)RT.DEFAULT_DATA_READERS.deref();
data_reader = (IFn)RT.get(data_readers, tag);
if(data_reader == null){
IFn default_reader = (IFn)RT.DEFAULT_DATA_READER_FN.deref();
if(default_reader != null)
return default_reader.invoke(tag, o);
else
throw new RuntimeException("No reader function for tag " + tag.toString());
}
}
return data_reader.invoke(o);
}
private Object readRecord(Object form, Symbol recordName, Object opts, Object pendingForms){
boolean readeval = RT.booleanCast(RT.READEVAL.deref());
if(!readeval)
{
throw Util.runtimeException("Record construction syntax can only be used when *read-eval* == true");
}
Class recordClass = RT.classForNameNonLoading(recordName.toString());
boolean shortForm = true;View on GitHub (pinned to f3b143341d)