Google Cloud Pub/Sub with Clojure

Google Cloud Pub/Sub is simple to use from Python, but what about Clojure? There is no official support and the Java interop is not straightforward. There are few libraries but none is active. We ended up building our internal mini-library jonotin, and today we have published the code.

Cheaper infrastructure with Pub/Sub

We process a lot of data. Big part of this is transforming all the patent text into knowledge graphs. That means that every time we improve the code, we need to process text documents into ~30 million graphs for the benefits. With 300 machines this takes around one week. In the beginning we split the data into monthly batches which the machines went through, one by one. This was fine, but as the data grew, the expenses grew too.

We optimised the parsing to the point that the next step could have been moving to C language. The problem with the batches became apparent, as we would have wanted to use the much cheaper preemptible machines. They are cheap machines that have a chance to be shut down any minute, and as processing a single batch could take an hour, a shutdown would cause bad data loss. Our data guru Juuso came up with the plan to process one document at a time, instead of a batch. This required new tools.

Today we are serious about queues.

Message queues are the standard way of solving such a problem. You publish messages to a queue which then can be processed by multiple subscribers. Pub/Sub is Google's version of this pattern. Ideally, we would have wanted to use cloud functions, but the parsing required more memory than the maximum of 2GB the cloud functions offer. Also, the parser machine startup time is never going to be super fast, as only the loading of the required 1GB language model to memory takes few seconds. So, we needed machines that could process a queue. This was actually straightforward in Python with google-cloud-pubsub library. With one week effort, Juuso had saved 75% of graph parsing expenses and all was great.

Pub/Sub from Clojure

We do everything except AI in Clojure, mostly because I love the language. We did the earliest data imports with monthly batches, and it worked well. Not fancy or the most optimal, but saving 100€ for such one time job was not worth neither the effort nor adding a new tool to our system. As we started getting new data sources in and some of the data needed to be processed again, queues became attractive. We already had them in use too, so it wouldn’t be that much extra mental load.

There are few pubsub implementations for Clojure. clj-gcp seems to do the trick, but its pubsub part is written to be used with integrant and is outdated. google-cloud is even more outdated, but the main problem for us was that it didn’t allow setting maximum number of pulled messages.

So why not to use Java interop? With the good experiences from Python and examples from the old libraries, this should be easy. Except that it wasn’t. Clojure interop with Java is decent, but Google has injected some heavy OOP to the Java google-cloud-pubsub. We needed to read through not only the google-cloud-pubsub code but also some code of its dependencies.

Our Pub/Sub wrapper jonotin

We endured more pain than we expected, and that is why we have published the code (github). Hopefully it helps someone out there. It’s 70 lines of Java interop with just two functions: publish! and subscribe!. There will be use cases it doesn’t fit right away, but for us this was the simplest solution that gets the job done. Easiest way to test it is use it as a Leiningen dependency.

[jonotin "0.2.1"]

Publishing messages is minimal. The published messages are any strings and jonotin takes only the parameters needed for identifying the Pub/Sub queue.

(require `[jonotin.core :as jonotin])

(jonotin/publish! {:project-name "my-gcloud-project"
                   :topic-name "my-pubsub-topic"
                   :messages ["message to queue"
                              "another message"]})

Subscribing is simple as well. The messages are read as long as there are any in the queue. Every error is caught and handle-error-fn can do whatever is needed with them. Whatever happens, message is acked in the end.

(require `[jonotin.core :as jonotin])
‍
(jonotin/subscribe! {:project-name "my-gcloud-project"
                     :subscription-name "my-pubsub-subscription"
                     :handle-msg-fn (fn [msg]
                                      (println "Handling" msg))
                     :handle-error-fn (fn [e]
                                        (println "Oops!" e))})

The name jonotin means “a thing that queues” in Finnish, a word that nobody ever uses. Most of the Clojure open source libraries from Finland are made by Metosin, a software house from Tampere. They usually come with funky Finnish names, like reitit, muuntaja or  kekkonen. We wanted to follow this tradition.

jonotin from inside

This section might be of interested for you if you plan to modify jonotin, or you are simply interested of the trouble we had to go through,

One detail to remember about publishing to Google Pub/Sub is to convert the string  into byte string before creating the message object. Publisher seems to behave strangely when large amount of messages is passed to it. We use safety limit of 10000 messages.

(-> (PubsubMessage/newBuilder)
    (.setData (ByteString/copyFromUtf8 "message to queue"))
    .build)

Subscribe contains most of the real tricks. For handling the messages we need to implement com.google.cloud.pubsub.v1.MessageReceiver interface and pass it to subscriber.

(let [msg-receiver (reify MessageReceiver
                     (receiveMessage [_ message consumer]
                       (let [data (.toStringUtf8 (.getData message))]
                         (try
                           (handle-msg-fn data)
                           (catch Exception e
                             (if (some? handle-error-fn)
                               (handle-error-fn e)
                               (throw e)))
                           (finally
                             (.ack consumer))))))
      subscriber (.build (Subscriber/newBuilder subscription-name-obj msg-receiver))]

‍

jonotin in Github

Read more

Webinar Q&A follow-up: your questions about the IPRally MCP answered

Thank you to everyone who joined Patent answers, inside the AI you already use. Across the two sessions on 4 November, you sent us more questions than we had time to answer live. We have grouped them by theme below, merged similar questions and included the answers given during the sessions.

Your IPRally workspace, wherever you work

Your AI assistant can now pick up where your IPRally work left off. The IPRally MCP now reads your collections, tags, search cases, projects, and agent runs, so your agent starts from results you’ve already vetted instead of a blank prompt.

Webinar Q&A follow-up: your questions about IPRally Drafting answered

Thank you to everyone who joined No more drafting in the dark: prior-art-aware patent drafting with IPRally Protection. Across our EU and US sessions, you sent us more than 80 questions, from file formats and drawings to claim clarity and US disclosure duties. We have grouped the answers by theme and merged similar questions. Some were answered live or in the chat during the sessions, and we include those here too.

Webinar Q&A follow-up: your questions about IPRally Intelligence answered

Your questions on IPRally Intelligence, answered: from working with larger datasets to verifying results at paragraph level.

IPRally is SOC 2 Type II compliant

IPRally has completed its SOC 2 Type II examination. Advantage Partners, our independent auditor, tested our security controls in operation from March 4 to June 4, 2026, and issued an unqualified opinion: no exceptions noted on any control tested.

When systems start talking: IPRally's journey toward a virtual IP department

Something new is happening: people talk to systems in plain language, systems talk back, and systems have started talking to each other the same way. As AI climbs from the small decisions to the large ones, the question for IP work is no longer whether it gets automated, but how to stay in control once it is. Co-founder and CPO, Sakari Arvela, lays out where this leads – through IPRally's new API and upcoming MCP support toward a virtual IP department that's always on – and why humans end up more important in that picture, not less.

Search, Intelligence, Protection: the three pillars of IPRally’s next chapter

For nearly a decade IPRally has been obsessed with getting one task right: patent search. Having built the best patent search in the world, we’re now reorganizing around three pillars – Search, Intelligence, and Protection – the newest of which makes drafting prior-art-aware from the first sentence. Co-founder and CPO Sakari Arvela maps out the next chapter, and why Protection is the shift he's most personally invested in.

NGen selects IPRally to bring AI-native patent intelligence to Canada's advanced manufacturers

After evaluating a range of tools on functionality, usability, and cost, Canada's Global Innovation Cluster for Advanced Manufacturing chose IPRally to power its Patent Search Adoption Tool Pilot Program.

IPRally and RPX Corporation announce strategic collaboration, cross-linking AI-powered patent search with RPX Empower

IPRally today announces a strategic collaboration with RPX Corporation, the global patent risk management service provider, that further expands the capabilities of the RPX Empower platform and provides seamless access to business critical complementary information for IPRally users.

Macro meets micro: IPRally's next chapter builds intelligence on search

Excellence in patent search was always the foundation. But foundations are meant to be built on. After years of staying focused – and saying no to a lot – IPRally is now ready to bring the same rigor and precision that redefined search to the bigger, strategic questions that businesses make decisions on. CCO Andreas Cehlinder explains why now, and what comes next.