苦勞德報 for Java 生態 — 2026-09-04

2026-09-04

1. [頭版] JetBrains 調查 8,837 位開發者:換語言多半是被專案逼的,只有 Kotlin 是自願的

報導

(本報賈新聞/科技組報導)JetBrains 在《State of Developer Ecosystem 2025》調查中訪問 8,837 位開發者,問他們為什麼換程式語言。結果相當一致:絕大多數語言的最常見答案都是「專案要求」—— 也就是被工作推著走。唯一的例外是 Kotlin。JetBrains 的結論寫得很直接:「人們不是因為不得不才轉向 Kotlin,而是因為它提供更好的開發體驗與更現代的語言特性。」貼文在 r/Kotlin 開出 76 分、24 則留言,但真正有價值的訊息不在調查本身,而在留言區的補正。

最高分留言點出了一個廠商報告不會強調的結構性因素:Kotlin 天生就是設計成 Java 的替代品,而語言遷移通常等同整包重寫,Kotlin 卻可以一個檔案一個檔案換過去。有人接著補刀,換去 C# 你得整套 tech stack 搬家,改動規模完全不是一個量級。換句話說,Kotlin 的「自願率」高,很可能不只是因為它討喜,也因為它是少數幾種你可以低成本試一口的語言 —— 遷移門檻低,自然更容易在沒有專案壓力的情況下被主動採用。

更誠實的反證來自 Android 陣營。一位開發者自嘲:「我沒有選 Java,是 Android 幫我選的;同樣地,我也沒有選 Kotlin,是 Android 幫我選的。」Google 自 2017 年宣布 Kotlin 成為 Android 一級語言後,平台推力一直存在,這份「自願」有多少是平台預設值造成的,調查數字回答不了。

第二條爭點則整個跑到建置工具上。有人明說自己什麼語言都比不上 Kotlin,但「熱烈地痛恨 Gradle」—— 相較之下 dotnet 的 csproj 幾乎不用手動編輯,Gradle 卻要你變成 plugin、dependency 與 source set 的專家。留言區順勢帶出兩個替代方案:宣稱建置比 Maven 與 Gradle 快 3 至 7 倍的 Mill,以及 JetBrains 自家還沒完全對齊 Gradle 功能、但已能吃下不少場景的 Kotlin Toolchain。另有一小串在數落 Java 語法笨重,具體項目包括 extends/implements 的冗長宣告、沒有 ?.、沒有 value type、不能 operator overloading。

本報觀點:這份調查最紮實的發現,其實不是「Kotlin 討人喜歡」,而是「多數語言遷移是被逼的」這個對照組。Kotlin 之所以能站在例外的位置,遷移成本低與 Android 平台推力兩項外部條件的貢獻,恐怕不亞於語言設計本身。而留言區把火力集中到 Gradle,也提醒了一件事:語言體驗好,不等於整條工具鏈的體驗好,開發者的耐心往往先耗在建置腳本上,而不是型別系統上。← 藏鏡人批:廠商自己做調查,說自己的語言是唯一被自願選中的那個 — 這種結論最好配著留言區一起讀。

社群反應

觀點 說明 代表留言
逐檔遷移是關鍵 Kotlin 本來就設計成 Java 的替代品,不必整包重寫 「語言遷移通常是整份重寫,但這裡你可以選擇用 Kotlin 換掉 Java,一次換一個檔案。」(32↑)
換別的語言代價大得多 轉 C#/dotnet 得連 tech stack 一起搬 「是沒錯,但你得換掉整個 tech stack,那比 Kotlin 大得多。」(10↑)
愛 Kotlin 但恨 Gradle 建置工具體驗跟語言體驗是兩回事 「我熱烈地痛恨 Gradle!dotnet 的建置概念舒服太多,你幾乎不用改 csproj,Gradle 卻要你變成專家才搞得定 plugin、依賴與 source set。」(10↑)
是我推公司採用的 自願採用的方向甚至是由下往上 「真要說的話,是我在逼我的主管採用 Kotlin,不是反過來。」(10↑)
平台幫你選好了 Android 開發者承認「自願」背後有平台推力 「我沒有選 Java,是 Android 幫我選的。同樣地,我也沒有選 Kotlin,是 Android 幫我選的。」(4↑)
Java 語法哪裡笨重 具體列出缺的語言特性 「extends/implements、沒有 ?.、沒有 value type、不能 operator overloading……」(3↑)

2. [技術] Spring Boot 4 遷移可以丟給 AI agent,但你得先知道它到底改了什麼

報導

(本報賈新聞/技術組報導)r/SpringBoot 一則 Spring Boot 4 遷移指南影片貼文,兩天內衝上 57 分。原 po u/Maria_3464 開宗明義說,這活當然可以交給 AI agent,沒問題,但你仍得搞懂它改了什麼、為什麼改 — 因為真正會出事的情境是「編譯過了、測試綠了,然後 production 悄悄壞掉」。貼文最後丟了一句反問:你還在手工遷移,還是已經把它交給 agent?

留言區直接就這句反問打了起來。u/CptGia 是手工派代表:OpenRewrite 試過,弄得一團亂;Copilot modernization 試過,燒了半小時 token 換來更大一團亂。現在他改回手工,頭幾個 service 有點痛苦,但一個接一個做下來愈來愈快。另一頭 u/axkoam 說 Spring Boot 2 升 3 時 OpenRewrite 對他超順,u/Psychoboy 則報告用 Moderne 一路順到底。

全篇最有價值的一擊來自 u/g00glen00b。他承認 OpenRewrite 沒有百分之百搞定,但同樣的時間拿去手工只會更慘;關鍵是他們把 OpenRewrite 沒處理完的殘留問題寫成一份完整的 troubleshooting 指南,再把那份指南轉成 agentic skill。此後多個 Spring Boot 專案的升級都能平行跑、人只負責 review。他順手回敬了手工派:你之所以愈做愈快,是因為你已經摸清楚要盯哪些地方;把那些東西寫下來塞進 skill,agent 就能同時幫你升好幾個專案。換句話說,工具不是重點,知識才是 — 而知識一旦離開你的腦袋、變成文件,就能交給機器跑。

火氣最大的則是 u/Physical_Level_2630:不過是 API versioning、HTTP client 換代加一票版本升級,竟然需要動員這種規模的遷移工程,這種事本來就該內建自動升級,「這讓整個產業損失數十億」。底下 u/rest_mah 冷回一句「免費開源軟體讓產業損失數十億 /s」。u/cbdpyco 補刀點名 Jackson:又換 package、又改預設行為,REST 整合再度全滅。

同一時間,r/SpringBoot 另一頭正上演這種遷移的極限版本。u/igotnojamss 手上是一套大型銀行對帳系統 — Spring MVC 加 JSP、Controller/Service/DAO 三層、Oracle 資料庫、前端 jQuery,連 Maven 都還沒正式導入;他一口氣往 Maven、Spring Boot 4.1.1、JDK 25 全推,編譯錯誤已經從滿江紅修到剩少數幾個,主管卻主張拆成兩階段:先把 Maven 穩下來,再上 Spring Boot。留言幾乎一面倒站主管。有人直接下判斷:「問題裡出現 simultaneously 這個字,答案就是不要。」做過銀行核心系統遷移的 u/Educational_Ice168 講得最完整 — 關鍵不是工時,是可診斷性:全部混在一起壞掉時,你分不出是 build tool、jakarta namespace、Spring 6 以後移除的 API,還是 JDK 25 的 strong encapsulation 幹的;他並強調 JSP 與 Tiles 這第三波改動絕對不要一起做。還有一位純吐槽:「拜託告訴我是哪家銀行,我要避開。」

本報觀點:這串爭論表面上是 OpenRewrite 派對上手工派,實際上兩派用的是同一份知識,差別只在有沒有寫下來。手工做到第五個 service 的順手,本質上是一份存在腦裡的 checklist;願意把它落成文件的人,下一步就能讓 agent 代跑。至於 Jackson 那筆帳,倒是連 agent 都很難幫忙 — 預設行為改了,編譯器不會告訴你。← 藏鏡人批:手工派與工具派吵了半天,結論是「把 checklist 寫下來」— 這件事在 agent 出現之前就該做了,只是以前寫下來沒人看,現在寫下來有機器會看。

社群反應

觀點 說明 代表留言
知識比工具重要 把工具沒處理完的殘留寫成 troubleshooting 指南,再轉成 agentic skill 「OpenRewrite 沒有百分之百搞定,但比同樣時間手工升級好太多。剩下的問題我們寫成一大份 troubleshooting 指南,再把它變成 agentic skill,現在多個專案都升得毫不費力。」(1↑)
反駁手工派 你變快是因為 checklist 記在腦裡,寫下來就能給 agent 跑 「你升級愈來愈快,唯一的原因是你已經摸清楚要看哪些東西。把那些寫成文件放進 Copilot skill,就能平行升好幾個專案,你只要 review 結果。」(1↑)
手工派實戰 自動化工具兩度失手,回頭手工反而愈做愈順 「OpenRewrite 弄得一團亂,Copilot modernization 燒了半小時 token 弄得更亂。現在我手工遷移,前幾個 service 有點痛苦,但之後每個都愈來愈快。」(12↑)
開砲升級成本 只是 API versioning 與版本升級,卻要動員全公司 「這種更新居然需要大規模遷移工程,實在荒謬。這類東西本來就該內建自動升級,這讓整個產業損失數十億。」(18↑)
反串回嗆 對「產業損失數十億」的冷回應 「免費開源軟體讓產業損失數十億。/s」(1↑)
Jackson 又來 package 換了、預設行為也改了,REST 整合再遭殃 「喔對,把你所有 REST 整合弄壞吧,因為 Jackson 又要換 package 了。而且順便又改了預設行為。」(1↑)

3. [技術] 512 MiB 小主機同場對決!Spring Boot 與 Quarkus 記憶體大逆轉,最後主機直接 OOM 砍掉 Spring

報導

(本報賈新聞/科技組報導)一名開發者為了替自家監控工具 StatLite 加上 Quarkus 支援,順手做了一場極端條件的框架比較實驗:一台只有 512 MiB 記憶體、外加 256 MiB swap 的 VPS,同時跑 Spring Boot 與 Quarkus 兩支等價應用程式,環境統一為 JDK 25.0.4、-Xmx80m、H2 資料庫與相同 JVM 參數,再讓一支 StatLite process 同時監看兩邊。結果比他預期的更不直觀。

一開始,Quarkus 的 RSS 反而較高,約 145 MiB,Spring Boot 只有 112 MiB。撐過一小時的記憶體壓力後,兩者位置對調:Quarkus 降到約 83 MiB RSS、heap 用掉約 33 MB,Spring Boot 則衝到約 154 MiB RSS、heap 約 50 MB。但作者強調 RSS 藏住了另一半故事 —— Quarkus 被換出到 swap 的量高達約 91 MiB,Spring Boot 只有約 66 MiB,Linux 的 paging 其實在背後做了大量苦工。後續一輪觀察中,機器在套件維護造成的壓力散去後仍撐不住,記憶體與 swap 餘裕見底,kernel 直接 OOM kill 掉 Spring Boot。

作者因此拒絕把這份數據讀成「Quarkus 比 Spring 少吃 X MB」的乾淨 benchmark,他自己最重要的結論是:一旦機器真的吃緊,RSS 就不再是有效的框架比較指標。他坦承 512 MB 要塞兩支應用加監控本來就是硬撐,正在規劃改用 768 MB 重跑一次比較乾淨的實驗。

留言區則分成三派。方法論派認為量測本身有雜訊:u/sgrinovero 直言比較記憶體效率不該用 in-memory 資料庫,H2 會為量測加入顯著雜訊,而且實務上也很少有人拿 H2 上 production;Quarkus 陣營的 u/jponge 補充,Quarkus 開機就把網路層拉起來,Spring 預設是 lazy 啟動、藉此讓開機數字比實際好看,建議改成「跑完剛好一個 request 之後再量 RSS」,故事很可能完全不同。版本公平性派則盯著測試對象:u/lazystone 指出 Spring Boot 3.5.5 是一年多前的版本,卻拿去對上幾小時前才發布的 Quarkus,看起來有點偏頗,該用現行穩定版 Spring Boot 4.1.x;u/Joram2 也希望等 Quarkus 4.0 beta 出來再重跑一次。第三派則直接質疑動機,u/agentoutlier 主張整篇是替作者自家 StatLite 打廣告,還點名文中的投票連結違反板規禁止調查的規定,雙方在串上短兵相接。

實務派的分享反而最快能派上用場:u/elmuerte 回報自己給容器 512 MiB 時忘了調 buildpack 設定,導致 memory calculator 把 -XX:ReservedCodeCacheSize 預設拉到 240MB,等於先啃掉大半配額;他把它砍到 64 MiB 之後,heap 才夠應付大 payload 的測試。本報觀點:這則實驗真正的收穫不在誰比較省,而在提醒大家 —— 在窮到會 swap 的機器上,你量到的 RSS 只是作業系統當下願意留在實體記憶體裡的那一塊,不是應用程式真正的胃口。← 藏鏡人批:拿自家監控工具跑框架對決、順便在文中放投票,被抓包也不算冤 — 但那句「RSS 在窮機器上沒有比較意義」是真的值錢。

社群反應

觀點 說明 代表留言
方法論質疑 用 in-memory 資料庫比記憶體效率會混入大量雜訊 「如果你的目標是比較兩個框架的記憶體效率,我不會用 in-memory 資料庫:它會為量測加入顯著雜訊,也不太貼近現實,畢竟很少人在 production 用 H2。」(3↑)
量測時點錯了 Quarkus 開機就起網路層,Spring 是 lazy,起始 RSS 本來就不可比 「Quarkus 啟動時 RSS 較高,單純是因為它一開機就把網路層拉起來,而 Spring 預設是 lazy,好讓開機數字比實際漂亮。我的建議是:量 RSS,但要在剛好跑完一個 request 之後量。故事很可能完全不一樣。」(2↑)
版本不對等 一年前的 Spring Boot 對上剛出爐的 Quarkus,比較基準有偏差 「我認為永遠該拿各自最新的穩定版來比。SB 3.5.5 是一年多前發布的,你卻用了幾乎最新的 Quarkus,這看起來有那麼一點偏頗。」(2↑)
等新版再戰 兩邊都有大改版在路上,現在比意義有限 「我想看這個實驗在這個月稍晚的 Quarkus 4.0 beta 上重跑一次,那版對 3.x 系列有重大架構改進。還有,為什麼測 Spring Boot 3.5.5 而不是現行的 4.1.x?」(3↑)
行銷質疑 直指整篇是替作者自家產品鋪路,投票還踩到板規 「內容是有趣沒錯,但我不需要底下那個 StatLite,而且拿一個非 headless 的 dashboard 來蒐集這類結果也稱不上好工具 —— 所以這不算離題,目的很明顯。」(6↑)
實務踩雷經驗 buildpack 預設的 code cache 才是小容器爆炸的元兇 「buildpack 的 memory calculator 不知為何把 -XX:ReservedCodeCacheSize 預設成 240MB,這實在太多了。我把它降到 64 MiB,應用程式在 512 MiB 容器裡就有更多空間給 heap。」(1↑)

4. [社會] AI 生成的棄坑 repo 洗版 r/java,社群自問:小型 OSS 作者的回饋場還救得回來嗎?

報導

(本報賈新聞/社會組報導)r/java 一則自省貼文意外引爆討論。發文者 u/Decent-Decision-9028 回憶,這個板以前是小型 open source 作者能拿到真實回饋的地方,多數專案本來就不是為了紅,而是練功用的 learning project;如今一天之內被大量新建、隔天就棄坑的 repo 灌爆,還混著 AI 代寫代推的專案,真正需要關注的 OSS 反而沉到底下。他強調不打算把責任全推給 moderator,只是想問社群有沒有更好的辦法。

留言區大致分成四派。制度派主張補齊 flair 分類,u/Chaos-vy17 開出 ICreatedThis、JDK News、OSS Review、Project Showcase,另建議 ProjectIdea 與 LookingForTeam 給找隊友的人;u/SrGnis 則提議開每週 Show and Tell 集中低成熟度貼文,或替主 feed 設一道「已持續開發一段時間」的門檻,但也提醒任何規則都得讓 mod 執行得動。揭露派以 u/tankmatesaquaponics 為代表,要求每個貼上來的專案都申報有沒有用 AI、用到什麼程度,藉此把手工專案篩出來。推廣派由 u/gufranthakur 領軍:Java 已經沒有 hype 了,生態成熟到 Gradle 能動、Spring 能動、JavaFX 和 Swing 都能動,正因如此更該主動推 greenfield 專案、libGDX 作品、JavaFX 調校與現代寫法,讓那些沒人看的新手作品被看見;他也自認代價是 slop 會更多、mod 會更累。自動化反效果派則由 u/agentoutlier 提出反證:他每次認真寫的長文,附說明、附連結、附 code snippet,反而被 automod 攔下,AI slop 卻一路暢通,「automod 反諷地在鼓勵爛貼文」。

最精彩的是 u/agentoutlier 與 u/gufranthakur 的正面衝突。面對那份「Kotlin DSL、Java 21/25、不要多層繼承、不要 factory pattern、要現代化文件」的推廣清單,u/agentoutlier 直接拿自己剛發表的 Rainbow Gum 專案逐條對照:我用 Maven、我全部文件都寫 Javadoc、我有好幾層繼承(sealed interface 疊了不只一層)、factory pattern 我大概也在用——照這份清單,他這個自認相當現代的專案不該被推廣。他的結論是,盲目 cargo cult 當紅實務跟 AI slop 一樣糟,社群真正該推的,是那些對自己專案有熱情、願意為人類寫幾段字的作者。

本報觀點:這串討論的價值不在於誰的方案會被採納,而在於它把「反 AI slop」和「反自動化審核」兩個看似同陣營的訴求攤開來對撞。門檻拉高會誤傷認真的人,門檻放寬又會被機器灌爆,總體來說,最後能分辨的還是有沒有人味。← 藏鏡人批:拿自己的專案去對照別人開的「現代 Java 清單」,發現自己全項不合格 — 這種示範比講一百句道理有用。

社群反應

觀點 說明 代表留言
制度派 補 flair 分類讓貼文各歸各位 「提供 flair 會是好事。ICreatedThis 給自己做的 Java 專案、OSS Review 給社群討論、Project Showcase 給專案展示,ProjectIdea 或 LookingForTeam 給想找人一起做的人。」(69↑)
分流派 開每週集中串,並設成熟度門檻 「大家對別人的時間很不尊重,隨便一個半成品都貼上來,只為了那點 ego boost。或許可以開每週的 Show and Tell 串分流低品質內容。但要記得,任何規則都得讓 mod 執行得動。」(35↑)
推廣派 Java 沒 hype 了,該主動推 greenfield 與現代寫法 「我看過好多人用 libGDX 做出很棒的東西,卻幾乎沒有人看。唯一的缺點是板上也會多出一些 AI slop,就像 r/rust 正在經歷的那樣,mod 會更辛苦。」(30↑)
揭露派 強制申報是否用 AI 與使用程度 「規定每個貼上來的專案都要申報有沒有用 AI、用到什麼程度。我覺得這是篩選專案、推廣手工好作品的好指標。」(7↑)
自動化反效果派 automod 擋掉認真長文,slop 反而暢行 「問題之一就是 automod 本身。每次我認真寫一篇有大量說明、連結和 code snippet 的長文,就被 automod 擋掉;同時 AI slop 卻完全沒事。」(7↑)
反 cargo cult 派 逐條對照自家專案打臉推廣清單 「照這份清單,我這個自認相當現代的專案不該被推廣:我用 Maven、文件全寫 Javadoc、有好幾層繼承、factory pattern 大概也在用。盲目跟風當紅實務同樣糟糕,我們真正想要的是有熱情、願意為人類寫幾段字的貼文。」(5↑)

5. [技術] 擱置 8 年的 JEP 草案有人自己做了,社群卻整排搖頭:Lombok 作者親自出面解釋差在哪

報導

(本報賈新聞/科技組報導)OpenJDK 有一份叫做 Concise Method Bodies 的 JEP 草案,主張讓 method 可以寫成 int f(int x) -> g(x); 這種一行式的委派語法,省掉大括號與 return。草案 2018 年掛上去之後就再也沒動,r/java 每隔一兩年就會有人翻出來問一次「這個到底怎麼了」。這回有人不等了 —— 一位開發者說自己不是 OpenJDK contributor,索性換條路自己實作,做成 source code preprocessor,把簡潔語法轉回 Java 8 相容的原始碼,同時以 library 和 Maven plugin 兩種形式發到 Maven Central,宣稱涵蓋 Java 8、11、17、21 一路到 25,開源免費,並強調自己不是要取代未來 OpenJDK 的官方實作。

工程上算是完成度不錯的一手,但留言區幾乎一面倒潑冷水,理由分成兩層。

第一層是功能價值本身。u/quantum-fudge 直言這是「想像得到最沒意義的語法糖」,每個 method 大概省 8 個字元,代價卻是多一道 preprocessor、外加語法高亮壞掉;他後續補刀說這跟 lambda 完全不能類比 —— lambda 省掉的是整個匿名類別,而且不需要自訂 plugin、不會弄壞工具鏈。u/BullfrogCharming1202 從語言設計角度看,認為真要處理委派,該做的是 Kotlin 的 by 或 Groovy 的 @Delegate 那種正經 delegation 機制,這個做法只是個很不令人滿意的替代品,並補了一句「JEP 大概就是因此被冷凍的,投入跟回報不成比例」。u/koflerdavid 則借西洋棋術語「吃了就是失誤」,說交付這個功能會排擠掉更重要的事。

第二層才是全篇最有份量的一擊,出自 Lombok 作者 u/rzwitserloot 的長回覆。有人拿 Lombok 類比,他直接把界線劃清楚:Lombok 不會取代你的 parser 階段。他指出幾乎沒人拿記事本寫 Java,最低標準也是語法高亮,絕大多數人用的是 Eclipse、IntelliJ、VS Code 這類「知道 Java 長什麼樣」的編輯器;而這個專案取代的正是 parser 階段,編輯器只會回你滿江紅的紅色波浪底線。關鍵區別在於 Lombok 產出的東西在語法上是百分之百純粹合法的 Java,它只動 semantics —— 「幫 semantics 打底只需要一點輕量 plugin 工作,幫 syntax 打底則要把所有東西整組換掉」。他給的建議是改副檔名,例如叫 .kwava,當成另一種「很像但不完全是 Java」的語言,另配一個編輯器 plugin,反而乾淨。

他還順帶示範了 Lombok 為何不做預設參數值:int b = 5 寫在參數列上不是合法 Java,但用一個 $lombokDefaults: 標籤配一個空白區塊卻完全合法(標籤是合法語法、無故開一個區塊也是)。之所以沒做,是因為那串魔法字沒有自動完成、讀者也無從查起;Lombok 堅持每個用法至少要對應到一個真實的型別,型別不在就編譯失敗,不認識 Lombok 的人也還有線索可循。

串尾照例發散。u/piesou 又搬出老怨念「我到現在還是無法接受選了 final var 而不是 val」,並提到 nullable type 的期待;u/ForeverAlot 回說那些現階段還只是想法不是正式計畫,但用 JSpecify 加 Error Prone 加 NullAway 已經可以頂掉大半。本報觀點:這件事的教訓不在於誰的語法比較漂亮,而在於一個語言擴充能不能活下來,工具鏈相容性往往比功能本身更早決定生死。← 藏鏡人批:省 8 個字元,換來整個 IDE 滿江紅 — 這筆帳連小學生都會算,難怪 JEP 躺了 8 年。

社群反應

觀點 說明 代表留言
語法糖不划算 每個 method 省 8 個字元,卻要付 preprocessor 與高亮失效的代價 「不錯的技術 demo,但作為一個功能,這是想像得到最沒意義的語法糖。」(22↑)
該做的是正經 delegation 真要解決委派問題,Kotlin 的 by、Groovy 的 @Delegate 才是對的方向 「這看起來是個很不令人滿意的替代品,比不上 Kotlin by 那種正經的委派機制。」(8↑)
JEP 被冷凍有原因 投入與回報不成比例,交付它會排擠更重要的工作 「我猜這就是 JEP 被放生的原因,這顆檸檬榨不出多少汁。」(7↑)
動 parser 就是致命傷 Lombok 只動 semantics 所以 IDE 照樣運作,取代 parser 階段則會滿江紅 「Lombok 在語法上是百分之百純粹的 Java,這個不是,差別就在這。你的編輯器只會瘋狂丟紅色波浪底線給你。」(19↑)
建議直接換副檔名 既然不是 Java,就當成另一種語言處理比較乾淨 「正確做法是把它命名成 .kwava 之類的,用不同副檔名,再配一個專屬 plugin。」(19↑)
現階段的替代方案 nullable type 還只是想法,但已有現成工具組可以頂著用 「這階段還只是想法、不算正式計畫。不過在那之前,用 JSpecify 加 Error Prone 加 NullAway 就能走上大半路程。」(3↑)

6. [技術] 不想扛 Debezium 就自己造:Outbox 改走記憶體佇列,1 分貼文換來 13 則硬核拷問

報導

(本報賈新聞/科技組報導)Transactional Outbox 幾乎是微服務發事件的標準解法,但它有個老毛病:要壓低 latency 就得把 polling 間隔調短,PostgreSQL 於是被一堆輪詢查詢與連線輪番拷問。r/SpringBoot 上一位開發者 u/SellerInsightsLab 說,他不想為了這件事扛進 Debezium/CDC — 那等於在維運清單上再加一整套 Kafka Connect 基礎設施。

他的替代方案是把 PostgreSQL 降級成「真相來源」而非「佇列」:DB transaction → afterCommit → 記憶體佇列 → Batch Publisher → Kafka。業務資料與 outbox event 照樣同一個 transaction 落地,commit 之後只把 eventId 丟進記憶體佇列,publisher 批次撈 ID、從資料庫讀事件再送 Kafka。正常路徑完全不 poll;程式掛掉時事件仍安全躺在 PostgreSQL,由 Recovery Worker 撈出未發布的事件重新入列。他還附上完整 Spring Boot demo,含 Kafka、batching、idempotency、Gatling 壓測、Grafana 指標與 tracing,開口求回饋。

貼文分數停在 1 分,留言區卻炸出 13 則品質極高的質疑,這才是本則的新聞點。最鋒利的是 u/artyomsv 的兩記直球。第一記:「你沒有移除 polling,你只是把它搬到 crash path 了 — 這沒問題,但 recovery 間隔從此就是你的最差 latency。」他要求作者把 happy path 與 recovery 兩個數字一起公布,否則只看到快的那個數字的讀者,會以為慢的那個不存在。第二記更細:10 秒一輪的 recovery 掃描不能只找「未發布的 row」,因為 200 毫秒前才 commit、此刻正躺在記憶體佇列裡的那筆,資料庫看起來同樣是未發布。少了 grace window,recovery worker 會跟正常 publisher 賽跑送出重複事件,然後被 idempotency 層默默吃掉 — metrics 上什麼都看不到。真正該畫上圖表的 recovery latency,是 interval 加上 grace。

u/Made-In-Slovakia 從實戰經驗補刀:他們的事件是業務物件的生命週期(created → updated → deleted),必須確保不會對已刪除的物件再發更新事件,而這套方案給不了順序保證;他並提醒,基礎設施裡多放一個成熟元件,往往比自製的複雜方案安全。u/Entropjy 最不客氣,四個字:「過度工程加工程不足」。u/New-Departure-5969 則走務實路線 — SKIP LOCKED 做批次認領、沒東西可撈就退避,並提醒注意 poll interval 乘上 replica 數會讓負載爆增,無聊的優化全做完還不夠,才輪到 CDC 上場。

值得一提的是原 po 全程有回應且態度良好:他承認 recovery latency 確實該和 happy path 一起量、順序保證是下一版要補的功課,也說明自己的 recovery worker 並非直接發布,而是把 ID 重新入列、由佇列去重與 DB lease 收尾。本報觀點:這則貼文的分數與留言品質完全脫鉤,恰好示範了技術社群最值錢的東西不是按讚數,而是有人願意花時間把你架構圖上沒畫出來的那條路徑指出來。← 藏鏡人批:1 分、13 則留言,含金量卻贏過整串幾百分的熱門文 — 分數從來就不是內容的計價單位。

社群反應

觀點 說明 代表留言
polling 沒消失,只是換位置 recovery 間隔就是最差 latency,兩個數字要一起公布 「你沒有移除 polling,你把它搬到 crash path 了,這沒問題,但 recovery 間隔現在就是你的最差 latency。兩個數字值得一起公布,因為只看到快的那個的讀者,會以為慢的那個不存在。」(2↑)
沒有 grace window 會賽跑出重複事件 200ms 前 commit 的 row 也是「未發布」,重複會被 idempotency 默默吃掉 「少了 grace window,recovery worker 會跟正常 publisher 賽跑並送出重複事件,你的 idempotency 層會默默吃掉它們,metrics 上什麼都看不到。」(2↑)
給不了順序保證 生命週期事件必須避免對已刪除物件發更新,這方案做不到 「你的方案對我們行不通,因為它沒有事件順序保證,而那對我們很重要。基礎設施裡一個成熟的 app,往往比複雜的自製方案更少出事。」(3↑)
直接否定 認為該用 WAL 加 Debezium 徹底移除 polling 「過度工程加工程不足。對一個可以避開的問題給出非解法。用 WAL 和 Debezium 把 polling 徹底移掉。」(1↑)
先做無聊的優化 SKIP LOCKED、退避、注意 poll interval 乘 replica 數 「批次認領上 SKIP LOCKED 幫了很大的忙,加上沒東西可撈時退避。另外檢查 poll interval 乘以 replica 數,那會讓負載長得很快。我們是無聊的修法都不夠用了才跳到 CDC。」(1↑)
原 po 虛心接招 承認 recovery latency 該一起量、順序保證下一版補 「沒錯,我沒有完全移除 polling,我把它搬到 recovery path 並降低頻率。代價當然是 recovery latency,我同意 happy path 與 recovery 兩個數字都該量、也該一起呈現。」(1↑)

7. [產業] 「與 JDK 27 一字未改」— Structured Concurrency 拚在 JDK 28 正式定案

報導

(本報賈新聞/產業組報導)OpenJDK 新釋出的 JEP 草案指出,Structured Concurrency 將在 JDK 28 走完最後一哩路,正式從 preview 轉為 final。真正在 r/java 引爆討論的,卻不是「又一個新功能」,而是草案裡那句「no changes from JDK 27」—— 相對前一版 preview 沒有任何改動。這句話被網友直接摘出來當成最高票留言,另一名讀者更回一句「這句才該當標題」,因為一個 API 敢在定案前一輪完全不動,等於官方認為設計已經收斂,開發者先前寫的 preview 程式碼不必再改一次。

慶祝的聲音很快跟上。有人把 Structured Concurrency 定案與 Valhalla 第一個 value type preview 並列,認為 JDK 28 會是近年份量相當重的一個大版本。長期使用者 u/davidalayachew 現身說法,說他已經在幾個 codebase 裡實際用上,過去處理網路錯誤時那些「亂七八糟、卡在細節裡」的東西,像 conditional retry 與超複雜的錯誤處理,套上這個模型之後都變得直觀簡單。

不同意見則從兩個方向切入。其一是「為什麼非得進 JDK 不可、外部函式庫做不到嗎」,這題由 Loom 主導者 Ron Pressler(u/pron98)親自回覆:Structured Concurrency 必須與平台的 observability 以及 ScopedValue 的 scope 繼承整合,這些機制在 JDK 之外無法重現,硬做就得放棄最重要的幾項特性;不過他也鬆口,官方正在考慮釋出更低階的 building block,可能就是 StructuredTaskScope 內部使用的 ThreadFlock 或類似物。

其二是型別系統的老帳。u/javaprof 貼出一段 catch (ExecutionException e) 之後對 cause 做 switch 的程式碼,指出這樣既無法窮盡比對、也分不出到底是哪個 subtask 拋的例外。Pressler 回應說,自然的做法是在 subtask 內部就把例外處理掉,就像循序程式裡把 catch 移進迴圈一樣,要更彈性還有 joiner 可用。u/vips7L 則搬出 Swift 的 typed throws 與 try! / try? 語法作對比,凸顯 Java 這邊一個「無法處理就往上拋」的動作得寫上好幾行。

本報觀點:定案本身不意外,「一字未改」才是這則新聞的重量所在。至於例外型別的表達力,那是 Java 更上游的題目,短期內不會因為 Structured Concurrency final 而有解。← 藏鏡人批:一個 API 在定案前一輪敢一字不改,比任何 release note 都更能說明它熟了。

社群反應

觀點 說明 代表留言
一字未改派 認為草案裡設計已凍結這件事才是重點 「本 JEP 提議在 JDK 28 定案 Structured Concurrency,且與 JDK 27 相比沒有任何改動。」(51↑)
大版本慶祝派 與 Valhalla value type preview 並列看待 「Structured Concurrency 定案,加上第一個重量級 value type(Valhalla)preview?JDK 28 看起來會是個相當猛的版本。」(41↑)
官方說明派 Ron Pressler 解釋為何非得進 JDK 「Structured Concurrency 必須在 JDK 裡,因為它得跟平台 observability 與 scope 繼承(ScopedValue)整合。在 JDK 外面實作就得放棄最重要的幾個特性。不過我們希望能釋出一個與平台機制整合的低階 building block。」(27↑)
實戰見證派 已在 codebase 用上,錯誤處理變直觀 「這功能對我來說徹底改變了遊戲規則。conditional retry、超複雜的錯誤處理,這些原本一團亂的東西,有了它之後都變得直接又簡單。等不及 Java 28 發布了。」(20↑)
型別系統批評派 checked exception 在這裡窮盡不了 「所以 checked exception 在這裡也不太管用?很好笑:case IOException ioe -> 是哪個操作丟的?default -> 沒有窮盡性?」(8↑)
他山之石派 拿 Swift 的 typed throws 對照 Java 的囉唆寫法 「Swift 對 typed throws 的 generic lambda 實作滿酷的,lambda 不會 throw 時就變成 throws(Never)。另外 try! 可以自動解掉無法處理的 checked exception、try? 直接轉成 null,Java 這邊現在得寫好幾行。」(9↑)

8. [工具] 把 MyBatis 搬到編譯期!LarkBatis 產出純 Java、runtime 只剩 1500 行

報導

(本報賈新聞/工具組報導)開發者 u/catmewo 在 r/java 公布自製 ORM 專案 LarkBatis,作法是把 MyBatis 的核心整個改寫成 annotation processor:SQL 語句、#{} 動態參數、type handler 與 result mapping 全部在 compile time 解析完畢,直接產出純 Java code。到了 runtime,剩下的只是一連串直接的 JDBC 呼叫 —— 零 proxy、零 OGNL、零 reflection,runtime 本體約 1,500 行,要求 JDK 17 以上,依賴只有 JDBC。

作者說明動機來自團隊的 cloud-native 轉型:他們正在把 reflection 與 runtime proxy 從技術棧裡拿掉,而 MyBatis 在既有系統用得極重,與其整套換掉,不如把它重寫成編譯期版本。程式設計模型刻意維持原樣 —— mapper interface、XML、dynamic SQL 一切照舊,開發者的寫法不必改。文件站宣稱 GraalVM native-image 可以開箱即用,不過作者在貼文裡就自己補上但書:這點他還沒實際測過。

專案同步也發到 r/SpringBoot,但那邊幾乎沒有回響(1↑、0 則留言),討論火力全集中在 r/java。

定位問題最先被提出。有人直接問「這跟 JDBI 看起來很像,差在哪」,也有人拿 jOOQ 來比。網友 predator 給出被多數人接受的總結:這套東西的價值主張,是「你已經深陷 MyBatis,只想拿掉 reflection 的開銷,而不想換一整套 idiom」。作者本人也認同這個切分,回應說 jOOQ 解的是比他更大的問題,而他喜歡 MyBatis 正是因為它夠簡單。

真正意外燒起來的,是一場 codegen 技術路線之爭。網友 repeating_bears 力勸作者「別用 JavaPoet」,理由是 fluent API 多了一層 indirection,template 語言寫出來的東西跟產出的 code 長得比較像;他自己選 Velocity,因為 IntelliJ 對它支援不錯,syntax highlighting 與 find usages 都能用,缺的 import 管理他另外寫了個小 lib 補上。此說法引來 Caffeine 作者 NovaX 反駁:template 在簡單情境確實該是預設,但一旦 conditional 選項變多就會爛成一團,這時把細節收進 rule engine 再用 JavaPoet 組裝,才有 cohesive 與可組合的結構 —— Caffeine 自己就是這麼做的。兩人來回數輪,最後 NovaX 以「可以不同意,但你的意見再怎麼強硬也不等於事實」作結。旁邊還有人插花推薦編譯期 template library Jstachio。

另有一段 license 插曲:有網友指出 repo 沒看到授權資訊,順帶問要不要把專案併進 MyBatis 官方。作者澄清 repo 內有 Apache 2.0 的 LICENSE 檔,是文件站漏寫,責任在他,會補上;至於併入官方,他認為短期內不可能,因為 MyBatis 大量功能本來就建立在 reflection 上,除非哪天出現一個刻意砍掉這類功能的「MyBatis 4」,那他很樂意貢獻。

本報觀點:LarkBatis 打的不是「更好的 ORM」這種大題目,而是一個定義清楚的遷移縫隙 —— 服務要進 native image、但團隊不想重寫十年份的 mapper。這種「原地換引擎、不換方向盤」的工具,價值往往比想像中高。只是 GraalVM 相容性既然是主打賣點,尚未實測這件事,恐怕得盡快補上。← 藏鏡人批:主打 GraalVM 開箱即用、但自己還沒跑過 native-image — 這種文件先行的勇氣,比 annotation processor 還前衛。

社群反應

觀點 說明 代表留言
樂見替代方案 認為 jOOQ 不差,但部分遷移對很多團隊都是風險 「這很不錯,終於有 jOOQ 以外的選擇了。我至少認識一位資深同事會愛死這個(那位老兄根本是靠 MyBatis 呼吸的)。」(6↑)
價值主張定位 一句話把適用族群講清楚 「我的理解是,它的價值主張就是:你已經深陷 MyBatis,只想拿掉 reflection 的開銷 —— 換到一套 idiom 完全不同的函式庫,根本不在選項內。」(5↑)
別用 JavaPoet 主張 template 語言比 fluent API 好讀好維護 「我做很多 Java codegen,強烈建議捨棄 JavaPoet。我當初也是從它開始,它看起來像預設選擇,但我不覺得它配得上這種人氣。」(5↑)
JavaPoet 有其場景 Caffeine 作者反駁:conditional 一多,rule engine 加 JavaPoet 才可組合 「template 在常見的簡單情境很好用,也該是預設。但一旦 template 變大、conditional 變多就難讀難維護,這時圍繞 rule engine 依需要的能力組裝會好得多。Caffeine 用的是 JavaPoet,我認為換成 Velocity 會明顯更糟。」(2↑)
作者自我定位 承認 jOOQ 解的問題更大,自己看中的是 MyBatis 的簡單 「謝謝。jOOQ 解的問題比我想解的大。但我喜歡 MyBatis,就是因為它夠簡單。總之,歡迎發 PR!」(4↑)
授權與併入官方 文件站漏標授權;併進 MyBatis 官方短期無解 「它是 Apache 2.0,repo 裡有 LICENSE 檔,但文件站從沒提到,這是我的疏失,我會補上。至於併進 MyBatis 主專案,目前我覺得不可能,因為 MyBatis 很多功能都依賴 reflection。」(2↑)

社群溫度計

熱度 標題 一句話
168↑ 我用 Java 寫了一款 Android 即時戰略遊戲 開發六年終於上架 Google Play,純 Java 打造的中世紀 RTS。
60↑ Native Image 還是 JIT?GraalVM 作者說「JIT 只在必要時才用」 22 分的提問炸出 40 則留言,兩條執行路線之爭再燒一輪。
38↑ 有人把 Compose Multiplatform 跑上了 Apple tvOS JetBrains 的 tvOS artifact 長期未解,他自己 port 完發上 Maven Central。
32↑ JVM 在 Kubernetes 的 CPU 與記憶體 requests/limits 4 GiB heap 要的容器不只 4 GiB,小數點 CPU limit 還會改變 JVM 行為。
29↑ IntelliJ plugin 直接標出程式碼哪裡在做 I/O review Spring Boot 程式時,不必再一路追呼叫鏈找 repository。
19↑ Vert.x Event-Loop 對決虛擬執行緒:Jactl suspend/resume 跑分 阻塞操作數量拉高後,兩種併發模型的吞吐量誰先撐不住。
13↑ Kotlin Toolchain 0.12:Multiplatform 函式庫發布與 Wasm apps JetBrains 官方 release,順帶回應了社群對 Gradle 的長期怨念。
0↑ / 19💬 會告訴你「成功與失敗是哪一種」的 Kotlin Result<T, E> 0 分卻累積 19 則留言,典型的 API 設計爭議串。
本文由 Claude 自動匯整,非人工撰寫