Несмотря на наличие AwesomeJavaFX, я всё же решил оформить статью с небольшой коллекцией заметок. Подобного дополнения к набору ссылок и туториалов мне когда-то очень сильно не хватало, из-за чего было потрачено очень большое количество времени моей жизни.

  1. Русскоязычные ресурсы
  2. Архитектура тулкита и api
  3. Инструменты разработки
  4. Разные полезные ресурсы
  5. Заметки о JavaFX-приложении

Предполагаю, что вы знакомы с общим описанием тулкита и его устройством: JavaFX Architecture или в рамках туториалов\книг в разделах AwesomeJavaFX, который я не буду стараться дублировать или википедии и т.п. обзорным материалам. В дополнение к англоязычным урокам вспомним русскоязычные.

Русскоязычные ресурсы

Русскоязычной литературы мало:

Список англоязычной литературы есть на гитхабе.

Архитектура тулкита и api

C Java 11 JavaFX отделен от JDK, есть собственный сайт https://openjfx.io

Скорее всего вам будут постоянно попадаться туториалы прошлых лет, то отмечу несколько важных докладов, которые помогут адаптировать легаси-код:
Доклад от Kevin Rushforth и Jonathan Giles (2016): JavaFX: New & Noteworthy
https://static.rainfocus.com/oracle/oow16/sess/1462485593256001c2xn/ppt/JavaFX%209%20-%20New%20and%20Noteworthy.pdf о чрезвычайно важных нововведениях в 9 версии.

Building JavaFX UI Controls
https://static.rainfocus.com/oracle/oow16/sess/1462484351438001p6a1/ppt/Custom%20Controls.pdf об устройстве контролов и кастомизации.

Особенно полезен проект с альтернативной документацией, много нюансов: JavaFX Documentation Project

Существует вики: https://wiki.openjdk.java.net/display/OpenJFX, где можно найти также и ссылки на css, fxml документацию. Разве что некоторые устаревшие ссылки запрятаны достаточно глубоко, хотя все еще могут быть чем-то полезны, например, эта статья о производительности: https://wiki.openjdk.java.net/display/OpenJFX/Performance+Tips+and+Tricks

Репозитории тулкита:
https://github.com/openjdk/jfx
http://hg.openjdk.java.net/openjfx

Багтрекер, на котором вы скорее всего будете очень частым гостем.

Рассылка для разработчиков: http://mail.openjdk.java.net/pipermail/openjfx-dev/

Dirk Lemmermann поддерживает один из ключевых сайтов инфраструктуры JFX-Central.

Инструменты разработки

Некоторые ссылки переместились с сайтов Oracle на сайт Gluon. Закономерно опасение разработчика что-то скачивать с другого сайта, но это действительно так: Bye Bye JavaFX Scene Builder, Welcome Gluon Scene Builder 8.0.0

Ключевое приложение JavaFX-инфраструктуры графический FXML-редактор SceneBuilder теперь живет на сайте Gluon: https://gluonhq.com/products/scene-builder/ Программа имеет несколько противных багов, но вполне работоспособна. Также он встроен в IDE, однако у многих работает проблемно с такой интеграцией.

На этом же сайте можно скачать и либы JavaFX раннего доступа в обход Maven-репозиториев: https://gluonhq.com/products/javafx/

Один из очень полезных инструментов Scenic View, программа для просмотра графы сцены с выводом различной информации об узлах: https://github.com/JonathanGiles/scenic-view. С программой раньше были определенные проблемы и после добавления отладочного окна в собственное приложение я практически её не использую. Большая часть задач сводится обычно к перерасчету различных координат узла в родительские координаты, сцены в экран и т.п., что делает просмотр геометрии узлов жизненно необходимым. Также множество проблем может быть со стороны CSS, здесь она тоже поможет.

Разные полезные ресурсы

Некоторые блоги стоит вспомнить еще раз, хотя бы они и есть в awesome-списке, например:

Christoph Nahr, есть очень полезные заметки, о нюансах масштабирования интерфейса и т.п.

Carl Walker, много примеров более сложных биндингов, привязка размеров списков, логические операции, биндинг на пустые строки и т.п. С биндингами можно сделать много разных удобных вещей.

Gerrit Grunwald и его репозитории github.com/HanSolo, в которых можно найти много полезного, особенно по кастомным контролам, я и сам в них часто копался.

Dirk Lemmermann, также множество полезных идей и реализаций функционала.

Немного статей также есть и на https://www.javacodegeeks.com/tag/javafx

Обзор WebView: JavaFX WebView Overview

Запуск на Raspberry Pi 4: Liberica JDK 13 EA With OpenJFX 13 On The Raspberry Pi 4

Альтернативный билд OpenJDK, содержащий OpenJFX: Zulu

Заметки о JavaFX-приложении

Вспомню несколько практических моментов, особенно если вы не используете Spring, другие тяжелые зависимости, хотите поэкспериментировать с архитектурой, получить опыт или просто построить приложение с нуля ради фана.

Тулкит обладает очень большой гибкостью в построении приложения и разные задачи можно решить различными способами. Для самого простого приложения вполне достаточно самой примитивной архитектуры с использованием всех возможностей FXML, которые на самом деле даже шире (переменные, вычисляемые значения и т.п.), чем их поддерживает SceneBuilder. Это позволяет быстро создать прототип, однако переход его на более высокие уровни организации требует уже продумывания минимальной архитектуры.

Если вы создаете приложение с нуля, то оно склонно постепенно разрастаться и расслаиваться, требуя выделения ядра фреймворка, общей логики для приложений этого класса (в нашем случае десктопных) и специфической предметной логики, которая решает специфические задачи каждого конкретного приложения. Если этого не сделать, то реиспользование кода в будущем станет проблематичным, хотя архитектура в целом упростится. С другой стороны, на уровне прототипа такое разделение часто может быть избыточным и ничем хорошим не закончится, поскольку неизвестно, выживет ли написанный код или нет, а в IDE из мира Java очень хороший рефакторинг.

Как правило, любое десктопное приложение начинает сочетать в себе признаки фронтенда и бэкенда. Если задача более-менее серьезная, то на самом раннем этапе есть смысл продумать управление зависимостями, поскольку это определит большую часть архитектуры. Это важно для сквозной логики и стыков слоев приложения (например, контроллер-модель), любые переделки в этих местах потребуют поломки всего приложения. В менее критичных участках излишнее усердие по пробросу зависимостей может даже навредить, сильно всё запутывая.

Для простого приложения IoC-контейнер может оказаться излишним. Напомню, что есть книга на эту тему: Марк Симан, Внедрение зависимостей в .NET, принципы которой вполне универсальны и относятся не только к C#, хотя некоторые моменты достаточно спорны. Здесь есть шанс все усложнить и запутать, получив на выходе более худший результат, чем без управления зависимостями вообще, любое усложнение требует осторожности.

Наряду с разными подходами, в самом простом случае можно выбрать иерархичную архитектуру "служб" (я взял слово в кавычки, ибо каждый автор понимает его по-разному): в главном классе или билдере собрать все зависимости, например, логгер, конфиг и т.п., передать контроллеру, который передает их всем остальным - вложенным контроллерам, моделям и т.п.

Однако этот простой способ, кроме инициализации сразу большого графа объектов имеет и еще один очень существенный недостаток: появляется вопрос об уровнях вложенности служб друг в друга, поскольку количество их всегда будет расти, требуя наличия агрегатов.

Это одна из отложенных архитектурных проблем, например, вы имеете менеджер языковых ресурсов и определяете его на поле в каком-либо рутовом компоненте вашего приложения, передавая его остальным. Далее появится менеджер локали, времени и т.п., что входит в интернационализацию. Так будет постепенно расслаиваться каждая "служба", которая была когда-то одним классом, но т.к. управлять и уследить за огромным числом зависимостей невозможно, то большое их количество требует агрегата, который не делает ничего, лишь организуя следующий уровень вложенности: getLocaleManager() превращается в getI18n().getLocaleManager() или в сложных случаях спасает положение, организовывая между ними какое-то взаимодействие. Поэтому часто встречается подход - делать подобные агрегаты сразу, не ломая в будущем код.

Несложно догадаться, что с ростом уровней вложенности растут и проблемы использования api и читаемости кода. У приложения появляется потолок, при достижении которого нужно будет объединять ответственность классов, что приведет к другим проблемам. Еще одной проблемой может быть сама передача зависимостей другим компонентам. Какой бы способ не был использован, но все равно корректнее вызывать сеттеры у собираемого компонента, что, конечно же, можно сделать рефлексией. Если по каким-то причинам рефлексия невозможна, то это также ограничивает количество зависимостей, иначе каждый раз нужно будет править эти билдеры\фабрики.

Несмотря на множественные недостатки, такая структура может неплохо сработать для небольшого приложения и в этом случае имеет больше преимуществ, нежели сложный в сопровождении и отладке контейнер зависимостей, который имеет уже свои собственные проблемы и ограничения, да и легковесных альтернатив в Java-мире не особо много. Она хороша и тем, что вписывается в логику работы мышления - при поиске апи сначала отнести его к какой-то области, например, графике, затем к частности, например, менеджер иконок и т.п. что удобно при подсветке методов в IDE. Она проста и интуитивна, легко отлаживается, гибка, кроме того, способна предотвращать некоторые архитектурные ошибки.

Поскольку множественного наследования нет, то скорее всего основные компоненты фреймворка будут наследоваться от других и слою предметной логики будет проблематично настраивать их. Здесь возможен хак: пропустить иерархию наследования через предметную логику, завязав на ней ядро фреймворка. Это самый простой способ усложнит отделение библиотек (разве что может выручить Service Provider Interface, он же SPI), но решит часть проблем. Другой вариант, часто порицаемый Service Locator и его варианты, что уже недалеко и от DI-контейнеров. Тогда при невозможности наследования можно пробросить зависимость через него.

Какое бы решение вы не выбрали - использовать IoC-контейнер, простой проброс объектов друг другу или гибридный вариант, сочетающий все сразу, но это решение будет критичным для приложения и сильно отразится на его будущем, определяя иерархию самых базовых классов приложения\фреймворка от которого будут наследоваться остальные компоненты - контроллеры, модели, сервисы и т.п.

В рутовой иерархии можно легко получить хрупкий базовый класс, а использования инжекции в конструктор при обязательном переопределении конструкторов в Java может привести к внезапной поломке конструкторов всей иерархии при изменениях. Можно использовать Lombok и подобные проекты в базовых компонентах, но обратной стороной будет завязка сквозной логики на стороннюю библиотеку, проблемы с которой приведут ровно также к масштабной поломке всего приложения.

Здесь можно получить массу проблем и нужно тщательно взвесить преимущества и недостатки выбранного решения. В конце концов, вы можете вообще не заниматься никакими зависимостями, инстанцируя все самые важные службы\сервисы в коде или используя статику, но любые архитектурные решения имеют свои недостатки и нужно все равно подумать о последствиях.

Типичный JavaFX-туториал использует, по крайней мере, один контроллер окна. У более сложного приложения в случае наличия только одного контроллера главного окна могут быть самые различные проблемы, например, при наличии таймера, при работе трея или виджета кто-то должен управлять сообщениями между ними и главным окном, особенно если какое-то окно скрыто. Если контроллер главного окна будет управлять еще и треем, то это усложняет его использование как простого FXML-контроллера. А если скрыты все окна и приложение имеет настройку не загружать окна вообще, то это еще проблемнее.

Одним из самых простых вариантов может быть выделение главного контроллера приложения, который управляет всем остальным, но не содержит в себе контролов или чего-то связанного с интерфейсом. Обратной стороной такого решения будет, конечно же, усложнение обмена событиями между контроллерами. Это первые намеки на то, что логика управления окнами и FXML-контроллерами будет особенно неприятным местом, поэтому неплохо бы определиться с окнами и FXML.

Я наблюдал различные подходы к FXML: кто-то активно использует создание контролов только в коде, кто-то лишь в случаях небольших открываемых окон\диалогов, а в остальной части шаблоны. У каждого решения есть свои преимущества и недостатки для разных ситуаций. Полагаю, что для большинства (в т.ч. и для меня) сильной стороной тулкита является скорость прототипирования и необходимость постоянного редактирования интерфейса, что требует FXML в связке со SceneBuilder для основных окон и создание из кода для простого функционала, где загрузка fxml-шаблона неоправданна.

Очень удобным решением в этом случае будет и композиция контроллеров, с помощью которой можно привязывать отдельный контроллер к табу в TabPane, разгружать особо толстенькие контроллеры и т.п. Это очень удобно, однако сильно влияет на архитектуру. Вам нужно инициализировать или управлять этими контроллерами: через фабрику в FXMLLoader, либо вручную.

В случае FXML есть смысл сразу определить границы его влияния на приложение: слушатели можно установить как в FXML, как и в коде на сам контрол. У каждого метода свои преимущества и недостатки, как всегда. Поэкспериментировав, я убрал все привязки вызовов методов из FXML, переместив их в код, чтобы не разбрасывать логику, однако это не подходит для специфических случаев, например, для возможности изменения вызова метода путем редактирования fxml-шаблона, что намекает на продумывание местоположения FXML - или jar-файл или в какой-нибудь директории.

После перебора разных вариантов деплоя я остановился на разделении кода и ресурсов, все ресурсы (в т.ч. и fxml-файлы) хранятся в директории данных. Такой подход сильно облегчает фикс ошибок и обслуживание приложения, вплоть до быстрого изменения интерфейса и его поведения за счёт правки fxml. С другой стороны, для приложения, требующего безопасности этот вариант выглядит очень опасным. Я не нашел каких-либо материалов о возможных уязвимостях через шаблоны и css, но этого явно стоит опасаться и наверное есть смысл условно признать их уязвимыми, учитывая возможности и тесную интеграцию с кодом.

В FXML можно привязать не только методы, но использовать интернационализацию в виде %ключа, а также пути к изображениям. Если у вас будет какой-нибудь менеджер языковых ресурсов, то нужно как-то передавать их в шаблоны. А вот с изображениями проблемнее, особенно в CSS-теме. Тулкит имеет функцию автомасштабирования JavaFX Tip 27: HiRes / Retina Icons через постфикс @2x в имени файла изображения. Однако если приложение имеет настройку размеров иконок или как-то управляет ими, то пути в FXML могут многое осложнить, хотя они чрезвычайно удобны для быстрой разработки. Поразмыслив так и желая подстраховаться на случай различных проблем и багов тулкита на разных системах, я полностью убрал иконки из FXML, переместив работу с любой графикой в код, но опять таки, это может быть не всегда удобным.

На этом этапе также может потребоваться определить и структуру директорий данных программы, как их хранить и где. Это может отвлечь на реализацию конфигурации, интернационализации, графики и обработки командной строки (у Application есть getParameters, которого может быть достаточным для очень простых случаев).

Конечно же, на этапе прототипа неверно определенное api скорее просто приведет к потери времени, о чем следует помнить и не заморачиваться особо. С другой стороны, любое изменение в последующем будет порождать регрессионные баги, поэтому основные вспомогательные службы\сервисы есть смысл продумывать сразу.

Хорошо себя зарекомендовала валидация конфигов и проверка директорий данных. Пользовательские или любые другие важные данные обычно склонны бэкапиться, что намекает на их отделение.

Если есть задача полностью портабельного приложения, то нужно помнить о директориях, которые неочевидно создает запуск приложения в системе: директория с кешируемыми либами, сохраняемые настройки LocalStorage из WebEngine, для изменения которой есть setUserDataDirectory.

Приложение усложнится и может потребоваться отладка. CLI может очень сильно сэкономить время, например, через переопределение ключей конфига, позволяя создавать различные конфигурации запуска для приложения. С другой стороны, для приложения, требующего безопасности, это может привести ко множественным уязвимостям.

Однако большое количество багов будет не только в логике, но и в интерфейсе. Вы можете использовать либо ту же ScenicView, либо как я, разработать свой вариант отладочного окна, который позволяет быстро просмотреть структуру контрола или изменить его геометрию.

На фоне усложнения логики все больше будут проявляться мелкие недоделки тулкита, имея тенденцию сливаться в большие проблемы. Даже на самом раннем этапе есть смысл сразу выделить хелперы\службу\сервис, которая будет ответственна за фиксы контролов.

После Java 9 появились сильные ограничения в рефлексии, в некоторых случаях может потребоваться расковырять исходники контрола и выносить логику в свои классы. Есть и другой путь - через кастомизацию контролов. Некоторые улучшения, например, контекстные меню или копирование текста Label можно реализовать кастомным контролом, недостаток - необходимость добавления их в SceneBuilder, многочисленность таких кастомных контролов с неудобством каждый раз подменять их в fxml или создавать их кода. При этом в менее важных окнах некоторым функционалом можно и пожертвовать.

Идея такой службы фиксов у меня появилась отсюда: Команда плагинов для настройки JavaFX компонент в настольном приложении. Сам концепт универсален и полезен для любого тулкита вообще, не только десктопного. Конечно же, на этапе прототипа такое расслоение проблематично, но в будущем способно сэкономить много времени, как и получить трудноуловимую утечку памяти за счет затерянной жесткой ссылки на слушатель или т.п. и за чем нужно следить. Еще один недостаток - возможность очень сильно запутать и осложнить борьбу с багами за счет дополнительной прослойки кода с фиксами, проблемы в котором перемешиваются с проблемами самого тулкита.

Где-то в районе появления множества контроллеров у вас будет большое количество багов и растущее приложение потребует структурирования и компенсации еще одной особенности ООП - взаимодействие между объектами, расположенных достаточно далеко друг от друга.

Обычным наблюдателем это реализовать проблематично, нужно пробрасывать очень много зависимостей. Я выбрал шину сообщений, как наиболее простой вариант, сначала на Guava: EventBusExplained, а затем заменил на свою реализацию, ибо сложной многопоточности у меня не требуется и за счет наличия Platform.runLater можно сильно упростить передачу сообщений, сделав отладку и контроль за ними более удобными, ну и удалить лишнюю зависимость. Конечно же, шина также имеет множественные недостатки и способна запросто превратить жизнь в событийный ад. Поэтому для относительно простых приложений шина может создать больше вреда, чем пользы, да и последующее удаление шины из приложения очень и очень трудоёмкая задача, что создаёт основной риск.

В конце концов, начнутся проблемы с багами и в различных состояниях приложения. Если вы удачно начали приложения с TabPane и вложенными контроллерами выше, которые нужно запускать и останавливать, а также если кейсом было приложение, где есть смена окон или состояний (игра, тесты и т.п.), то этот вопрос поднимется сразу.

Есть разные варианты, например, из мира C#: Простые стейт-машины на службе у разработчика, однако моя велосипедная собственно-реализованная стейт-машина на графах оказалась слишком сложной и неудобной для повседневного использования, хотя бы и помогла поймать у одного из моих пользователей двойной клик поломанной мышки. В некоторых случаях удобнее замаскировать TabPane, удалив хидер и вручную переключая табы, создавая видимость смены окна, это костыльно, но вполне работающий вариант, конечно же, до определенного момента.

Что касается структурирования самих контролов, то я пробовал придумать какие-то правила работы с ними в методах инициализации контроллеров, определяя строгий порядок их настройки, разделяя комментариями и т.п., но все идеи оказались нежизнеспособными или тратили время впустую не давая особых выгод. Пока этот вопрос остается для меня открытым. Как правило, любая библиотека, которая должна что-то одно облегчать - усложняет что-то другое, например, отладку, понимание кода и т.п., что плохо подходит для повсеместного применения.

В разных ситуациях может потребоваться освобождение ресурсов контроллером. Можно объединить управление состояниями контроллеров и события окна, например, при его закрытии останавливая контроллер и освобождая ресурсы, если окно больше не будет использовано. Однако в этом случае рождается тонкий баг для вложенных контроллеров, состояния которых могут переключаться некорректно если они также должны иметь доступ к окну.

Большое количество багов потребует валидации объектов, заняться валидаторами стоит пораньше. В части JavaFX валидация очень полезна для проверки @FXML полей контроллеров на null, что бывает чрезвычайно часто из-за постоянной правки шаблонов. SceneBuilder может сгенерировать такие проверки, однако при частом изменении полей этот метод нужно будет постоянно корректировать, а это жутко неудобно.

Валидатор может многое упростить и сэкономить время на поиск бага. Это была одна из моих первых ошибок при знакомстве с тулкитом. Я решил отложить валидаторы на попозже, начав работать над менеджером самодиагностики приложения. У меня появились правила, например, проверки лога на ошибки, ключей в конфиге и т.п. и менеджер, которые ими управляет. После появления пакета валидаторов оказалось, что логика этих правил диагностики дублирует логику валидаторов и пришлось сильно все менять, конечно же получая регрессионные баги где только можно. Здесь грех не использовать валидацию бинов, я, например, перевел приложение на Jakarta Bean Validation API, однако не желая тянуть в зависимость приложения громоздкие валидаторы, некоторые простые проверки пришлось реализовывать вручную. С другой стороны, апи завязывается на пакет валидаторов, усложняя реиспользование кода.

Можно ли валидировать FXML, учитывая потенциально бесконечные варианты рантаймовых в нем ошибок? В 9 версии появился javafx.fxml.LoadListener, который можно использовать для перехвата парсинга и проверки, например, путей иконок. Однако есть проблема в большом количестве окон и файлов fxml-шаблонов, привязанных к ним, а в каждом из них может быть потенциальная ошибка. В конце концов, не найдя более лучшего способа, я просто добавил в режиме дебага валидатор, который вхолостую прогружает все FXML-файлы, пытаясь перехватить эксепшен и уведомляя о баге. Способ достаточно затратен и может быть проблемным за счет инклюдинга и т.п. фич FXML.

Большое количество разнообразных валидаторов и сложность их иерархии (как и любая подобная логика) может на поздних этапах захотеть многопоточности из-за какого-то особо тугодумного валидатора. Добавление многопоточности в любую развитую или сложную логику может потребовать полного перепроектирования, очень и очень болезненного, так что нужно всегда помнить об этом риске.

Рано или поздно потребуется работа с базой данных. При этом вскроются недостатки Java как интерпрайзного языка и малое количество актуальных легковесных библиотек еще и с учетом того, что в десктопных приложениях очень активно используется SQLite. Конечно же, можно взять любую библиотеку, однако зависимость в приложении автоматически ставит вас в зависимость от этой зависимости и тем больше зависимость, тем больше ваша зависимость, такой вот каламбур.

Например, при переходе на Java 9 у меня умерла ORMLite. Все же десктопное программирование в мире Java развито очень слабо, в условиях бедности на альтернативы это было достаточно катастрофичным событием и был выбор: пересобирать ORMLite, меняя имена пакетов для исключения конфликта либо удалить её и разработать примитивную замену.

Вместе с логикой доступа к данным потребуется активное взаимодействие с пользователем - диалоги, подсказки и т.п. Например, вывод в диалог эксепшена при неудачном добавлении в базу данных, вывод ошибки доступа к файлу и т.п., что сильно ускоряет отладку. Эта служба\сервис начинает играть очень важную роль, создавая паттерн: оборачивая логики в try..catch с логированием и последующей передачей исключения в службу диалога для показа его пользователю.

Учитывая потенциальные проблемы с Drag-and-drop на разных операционных системах и прочей платформозависимости (буфер обмена и т.п.) есть смысл, по возможности, дублировать критичный или платформозависимый функционал.

Баг может родиться совершенно неочевидным способом, например, открытие кастомного ColorPicker вызывал падение одного из рабочих столов на Linux, просто убивая его, а DnD временами просто переставал работать.

Возможность наткнуться на баг с загрузкой процессора намекает на необходимость предусмотреть отключение анимации и т.п.

Например, можно учесть невозможность открытия пути в системном обозревателе и добавить возможность его копирования либо заменить на сочетание кнопки (или аналога) и текстового поля, из которого можно скопировать текст. Пострадает дизайн, но появляется альтернативный способ решения проблемы, хотя бы и в ручном режиме. Если же сделать путь в виде некопируемой ссылки, то в случае невозможности её открытия в системе о вас очень плохо вспомнят.

Что касается украшательств, то CSS-тему возможно собрать через Sass. Не могу сказать о совсем новых Dart-версиях, которые используют модульный подход, но такая сборка вполне удобна, особенно вместе с вотчером, который перегружает файл темы.

Весьма хорошо себя зарекомендовало окно в приложении со всеми контролами темы, которое можно открыть для отладки и проверки ошибок после правки CSS-файла. С другой стороны, если какая-либо дополнительная проверка FXML-файлов осуществляется на этапе запуска приложения, то такой убер-шаблон может потребовать много ресурсов.

Так как кроссплатформенные баги потенциально бесконечны, то в приложении есть смысл продумать хорошую систему логирования и перехвата ошибок, чтобы случайно не потерять исключение, это очень критичный участок. Стоит помнить, что исключение может прилететь из JavaFX-потока, причем несколько подряд.

В условиях современной Java можно создать модульное приложение. При этом в module-info может потребоваться открыть свои контроллеры к javafx.fxml для использования FXML-аннотаций. Разброс контроллеров по пакетам осложняет данную не очень веселую процедуру. Это касается и других случаев, например, заполнения JavaFX-таблиц через рефлексию, а может повлиять и на систему плагинов, например, на интеграцию Groovy.

Здесь также появляется новая беда: всяческие вариации модульных конфликтов библиотек с приложением, друг с другом и просто так. На компиляцию, а особенно на запуск JavaFX приложения могут влиять очень много факторов: модульность, сборщик, IDE и т.п., создавая большой количество комбинаций.

В случае проблем проще держаться проверенного алгоритма: заставить работать заведомо рабочий код из репозиториев примеров либо с сайта тулкита. А после получения работоспособного примера натягивать на него свой код, ломая его, не наоборот.

Если случайно взятая библиотека сложная, давно не обновлялась или же поддерживает высокий уровень совместимости со старыми версиями Java (а обычно так и делается), то нужно ждать беды. Есть различные плагины для автоматической модуляризации, например, moditect, extra-java-module-info и т.п. А можно просто удалить модульность из приложения.

С другой стороны, модульность снимает давнюю проблему JavaFX - сложность деплоя. Плагины навроде badass-jlink-plugin могут создавать легковесные рантаймовые JVM-образы со скриптами запуска в комплекте, делать установщики, паковать в архив и прочее.

Конечно же, с некоторыми из этих проблем, перечисленных выше, можно и не столкнуться, а для кого-то это не является проблемой вообще, особенно при выборе иной архитектуры или добровольном принятии рисков. Вариантов здесь очень много и всё зависит от задач приложения и конкретной ситуации.

Но знакомство с потенциальными сложностями все равно может быть полезным, например, для прогнозирования других неприятностей по аналогии. Так или иначе, но надеюсь это поможет вам меньше наступать на грабли и сэкономить своё время.