Hi All, Looking for suggestions on how to debug the following problem: I have a Leiningen project that ultimately builds an application into an uberjar that deploys inside a docker container as a Kubernetes microservice. It has been stable for years now. Recently, the local build on OSX started exhibiting weird behavior where I can’t run the uberjar itself (e.g., via ‘java -jar path/to/uber.jar’) if I built it on my Mac. It just silently exits on both OSX and within the Linux docker env. It works fine if I use the uberjar as built from the Dockerfile. It also works fine if I ‘lein run’ it. I ran it through jdwp suspend=y, and it seems that the problem is a java.lang.NoClassDefFoundError when running the uberjar built by OSX directly, so I am guessing this is somehow related to the classpath. Both the Linux and OSX environments are using JDK22 by default, though I have also tried jenv to build the uberjar with other JDK variants (21, 20, 13, etc) on the Mac to try to isolate to no avail. It would be much appreciated if anyone could give an idea of what might cause this or what to look for.
In general, the fact that the uberjar built from the Dockerfile continues to run fine I suppose points to OSX vs Linux vs something wrong with my lein project.clj, but I am at a loss
As another data point, I can build other microservices that use the same pattern and the uberjar runs fine. So there is something about this particular app
What is the class that is not found?
I cant really tell. I’ve tried this with jdb
I think this is telling me that the problem is with
manetu.protobuf.attributes.meta__init.load ?
and frame 8 has
main[8] locals
Method arguments:
name = "manetu.protobuf.attributes.meta$write_Literal_Lang"
Local variables:so as best I can tell its related to trying to load that class…but also as best I can tell, that class is available in the classpath
If I run java -verbose i can see some activity in this area but no clear error
It’s not uncommon for protobuf classes to be included in multiple dependency jars and thus for you to end up with an arbitrary (perhaps old) copy from a dependency in your uberjar. Might also check if the those are compiled with the same jvm version
In this case they are .cljc files generated by my protojure tool. They don’t seem to be included in any obviously wrong way (old, multiple times, etc). It seems to purely be how the cljc gets incorporated into the uberjar
It’s very odd
The generated code looks like this
;;;----------------------------------------------------------------------------------
;;; Generated by protoc-gen-clojure. DO NOT EDIT
;;;
;;; Message Implementation of package manetu.protobuf.attributes.meta
;;;----------------------------------------------------------------------------------
(ns manetu.protobuf.attributes.meta
(:require [protojure.protobuf.protocol :as pb]
[protojure.protobuf.serdes.core :as serdes.core]
[protojure.protobuf.serdes.complex :as serdes.complex]
[protojure.protobuf.serdes.utils :refer [tag-map]]
[protojure.protobuf.serdes.stream :as serdes.stream]
[com.google.protobuf :as com.google.protobuf]
[manetu.protobuf :as manetu.protobuf]
[clojure.set :as set]
[clojure.spec.alpha :as s]))
;;----------------------------------------------------------------------------------
;;----------------------------------------------------------------------------------
;; Enumerations
;;----------------------------------------------------------------------------------
;;----------------------------------------------------------------------------------
;-----------------------------------------------------------------------------
; Subject-Type
;-----------------------------------------------------------------------------
(def Subject-Type-default :type-attribute)
(def Subject-Type-val2label {
0 :type-attribute
1 :type-metadata
2 :type-tag})
(def Subject-Type-label2val (set/map-invert Subject-Type-val2label))
(defn cis->Subject-Type [is]
(let [val (serdes.core/cis->Enum is)]
(get Subject-Type-val2label val val)))
(defn- get-Subject-Type [value]
{:pre [(or (int? value) (contains? Subject-Type-label2val value))]}
(get Subject-Type-label2val value value))
(defn write-Subject-Type
([tag value os] (write-Subject-Type tag {:optimize false} value os))
([tag options value os]
(serdes.core/write-Enum tag options (get-Subject-Type value) os)))
;-----------------------------------------------------------------------------
; Literal-DataType
;-----------------------------------------------------------------------------
(def Literal-DataType-default :datatype-integer)
(def Literal-DataType-val2label {
3 :datatype-integer
4 :datatype-double
8 :datatype-datetime
7 :datatype-time
5 :datatype-float
6 :datatype-date
1 :datatype-bool
0 :datatype-string
2 :datatype-decimal})
(def Literal-DataType-label2val (set/map-invert Literal-DataType-val2label))
(defn cis->Literal-DataType [is]
(let [val (serdes.core/cis->Enum is)]
(get Literal-DataType-val2label val val)))
(defn- get-Literal-DataType [value]
{:pre [(or (int? value) (contains? Literal-DataType-label2val value))]}
(get Literal-DataType-label2val value value))
(defn write-Literal-DataType
([tag value os] (write-Literal-DataType tag {:optimize false} value os))
([tag options value os]
(serdes.core/write-Enum tag options (get-Literal-DataType value) os)))
;-----------------------------------------------------------------------------
; Literal-Lang
;-----------------------------------------------------------------------------
(def Literal-Lang-default :lang-none)
(def Literal-Lang-val2label {
0 :lang-none})
(def Literal-Lang-label2val (set/map-invert Literal-Lang-val2label))
(defn cis->Literal-Lang [is]
(let [val (serdes.core/cis->Enum is)]
(get Literal-Lang-val2label val val)))
(defn- get-Literal-Lang [value]
{:pre [(or (int? value) (contains? Literal-Lang-label2val value))]}
(get Literal-Lang-label2val value value))
(defn write-Literal-Lang
([tag value os] (write-Literal-Lang tag {:optimize false} value os))
([tag options value os]
(serdes.core/write-Enum tag options (get-Literal-Lang value) os)))and you can see in the -verbose output that it was making its way through some of the functions with a cis -> get -> write pattern, but then for some reason on the Literal_Lang type it seems to balk at write-Literal-Lang after successfully calling the first two (cis, get)
and like I mentioned, it works fine in uberjars built by linux, or lein run.. I dont get it
I unzipped the resulting uberjar. The files in question are
(default ) ✔ ~/sandbox/git/mcp-attribute-service/target/uberjar/manetu/protobuf/attributes/meta [master|⚑ 13]
10:13 $ ls
ChangeRecord-KafkaState-record$reify__23964.class ChangeRecord-record$reify__24011.class Literal-record$reify__24202.class
ChangeRecord-KafkaState-record$reify__23966.class ChangeRecord-record$reify__24013.class Literal-record.class
ChangeRecord-KafkaState-record$reify__23968.class ChangeRecord-record$reify__24015.class Subject-Ref-record$reify__23804.class
ChangeRecord-KafkaState-record.class ChangeRecord-record$reify__24017.class Subject-Ref-record$reify__23806.class
ChangeRecord-Triple-record$reify__24153.class ChangeRecord-record$reify__24019.class Subject-Ref-record$reify__23808.class
ChangeRecord-Triple-record$reify__24155.class ChangeRecord-record$reify__24021.class Subject-Ref-record.class
ChangeRecord-Triple-record$reify__24157.class ChangeRecord-record.class Subject-record$reify__23876.class
ChangeRecord-Triple-record$reify__24159.class CompletionResult-record$reify__24113.class Subject-record$reify__23878.class
ChangeRecord-Triple-record$reify__24161.class CompletionResult-record$reify__24115.class Subject-record$reify__23880.class
ChangeRecord-Triple-record.class CompletionResult-record$reify__24117.class Subject-record$reify__23882.class
ChangeRecord-TxnState-record$reify__24077.class CompletionResult-record.class Subject-record$reify__23884.class
ChangeRecord-TxnState-record$reify__24079.class EncryptionEnvelope-record$reify__23925.class Subject-record$reify__23886.class
ChangeRecord-TxnState-record.class EncryptionEnvelope-record$reify__23927.class Subject-record.class
ChangeRecord-record$reify__24003.class EncryptionEnvelope-record$reify__23929.class Value-record$reify__23843.class
ChangeRecord-record$reify__24005.class EncryptionEnvelope-record.class Value-record.class
ChangeRecord-record$reify__24007.class Literal-record$reify__24198.class
ChangeRecord-record$reify__24009.class Literal-record$reify__24200.classi assume these are the interesting bits
$ ls Literal-record*
Literal-record$reify__24198.class Literal-record$reify__24200.class Literal-record$reify__24202.class Literal-record.classis there something I could look for to ensure the functions are present?
I tried playing around with disassembler and just plain binary grep, but it wasnt very fruitful
oh wait, its one directory up that is pertinent
$ ls -la | grep Literal
-rw-r--r-- 1 ghaskins staff 2887 Aug 25 08:48 meta$cis__GT_Literal$fn__24225.class
-rw-r--r-- 1 ghaskins staff 1362 Aug 25 08:48 meta$cis__GT_Literal.class
-rw-r--r-- 1 ghaskins staff 1221 Aug 25 08:48 meta$cis__GT_Literal_DataType.class
-rw-r--r-- 1 ghaskins staff 1213 Aug 25 08:48 meta$cis__GT_Literal_Lang.class
-rw-r--r-- 1 ghaskins staff 1742 Aug 25 08:48 meta$convert_Literal_lang.class
-rw-r--r-- 1 ghaskins staff 1742 Aug 25 08:48 meta$convert_Literal_type.class
-rw-r--r-- 1 ghaskins staff 1141 Aug 25 08:48 meta$ecis__GT_Literal.class
-rw-r--r-- 1 ghaskins staff 819 Aug 25 08:48 meta$fn__24194$__GT_Literal_record__24215.class
-rw-r--r-- 1 ghaskins staff 1442 Aug 25 08:48 meta$fn__24194$map__GT_Literal_record__24217.class
-rw-r--r-- 1 ghaskins staff 2772 Aug 25 08:48 meta$get_Literal_DataType.class
-rw-r--r-- 1 ghaskins staff 2767 Aug 25 08:48 meta$get_Literal_Lang.class
-rw-r--r-- 1 ghaskins staff 3404 Aug 25 08:48 meta$new_Literal.class
-rw-r--r-- 1 ghaskins staff 1095 Aug 25 08:48 meta$pb__GT_Literal.class
-rw-r--r-- 1 ghaskins staff 1958 Aug 25 08:48 meta$write_Literal_DataType.class
-rw-r--r-- 1 ghaskins staff 2734 Aug 25 08:48 meta$write_Literal_Lang.class
-rw-r--r-- 1 ghaskins staff 2738 Aug 25 08:48 meta$write_Literal_type.class$ javap meta\$write_Literal_Lang.class
Compiled from "meta.cljc"
public final class manetu.protobuf.attributes.meta$write_Literal_lang extends clojure.lang.AFunction {
public static final clojure.lang.Var const__0;
public static final clojure.lang.Var const__2;
public static final clojure.lang.Var const__3;
public static final clojure.lang.Keyword const__4;
public static final clojure.lang.Var const__5;
public static final java.lang.Object const__6;
public static final clojure.lang.AFn const__8;
public static final clojure.lang.Keyword const__9;
public static final clojure.lang.Var const__10;
public static final java.lang.Object const__11;
public static final clojure.lang.AFn const__12;
public manetu.protobuf.attributes.meta$write_Literal_lang();
public static java.lang.Object invokeStatic(java.lang.Object, java.lang.Object);
public java.lang.Object invoke(java.lang.Object, java.lang.Object);
public static {};
}it seems like everything is there, so I dont know where the exception is coming from
a debug tip:
run java -cp path/to/uber.jar clojure.main
it will open a clojure REPL
then you do (require 'your-app.main) and try to call (your-app.main/-main "with" "argv")
it will move your problem from "silent exit" to a clear exception
That was something I didn’t know, so thank you for that! Unfortunately its only telling me info I already know
$ java -cp target/uberjar/app.jar clojure.main
Clojure 1.11.4
user=> (require 'manetu.attribute-service.main)
Warning: environ value /Users/ghaskins/.jenv/versions/22 for key :java-home has been overwritten with /usr/local/Cellar/openjdk/22.0.2/libexec/openjdk.jdk/Contents/Home
WARNING: import already refers to: #'clojure.core/import in namespace: manetu.utils.time.google, being replaced by: #'manetu.utils.time.google/import
WARNING: compile already refers to: #'clojure.core/compile in namespace: manetu.attribute-service.compiler-cache, being replaced by: #'manetu.attribute-service.compiler-cache/compile
Execution error (NoClassDefFoundError) at java.lang.ClassLoader/defineClass1 (ClassLoader.java:-2).
manetu/protobuf/attributes/meta$write_Literal_Lang (wrong name: manetu/protobuf/attributes/meta$write_Literal_lang)Any further tips appreciated
there's a typo difference there at the end - ...Lang vs ...lang
Good eye, but I don’t think I’m doing that. Seems to be a problem with the toolchain on my Mac. Is that possible?
Ie this code works if I build an uberjar on Linux or if I run it via lein run. So it seems to be how the uberjar is emitted on my Mac
@ghaskins compare your jar content, between the jar that works and the jar that does not work
linux ~$ jar tf path/to/app.jar
macos ~$ jar tf path/to/app.jar
if there is something different
run lein classpath on both system and check:
• if both classpath are equal
• try to find the origin of the difference between the systems. it is possible - the Mac filesystem is case-insensitive but case preserving and you can absolutely be picking up a case difference in class names
Java is case-sensitive in class names so definitely matters to java