beginners 2026-07-26

Hello. I'm on Linux Debian and trying to get through creating a simple webpage by following the ClojureScript Quick Start Guide at https://clojurescript.org/guides/quick-start, but I'm getting this error: Execution error (UnsupportedClassVersionError) at java.lang.ClassLoader/defineClass1 (ClassLoader.java:-2). com/google/javascript/jscomp/CompilerOptions$LanguageMode has been compiled by a more recent version of the Java Runtime (class file version 65.0), this version of the Java Runtime only recognizes class file versions up to 61.0 It seems like maybe my JDK version is too much newer than my JRE version, but I don't know what I can do about that.

61 = JDK 17, 65 = JDK 21, I think. What does java -version show on your machine?

And what does clojure -version show?

Also, I know you said Linux @factorhengineering but are you using the standalone cljs.jar per the Windows instructions?

I can repro, using the standalone JAR and JDK 17:

(!2037)-> $OPENJDK17_HOME/bin/java -cp cljs.jar:src cljs.main -M --repl
Exception in thread "main" java.lang.UnsupportedClassVersionError: com/google/javascript/jscomp/CompilerOptions$LanguageMode has been compiled by a more recent version of the Java Runtime (class file version 65.0), this version of the Java Runtime only recognizes class file versions up to 61.0
So, I think you are trying to use JDK 17 to run this when the instructions say you need JDK 21 (I've confirmed that using JDK 21 or later works).

Clojure itself runs on JDK 8 or later, right up to Clojure 1.13.0 Alpha 5 which requires JDK 17, so you're fine for running Clojure. It's only the standalone cljs.jar that requires JDK 21.

java -version yields openjdk version "17.0.19" 2026-04-21 OpenJDK Runtime Environment (build 17.0.19+10-1-deb12u2-Debian) OpenJDK 64-Bit Server VM (build 17.0.19+10-1-deb12u2-Debian, mixed mode, sharing)

I am in Linux only, not WSL/WSL2, so I am following the Linux instructions and not the Windows instructions.

If my JDK version, 17(.0.19) is so far behind (21), then why does it show a date that is so recent (2026-04-21)? Very confusing. I've tried to update it, but it never goes to any higher version number than 17.

According to https://linuxcapable.com/how-to-install-openjdk-25-on-debian-linux/, it looks like my system refuses to take Java beyond V17 because I have Debian 12, and if I were to switch to Debian 13 then it would update Java as far as V21. It is bonkers for me to have to update my whole OS just to get something working that I successfully ran in the past (and am just trying to get back into now), but I suppose I'll give that a try.

That same web article suggests a second option would be for me to not switch from Debian 12 to Debian 13, but instead install JDK V25 "Through the Adoptium APT repository". That's another can of worms I'd rather not have to open, though. This whole time I don't understand why or how the compilation is being done in a JDK version higher than the V17 I have on my computer. How is that happening? Can I just get it to compile with V17 or similar?

By the way, I only see Quick Guide instructions specifying JDK <= 21 for Windows, not for Linux.

Or maybe instead of the 'Adoptium' method/source, I could just manually install OpenJDK V26 ("GA") after downloading it from here? https://jdk.java.net/26/ I'm not sure what to do with the .tar.gv file though.

And again, do I really need to push for a higher JDK version than what my current OS naturally caps out at? Can I not just stop the compilation (by Clojure?) from being done by such a high JDK version number? (And how is it even using such a high version number I evidently don't have??)

You can install any JDK locally by using https://sdkman.io/

@factorhengineering Clojure does not require such a high version of the JVM if you don't want to upgrade past your OS provided 17. The problem is that you are trying to use a version of Clojure specifically created to be run on 21. Have you tried following the instructions on installing Clojure specifically on Linux? • https://clojure.org/guides/install_clojure#_linux_instructions • https://www.youtube.com/watch?v=544xOAIg3y0

@pithyless This is about ClojureScript, not Clojure.

The ClojureScript getting started says it requires JDK 21, but my reading is it is Windows-only, and on Linux, you should be able to still use JDK 17...

@factorhengineering You can follow the Adoptium/Temurin Linux Deb instructions to install "any" JDK—but it requires a few setup steps to do it via apt-get install: https://adoptium.net/installation/linux#deb-installation-on-debian-or-ubuntu <-- this is how I install various JDKs on Ubuntu

Regarding versions and dates: older JDKs get updates so they can have recent dates. JDK 25 is the most recent LTS (Long Term Support). Clojure only requires 17+. It seems ClojureScript requires 21+. JDK 25 is the current LTS (Long Term Support) version. JDK 26 has been out for a while. JDK 27 is available in "early access" (direct from https://jdk.java.net/27/ as a .tar.gz).

TIL: 1.12.42 released in May 2025 > Google Closure Compiler has been updated to v20250402. This change makes Java 21 a requirement for ClojureScript. https://clojurescript.org/news/2025-05-16-release

Yeah... I wasn't sure when that had happened. But given JDK 25 LTS has been available for a while, a JDK 21+ requirement isn't really "a big deal".

We have JDK 26 in production, and JDK 27 ea in local testing.

Just one more note from my side: I recommend exploring one of the several ways of separating local dependency on the JVM from the global OS version (personally I favor the sdkman shim, but several ideas have been posted in this thread). Once developing in the JVM ecosystem you may want to switch and jump between versions (or test multiple) and you should not feel forced to be stuck with your OS default. And your system packages were designed and tested with a specific JVM version so it would be a shame to accidentally create more instability in your OS. This is no different than someone developing in ruby or python and using one of several shims to decouple their ruby or python runtime from the system default (which often lags behind).

I'm not sure what you mean by "system packages". Is that packages related to running of the OS itself, or packages related to running Clojure and ClojureScript?

I'll offer a different approach: I download various JDK versions as .tar.gz files. On Linux, I pick a folder where I'm going to have all my JDKs installed (this could just be ~/jdks). In that folder, run tar xfz /path/to/the.tar.gz to unpack each downloaded JDK. Then I add env vars to my ~/.profile file (I happened to put all the JDKs in a fresh /Developer folder since that's what I used to do on a Mac, years ago):

export OPENJDK8_HOME=/Developer/jdk8u332-b09
export OPENJDK11_HOME=/Developer/jdk-11.0.28+6
export OPENJDK17_HOME=/Developer/jdk-17.0.16+8
export OPENJDK18_HOME=/Developer/jdk-18.0.2+9
export OPENJDK19_HOME=/Developer/jdk-19.0.2+7
export OPENJDK20_HOME=/Developer/jdk-20.0.1+9
export OPENJDK21_HOME=/Developer/jdk-21.0.1+12
export OPENJDK23_HOME=/Developer/jdk-23
export OPENJDK24_HOME=/Developer/jdk-24.0.1+9
export OPENJDK25_HOME=/Developer/jdk-25
export OPENJDK26_HOME=/Developer/jdk-26
export OPENJDK27_HOME=/Developer/jdk-27
and then I pick which one I want as a default:
export JAVA_HOME=$OPENJDK27_HOME
export JAVA_CMD=$JAVA_HOME/bin/java
If I want a different JDK when running clojure (or clj), I do something like this:
JAVA_CMD=$OPENJDK21_HOME/bin/java clojure ...whatever arguments...

➕ 1

This allows me to easily test something against a dozen different JDKs, and accommodate projects that have different JDK requirements.

I pretty much never use my OS packages for development. Except for something "as global as it gets", like Docker or Podman. And even that sometimes results in issues... So JDK, Python, PostgreSQL, Node, etc. - they all come from third-party package managers, manually extracted archives, OCI images. Never OS packages - that's always trouble.

👍🏻 1
💯 1

> I'm not sure what you mean by "system packages". Is that packages related to running of the OS itself Yes - specifically programs and libraries installed by the system (in your case apt) that expect a Java runtime (and presumably are linked and tested against a dependency on the JVM that is packaged with your Debian version).

When you all refer to which Java versions you are using "for development" or "locally", do you just mean for the encoding/compilation, or for the running as well?

Both „compilation” and „runtime” for code you control - ie the app you are working on. Since that is very blurry line in the case of JVM and bytecode.

In my case it was compiling with Java 21 (or maybe higher) with no control by me (if I stick to using the latest recommended ClojureScript version). So really what I would be changing would be the runtime side? Making it so that when I run a program I've written I can select for it to run using something other than the default Java in my OS (Java 17)?

In my case it was compiling with Java 21
It was not compiling, CLJS is distributed in an already compiled form. (Well, maybe not CLJS itself, maybe not fully, but IIRC definitely some of its dependencies, probably Google Closure Compiler.)

All of my examples are about running code (note that Clojure compiles code on demand, based on the JDK you are using to run it).

Without getting into the weeds… yes @factorhengineering. If there are system installed packages installed expecting java, you want them to basically run java -jar …. and find the default OS v17. Whereas when you have a running shell, you want to modify the classpath and path in such a way that running java -jar … will run your alternative v21 or whatever version you want.

There is some complexity and indirection, but clojure is just a library that runs java -jar and converts CLJ syntax to Java and runs it

and similarly clojurescript is a clojure library that uses java -jar to convert CLJS code to JS

Hence my approach of using env vars so I can do $OPENJDK21_HOME/bin/java -jar ... or JAVA_CMD=$OPENJDK27_HOME/bin/java clojure ... 🙂

If it helps: it's the equivalent of changing and prefixing to $PATH to run a different version of a command in linux

In fact, if that was not obvious before: when you think you're "compiling" or building clojure or clojurescript - you're in fact just running some scripts that internally run java -jar with access to the clojure or clojurescript library, that parses your code and generates JVM or JS code. And then that generated code gets saved to disk and/or run immediately.

(Obviously I'm making some simplifications with that statement, but maybe that makes it easier to understand the symbiotic relationship between Java and both Clojure and ClojureScript)

OK, I'll consider this hurdle not a prompt to update my system/OS/global Java version, but a prompt to separate development/local Java version from system/OS/global Java version. Well, at least separate the part that isn't already separate due to calls to a Java I didn't install.

So I've downloaded a copy of JDK 26.0.2, and extracted that somewhere, and I can start, when I need Java for programming purposes, instead of calling java calling ~/Documents/programming/JDKs/jdk-26.0.2/java What I don't get is how to apply that to ClojureScript. The command I've been running doesn't include a call to java, so how can I use this new separate-from-OS-java approach for this? clj -M --main cljs.main --compile program-name.core --repl

JAVA_CMD=~/Documents/programming/JDKs/jdk-26.0.2/java clj -M --main ...

That sets an env var just for executing that command and the clojure script (which clj invokes) looks for $JAVA_CMD to use as the way to run java.

That's what I was showing above -- I created an env var for each installed JDK so I can easily run commands using each of those JDKs.

(if the above doesn't work you might need JAVA_CMD=$HOME/Documents/programming/JDKs/jdk-26.0.2/java -- I'm a bit surprised that doesn't end in jdk-26.0.2/bin/java to be honest)

Ah, when I read your

JAVA_CMD=$OPENJDK21_HOME/bin/java clojure ...whatever arguments...
above, I saw that as just calling java. I missed the variable assignment part.

Oh, and there is a /bin/ in there. Good catch.

Just in case - if you use an IDE, it probably has a way of integrating that knowledge. E.g. with Cursive I don't need to even remember where my Java is once I set a particular version up. I can simply select an appropriate version from a drop-down menu.

I just changed by dependency back from the JDK-17-friendly 1.11.132 to the Quick-Start-example 1.12.145 and I ran JAVA_CMD=~/Docu ments/programming/JDKs/jdk-26.0.2/bin/java clj -M --main cljs.main --compile program-name.core --repl ...and it works! I wanted to try it that way because I don't quite understand the need for environment variables other than JAVA_CMD.

To answer your question, I'm not using an IDE for now. I'm just editing the files with Vi, and later I'll most likely be using EMacs (which I'm familiar with just from using org-mode, but I also have heard good things about it having CIDER).

> I don't quite understand the need for environment variables other than JAVA_CMD It's just a convenience to avoid writing out that long path in full, and to make it easier to switch between JDKs in future.

Is there any reason not to include the /bin/ in those paths for the individual JDK versions?

Some commands respect JAVA_HOME, some respect JAVA_CMD, so I set things up to be "home" based, and then JAVA_CMD=$<somejdkhome>/bin/java for each of them.

Does that mean even if I am only doing this for Java 26, I will need to use not just JAVA_CMD (in the way I just did) but also JAVA_HOME sometime?

For clojure you only need to worry about JAVA_CMD.

I just have a lot of JDKs and have worked with a lot of Java-based stuff 🙂

Haha. OK. I may yet set up the JAVA_HOME variable the way you did (sometime after I understand it enough for permanently setting environment variables via editing a .profile file not to feel like a magical incantation but also not feel like something I'll later forget I've done). For now I don't mind keeping on with running the command I did starting with JAVA_CMD. I also like keeping as much as I can explicit, at least for now while all of this is volatile in my mind.

And if you're really curious, my ~/.config/clojure files: https://github.com/seancorfield/dot-clojure and my VS Code / Calva setup: https://github.com/seancorfield/vscode-calva-setup -- maybe bookmark these for later reading 🙂

If you set your JAVA_CMD variable value in your .profile, as opposed to per-use within commands, doesn't that affect Java use globally? Actually, does setting it just within a command, like I did, affect global use?

You can have a global default (in .profile) and still override it per-command (which doesn't change anything, except for that command invocation).

As an illustration:

(!2002)-> JAVA_CMD=$OPENJDK11_HOME/bin/java clojure -M -e '(System/getProperty "java.version")'
Error: LinkageError occurred while loading main class clojure.main
        java.lang.UnsupportedClassVersionError: clojure/main has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 55.0
My default Clojure version is 1.13.0-alpha5 which requires JDK 17 point_up::skin-tone-2 but I can select Clojure 1.12.5 via an alias:
(!2003)-> JAVA_CMD=$OPENJDK11_HOME/bin/java clojure -M:1.12 -e '(System/getProperty "java.version")'
"11.0.28"
(!2004)-> JAVA_CMD=$OPENJDK17_HOME/bin/java clojure -M:1.12 -e '(System/getProperty "java.version")'
"17.0.16"
now back to Clojure 1.13 and JDK 17
(!2005)-> JAVA_CMD=$OPENJDK17_HOME/bin/java clojure -M:1.13 -e '(System/getProperty "java.version")'
"17.0.16"
(!2006)-> JAVA_CMD=$OPENJDK21_HOME/bin/java clojure -M:1.13 -e '(System/getProperty "java.version")'
"21.0.1"
(!2007)-> JAVA_CMD=$OPENJDK25_HOME/bin/java clojure -M:1.13 -e '(System/getProperty "java.version")'
"25"
(!2008)-> echo $JAVA_CMD
/Developer/jdk-27/bin/java
JAVA_CMD stays unchanged globally.

Just to confirm, these lines in your .profile do set the global value though? export OPENJDK25_HOME=/Developer/jdk-25 export JAVA_HOME=$OPENJDK25_HOME export JAVA_CMD=$JAVA_HOME/bin/java

Yes, all those exports set global env vars for my session.

Then <somevar>=<somevalue> my_command will run my_command with <somevar> set to <somevalue> just for that process while it is running.

Great. Thank you!

I've gone through the ClojureScript Quick Start example, which somehow serves up a webpage with the CLJS logo and some text, and then just reflects a programmed console message and a pop-up message. I am wondering where that webpage comes from, as I don't see any .html files referenced anywhere. More importantly, I want to know how to instead use a new index.html file I have created, and have the ClojureScript work on that. Since I don't see how the default webpage is referenced, though, I don't know how to change over to my own .html file.

It makes no sense to me that in the entire Quick Start guide for ClojureScript, at https://clojurescript.org/guides/quick-start, there is not a single reference to HTML files!

That's because it's a quick start for a programming language, not a tutorial on web development with the language. So HTML used there to some extent is a means to an end, not a goal. What you want is described here: https://clojurescript.org/news/2018-03-26-clojurescript-command-line#creating-a-browser-app Might be in some other places as well, but that's where I found it.

I guess the webpage that is served up when one runs the Quick Start program is related to the REPL? If I leave the --repl part out of the command line entry, and change to JAVA_CMD=~/Documents/programming/JDKs/jdk-26.0.2/bin/java clj -M --main cljs.main --compile aa2026-quiz.core --repl to JAVA_CMD=~/Documents/programming/JDKs/jdk-26.0.2/bin/java clj -M --main cljs.main --compile aa2026-quiz.core no webpage comes up at all, though.

The web page is where the compiled code runs. You cannot have a browser REPL session without a browser page being open somewhere.

And you can have a non-browser REPL as well. Specifically, a Node REPL is supported. It doesn't require a browser page.

Just in case, if you're open to using non-built-in tools, I'd also recommend checking out shadow-cljs. It has a much more thorough documentation, one that's suitable for beginners as well. > shadow-cljs provides everything you need to compile your ClojureScript projects with a focus on simplicity and ease of use. https://shadow-cljs.github.io/docs/UsersGuide.html

I gave shadow-cljs a try a long time ago and didn't get very far. This time around I want to start with more basic elements as much as possible, so I can get to understand things better.

👍 1

Thanks for this. https://clojurescript.org/news/2018-03-26-clojurescript-command-line#creating-a-browser-app Good find. It almost seems like that should be the real Quick Start Guide!

A quick start for a browser app - yeah, maybe. :) Not a general quick start where you just want to start playing around with CLJS without caring how and where it's being executed.

Well, at least the first part, with drawing the circle, could be part of the Quick Start.

By the way, I can't get that to work. I don't at all see how to have the program reference my .html file. Right now I have it sitting in .../program-name/resources/

Maybe it's supposed to be in .../program-name/resources/public/ ? I think I saw that somewhere.

You are not wrong, but lots and lots and lots of things could be a part of the quick start. Have you tried putting the HTML file right where you start clj?

Quoting that page: > We’ll want to create a custom index.html for our app in the current directory

I read that but was resisting moving the file. I was looking for a way to have it point to where the file is supposed to be according to the folder structure I saw somewhere before.

That works, though. Good stuff.

At any point in time, there are dozens, if not hundreds, of different moving parts. Even for the simplest programs. If you stray from the outlined path, you are bound to be hit by some of those things. A specific directory structure must be known to whatever serves that HTML file - to the server. If you control the server, you usually control the directory structure, at least to some degree. But if you rely on some server you have little control over, you must do what it requires. In this case, you're relying on cljs.main for serving files, so you must do what it wants, albeit it doesn't seem to be explicitly mentioned that --repl also serves something. The CLI docs have only this:

-s, --serve host:port      Start a simple web server to serve the current
                              directory

If I could just marry the two examples together, now... I'd like to run the original Quick Start program, with the REPL and all (and I see the command for that is different in some ways, like having --main and --compile, but not -m or -w or src or -c or -r), but have it work with my .html file. In the end I should be able to take the .html file and the .js file(s?) it emits and move them and use them independently, shouldn't I, at least by doing the production build option?

A long time ago I managed to use ClojureScript to generate a single .js file that would work with a .html file of mine, and essentially operate on some text fields in that. I don't recall how I did that.

You can do all that, you just have to put things in the right places. cljs.main does not provide any command that makes a self-contained archive/folder with your website, because it doesn't care about websites - what it provides you with (that's not about CLJS, that is) is for convenience only, not because it's a goal by itself. Yes, you can create a single JS file - that's what the --compile option is for. By itself it will create many files, so you have to also supply --compile-opts '{:optimizations :advanced}'. Make sure it's before --compile in the CLI. The result will be in the out dir by default, which is configurable.

Yes, I am totally trying to figure out how to put things in the right places. As for the related question of taking things (after compilation etc.) from the right places, if I compile my program (with advanced optimizations) and don't do any custom configuration of where output things go, where do they go? My /out/ folder is left with a whole lot in it. I was guessing the outputs I would use would be main.js and cljs_deps.js, but I would have expected main.js to refer to the other .js file and it doesn't.

That, above, is just about the outputs, but I really feel like I'm doing something wrong with the inputs and that's the first thing I need to solve. I wish I could remember where I saw (very recently) that index.html is supposed to be put in /resources/ or /resources/public/. In any case I don't recall it being so difficult to compile a single .js file that works on a single .html file years ago.

> if I compile my program (with advanced optimizations) and don't do any custom configuration of where output things go, where do they go? To the out dir. > I wish I could remember where I saw (very recently) that index.html is supposed to be put in /resources/ or /resources/public/ Anything that uses a server that's different from the one built into cljs.main should allow you to do that. Even shadow-cljs allows you to do that, via :asset-path. > In any case I don't recall it being so difficult to compile a single .js file that works on a single .html file years ago. It... isn't that difficult? :) It's just a single command and a specific directory structure, that's it. That's the bare minimum you'd also have with any other tool chain. Granted, the command is not immediately obvious with its :optimizations :advanced (I think you can also use other optimization levels, except :none, but I only know for sure about :advanced).

BTW I'd recommend checking out shadow-cljs even if you don't plan on using it. It's docs are quite thorough and in many areas aren't specific to shadow-cljs, so they're useful for vanilla CLJS as well. A single giant page is certainly easier to search and navigate around than a bunch of pages in different places, sometimes on different websites. And if it describes something that's not a part of vanilla CLJS but that you still need - well, you'll know what to do. :)

I think a big source of confusion for me was never knowing how where the .html for the original webpage from the ClojureScript Quick Start Guide comes from. I still don't know where it comes from (for if I wanted to edit it, for instance), but at least now I it is a purely a product of the REPL and has nothing to do with my ClojureScript program (except maybe my ClojureScript program could act on it? I don't know.). I think what would have made things much clearer to me from the start would have been the following simple statements added near the beginning of the Quick Start Guide: 1. Running a REPL, with the --repl part of a clj command will serve a default .html webpage (and this is because some webpage has to be served in order for a REPL to work). 2. Your ClojureScript program may overwrite part or all of the default REPL webpage through programmatic creation/manipulation of webpage elements. 3. If you would like to have a .html webpage of your own be the starting point and be served (and be what is operated on by your program, and by the REPL if you run a REPL), this needs to be in the root folder for your program. (There is no way to have the clj command look elsewhere for a webpage to serve, and doing so would require other tools.) I've moved my index.html files (and style.css file) up to the root directory for my ClojureScript program and now things are working as expected. Thanks for the info about the basic clj command only being able to serve a webpage in the root folder for the program. I'm also reading about shadow.cljs again as you suggest, and looking out for things that are more widely applicable.

> (except maybe my ClojureScript program could act on it? I don't know.) It can, the quick start page shows an example of that, at the very bottom.

Yes, I actually went back and ran my old hello-world program, with --repl, as usual, to test this. Creating that h2 just seems to blow the default REPL .html page away.

Yeah, that's expected.

I am now trying to see if it can edit that HTML instead of overwriting it. Apparently what I wrote above about that REPL webpage belonging to the REPL...isn't correct? I would have expected one of these commands to open a REPL and serve the REPL webpage, but the first two don't serve anything and the third just throws an error about cljs.main not existing. JAVA_CMD=~/Documents/programming/JDKs/jdk-26.0.2/bin/java clj --repl JAVA_CMD=~/Documents/programming/JDKs/jdk-26.0.2/bin/java clj -M --repl JAVA_CMD=~/Documents/programming/JDKs/jdk-26.0.2/bin/java clj -M cljs.main --repl

I also tried the following, and the 4th one throws the same error as the 3rd, and the 5th one serves up my webpage instead of the REPL one. JAVA_CMD=~/Documents/programming/JDKs/jdk-26.0.2/bin/java clj -M --main --repl JAVA_CMD=~/Documents/programming/JDKs/jdk-26.0.2/bin/java clj -M --main cljs.main --repl

The 5th is the closest. I guess I just need to run it from somewhere other than my current program folder.

clj is Clojure, you need ClojureScript. That's why the first two commands don't work. ClojureScript is just a JAR that Clojure can load and run. That JAR has to be on the classpath of the Java process in order to be found (technically, not the JAR itself but just the cljs.main namespace, but not important now) - that's why the third command fails. That's what the second code block in the quick start guide does - it tells clj what ClojureScript to depend on, and clj can figure out where to get it from and how to add it to the class path.

> The 5th is the closest. I guess I just need to run it from somewhere other than my current program folder. Yes. But that location still has to have deps.edn or it has to specify the CLJS dependency via the clj CLI.

OK, based that, I created a new project folder with just a deps.edn file in it, with just the clojurescript dependency declared in that.

{:deps {org.clojure/clojurescript {:mvn/version "1.12.145"}}}
Then from the root of that program/project folder I ran JAVA_CMD=~/Documents/programming/JDKs/jdk-26.0.2/bin/java clj -M --main cljs.main --repl That just threw an error:
Error building classpath. Cannot invoke "java.io.File.getAbsolutePath()" because the return value of "clojure.java.
io$file.invokeStatic(Object)" is null 
java.lang.NullPointerException: Cannot invoke "java.io.File.getAbsolutePath()" because the return value of "clojure
.java.io$file.invokeStatic(Object)" is null

Hmm. What's the full stack trace?

The rest of the error text?

Error building classpath. Cannot invoke "java.io.File.getAbsolutePath()" because the return value of "clojure.java.
io$file.invokeStatic(Object)" is null 
java.lang.NullPointerException: Cannot invoke "java.io.File.getAbsolutePath()" because the return value of "clojure
.$file.invokeStatic(Object)" is null
       at clojure.tools.deps.edn$io_err.invokeStatic(edn.clj:43) 
       at clojure.tools.deps.edn$read_edn.invokeStatic(edn.clj:66) 
       at clojure.tools.deps.edn$read_deps.invokeStatic(edn.clj:137) 
       at clojure.tools.deps.edn$read_deps.doInvoke(edn.clj:127) 
       at clojure.lang.RestFn.invoke(RestFn.java:413) 
       at clojure.tools.deps.edn$choose_deps.invokeStatic(edn.clj:187) 
       at clojure.tools.deps.edn$create_edn_maps.invokeStatic(edn.clj:192) 
       at clojure.tools.deps$create_edn_maps.invokeStatic(deps.clj:63) 
       at clojure.tools.deps$create_basis.invokeStatic(deps.clj:696) 
       at clojure.tools.deps.script.make_classpath2$run_core.invokeStatic(make_classpath2.clj:93) 
       at clojure.tools.deps.script.make_classpath2$run.invokeStatic(make_classpath2.clj:144) 
       at clojure.tools.deps.script.make_classpath2$_main.invokeStatic(make_classpath2.clj:188) 
       at clojure.tools.deps.script.make_classpath2$_main.doInvoke(make_classpath2.clj:159) 
       at clojure.lang.RestFn.applyTo(RestFn.java:140) 
       at clojure.lang.Var.applyTo(Var.java:707) 
       at clojure.core$apply.invokeStatic(core.clj:667) 
       at clojure.main$main_opt.invokeStatic(main.clj:515) 
       at clojure.main$main_opt.invoke(main.clj:511) 
       at clojure.main$main.invokeStatic(main.clj:665) 
       at clojure.main$main.doInvoke(main.clj:617) 
       at clojure.lang.RestFn.applyTo(RestFn.java:140) 
       at clojure.lang.Var.applyTo(Var.java:707) 
       at clojure.main.main(main.java:40)

There seems to be a bug in clojure.tools.deps.edn that was fixed just today. It doesn't fix the issue itself, but it should print the offending file name. Could you try upgrading clj to the just released 1.12.5.1664 and see that the error becomes?

Also, just in case - have you saved deps.edn with that content?

OK, I've changed my deps.edn dependency line from 1,12.145 to 1.12.5.1664. I ran it again and it gave the same or similar error message.

What was the bug?

Can you paste the error message? Maybe it's different enough to give more details. The bug was about the error message, not about the error itself.

But AFAICT by the error stack trace, Clojure cannot read your deps.edn for some reason. Maybe you saved it mid-edit, so the content is truncated. Maybe there's some other content, somehow. Maybe the file is damaged. Maybe it's not readable by the process due to permissions. And so on.

Error building classpath. Cannot invoke "java.io.File.getAbsolutePath()" because the return value of "$file.invokeStatic(Object)" is null 
java.lang.NullPointerException: Cannot invoke "java.io.File.getAbsolutePath()" because the return value of "$file.invokeStatic(Object)" is null 
       at clojure.tools.deps.edn$io_err.invokeStatic(edn.clj:43) 
       at clojure.tools.deps.edn$read_edn.invokeStatic(edn.clj:66) 
       at clojure.tools.deps.edn$read_deps.invokeStatic(edn.clj:137) 
       at clojure.tools.deps.edn$read_deps.doInvoke(edn.clj:127) 
       at clojure.lang.RestFn.invoke(RestFn.java:413) 
       at clojure.tools.deps.edn$choose_deps.invokeStatic(edn.clj:187) 
       at clojure.tools.deps.edn$create_edn_maps.invokeStatic(edn.clj:192) 
       at clojure.tools.deps$create_edn_maps.invokeStatic(deps.clj:63) 
       at clojure.tools.deps$create_basis.invokeStatic(deps.clj:696) 
       at clojure.tools.deps.script.make_classpath2$run_core.invokeStatic(make_classpath2.clj:93) 
       at clojure.tools.deps.script.make_classpath2$run.invokeStatic(make_classpath2.clj:144) 
       at clojure.tools.deps.script.make_classpath2$_main.invokeStatic(make_classpath2.clj:188) 
       at clojure.tools.deps.script.make_classpath2$_main.doInvoke(make_classpath2.clj:159) 
       at clojure.lang.RestFn.applyTo(RestFn.java:140) 
       at clojure.lang.Var.applyTo(Var.java:707) 
       at clojure.core$apply.invokeStatic(core.clj:667) 
       at clojure.main$main_opt.invokeStatic(main.clj:515) 
       at clojure.main$main_opt.invoke(main.clj:511) 
       at clojure.main$main.invokeStatic(main.clj:665) 
       at clojure.main$main.doInvoke(main.clj:617) 
       at clojure.lang.RestFn.applyTo(RestFn.java:140) 
       at clojure.lang.Var.applyTo(Var.java:707) 
       at clojure.main.main(main.java:40)

I'm really not too worried about this, though. I just seems like the existence of a /src/program_name/ directory with a core.cljs file in it---even if pretty much empty---is required. I just tested going back to my hello world program and commenting out the part of core.cljs that uses .render, and I get the plain REPL webpage.

> I just seems like the existence of a /src/program_name/ directory with a core.cljs file in it---even if pretty much empty---is required. Hold on. So if you create that in that new dir, it works?..

No. I just tested that. Maybe it has to be compiled and all first?

I don't see why, though, if so.

Wow, I just realized that part of my suggested text to clarify the Quick Start Guide > 3. If you would like to have a .html webpage of your own be the starting point and be served (and be what is operated on by your program, and by the REPL if you run a REPL), this needs to be in the root folder for your program. (There is no way to have the clj command look elsewhere for a webpage to serve, and doing so would require other tools.) pretty much exists, just on that default CLJS REPL webpage and not actually in the guide

To provide your own custom page, place an index.html file in the REPL launch directory, starting with this template:

<html>
  <head>
    <meta charset="UTF-8">
  </head>
  <body>
    <script src="/main.js" type="text/javascript"></script>
  </body>
</html>

> No. I just tested that. Maybe it has to be compiled and all first? Alright, then things still make sense. The error is about deps.edn. Maybe not the one in the current directory, but some other, like e.g. ~/.clojure/deps.edn.

Can you run clj by itself?

By the way, I just created another new program/project folder, and did the first hello world steps again, and without ever compiling I ran JAVA_CMD=~/Documents/programming/JDKs/jdk-26.0.2/bin/java clj -M --main cljs.main --repl and it works. Well, it starts the REPL and serves the default REPL webpage. (Entering (js/alert "Hello CLJS!") doesn't do anything, and hasn't been working in at least 3 different programs I've just tested.) I have no idea why this second new program today would work but the first new program would not. I thought they were pretty much identical.

clj outputs

Clojure 1.12.5

> I have no idea why this second new program today would work but the first new program would not. I thought they were pretty much identical. I just checked the files and the are identical except for that change to a newer dependency you suggested, and the first new program today was failing before that change. Very strange for one to work (mostly) and one not to.

> I have no idea why this second new program today would work but the first new program would not. Compare deps.edn. The one where it throws that long exception must be broken in some way.

Exact same except for the mvn version you suggested I change, and errors started before that.

For the first program, the trailing " on the first version number was missing, though I don't know if that was the case before bumping up the version number. Anyway, there is still an error now, but a different one:

Error building classpath. The following artifacts could not be resolved: org.clojure:clojurescript:jar:1.12.5.1664 (absent): Could not find artifact org.clojure:clojurescript:jar:1.12.5.1664 in central ()

Alright, so deps.edn was broken. That version that I mentioned that it now complains about was for clj only - not for CLJS.

Clojure and ClojureScript are different, independent things. You can have a new CLJS on an old CLJ and vice versa.

Even running just clj from that first program's directory throws the same error. I didn't know the directory the CLI was in would matter, since I can run clj anywhere (and if I run it elsewhere I get 1.12.5).

> Even running just clj from that first program's directory throws the same error. If the error is the same, then the root cause is the same. :) Most likely. Depending on what "same error" means, it's either that broken deps.edn file or a wrong CLJS version.

You're saying the clj version is not different depending on the directory I'm in when I run it.

clj uses a specific default Clojure version that's hard-coded into it. But if you specify a different Clojure version in deps.edn in the current directory from which you're running clj then that version will be used instead.

And, just to reiterate, clj and ClojureScript are completely different independent things. CLJS is just a dependency here.

But all my deps.edn files specify only a ClojureScript version, not a Clojure version, yes?

If you were following the guide then yes. So the Clojure version would then be dictated by the version of the clj tool.

I tested reverting the dependency version number in the first program, and now it works. Makes sense since the two new programs are completely identical now, and that seems to prove the error was only ever the missing trailing double quote mark. ...and then the new error was just that it didn't like 1.12.5.1664.

So all makes sense now, it seems?

All except the newest strangeness of the js/alert thing not working. I know it produced a toast/popup in the past.

Well now it just decided to start working for me too. 😕 Oh well. Maybe I had multiple tabs of REPL webpages open or something.

OK, so I originally wanted to get back to a basic program that served the REPL webpage and didn't blow it away because I wanted to see if I could manipulate it. Well I tried to add something to it programmatically from the REPL and it didn't seem to do anything (even with a reload, no that I thought that would help). I tried (.createElement js/document "p") (.createTextNode js/document "This is text added to newly created paragraph.")

If you close the tab that was opened by a REPL process, that process will not have any runtime where the code is being executed. But it should start working again without having to restart the process if you simply open the same address again or undo closing the tab. Those forms that you're executing create an element and a text node. But they do not attach them to the document - that's a separate step.

I just keep closing the browser tab, and hitting [Ctrl]+[c] on the REPL to close them both.

No need for that. :) Doesn't hurt of course if you keep restarting the REPL, but doesn't achieve much as well if you don't have any Clojure (not -Script) state that's somehow gone bad. CLJS state can be cleaned by simply reloading the browser page.

I've been hoping to finally be settled into single working program and website I can modify like that, without needing to keep starting new ones, but I'm not there yet.

I'll try that again right after I finish this little experiment of trying to modify the REPL webpage.

I'm trying to follow this: https://www.w3schools.com/js/js_htmldom_nodes.asp but I'm not sure how to ClojureScript-ify those JavaScript commands

I suppose I could just do interop.

I'd advise against following W3 guides. Some years ago they were quite bad and factually incorrect, and I have completely lost trust in that website. I use only MDN nowadays for stuff like this. > I suppose I could just do interop. This is exactly what you should be doing to achieve that. "ClojureScript-ification" of JS that uses some JS APIs is mostly just interop.

Those commands involve setting variables too, and it's not clear to me whether or how those can be done with interop.

MDN? Oh, I would have thought W3 would be better. Less bias perhaps? OK, I'll check out MDN.

Just bind a name to a value, e.g. with (let [...] ...). Or, if you want to go step by step, use vars - (def para (.createElement js/document "p")).

> I would have thought W3 would be better. W3Schools is not affiliated with W3C in any way. It's just some random company.

My attempt at using let here (partial, missing last two JS lines for now): (let [para (.createElement js/document "p"), node (.createTextNode js/document "new text for new para")] .appendChild para node)

Missing an opening paren and a closing one as well in the body of let.

Success! with (let [para (.createElement js/document "p"), node (.createTextNode js/document "new text for new para"), element (.getElementById js/document "app")] (.appendChild para node) (.appendChild element para))

Given that mistake, instead of diving head-first into JS, interop, HTML JS API, I'd definitely recommend getting familiar with Clojure first: https://clojure.org/guides/learn/clojure. Perhaps not every single thing from those pages will work without modifications in CLJS - you can check here: https://clojurescript.org/about/differences.

Over the years I've kept coming back to learn Clojure/ClojureScript syntax in bits and pieces, and one point knew enough to make a small text manipulation and math-based program, but yes I hadn't used 'let' yet.

You've been missing out then. :D

There's so little syntax, you'll learn 95% of it in less than a day. The remaining 5% is macros-related, and you might actually never need it.

(That's separate from the standard library though, but let is arguably more a part of the syntax than the std.)

I'm going to come back at this in a few hours, feeling all set up to finally make some of the things I've wanted to make (and relearn/learn the language in the process of course). Thanks for the help! Hopefully I'm through the set-up phase now and can finally get on with it.

No problem, good luck! If you encounter new issues or come up with new questions I'd suggest starting a new thread though. Despite what it might look like, I'm not on this Slack for 24/7, and I doubt anyone new would visit this thread now that it has accumulated so many messages.

I thought this conversation was in the main thread. Haha I had checked the "also send to # beginners" checkbox for my message way back at the end of the Java version issue, and thought that brought me back to the main thread.

We actually prefer threads here, to keep things organized, to prevent excessive notifications for non-participants, to avoid interleaving independent discussions.

I'm guessing this error would be tripping me up whether trying to use ClojureScript or Clojure.