clojure 2025-11-25

Hello! What is the current idiomatic way of interroping with Java decorators? Like you would have from Sprint Boot for example? I have https://github.com/dbos-inc/dbos-demo-apps/blob/4ededfd32bab22edc5757c63df0e95cecfa9b91f/java/dbos-starter/src/main/java/com/example/dbosstarter/service/DurableStarterServiceImpl.java that I'd like to replicate in Clojure, particularly of interest is to replicate the @Workflow and @Step. I'll leave in the thread details about them for ease. From my own research, currently the best bet is to use gen-class which looks ugly and seems to limit me to 1 workflow per namespace (I might be wrong on this one) My goal in the end is to have some macros like deforkflow & defstep for ease of use. Context: The code is from a https://docs.dbos.dev/ java example

Currently, only relevant example of interop with decorators I found is https://linktosriram.github.io/clojure-spring-guide/ Code for Step & Workflow :

package dev.dbos.transact.workflow;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Step {
  String name() default "";

  boolean retriesAllowed() default false;

  double intervalSeconds() default StepOptions.DEFAULT_INTERVAL_SECONDS;

  int maxAttempts() default 3;

  double backOffRate() default StepOptions.DEFAULT_BACKOFF;
}
package dev.dbos.transact.workflow;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Workflow {
  String name() default "";

  int maxRecoveryAttempts() default -1;
}

I don't have a very good memory of the specifics because I myself have never had to do it, but whenever someone asks about decorators there's always something gnarly going on, sometimes to the point of "ah, well, this specific decorator is so nasty that even gen-class cannot be used here and you have to write Java". With that being said, I'd probably just generate Java from Clojure and compile it. Or write Java manually (multiple nested classes in a single file) and compile and use it from Clojure. Some links: • https://github.com/clj-commons/virgilhttps://github.com/tailrecursion/javastarhttps://github.com/athos/JiSEhttps://github.com/jgpc42/insnclojure.asm, although technically it's not a public API: https://gist.github.com/athos/1033052/b332f3786c40f1c5b85abf1ba56012024b977448

👍 1

I wonder if you can get away with definterface + deftype. E: relevant example

(import 'java.lang.Deprecated)

(definterface Something
  (^int m [^{Deprecated true} ^String x]))

(let [^java.lang.reflect.Method m (.getMethod Something "m" (into-array [String]))
      [[dep-ann]] (.getParameterAnnotations m)]
  (assert (instance? Deprecated dep-ann))
  (assert (= ["m" [String] Integer/TYPE] (method-sig (first (.getMethods Something))))))
but even then, Virgil makes working with Java classes very easy anyway.

Clojure has some support for annotations via type hints but it's all a bit laborious. Here's an old blog post of mine talking about implementing New Relic Trace annotations on functions: https://corfield.org/blog/2013/05/01/instrumenting-clojure-for-new-relic-monitoring/

🔥 2

Ah that's very handy thank you!