Showing posts with label issue. Show all posts
Showing posts with label issue. Show all posts

Monday, August 28, 2023

IntelliJ - the Community Edition rescued the broken Ultimate Edition . . . ;)

It sound funny to say that - IntelliJ Community Edition (free edition) rescued Ultimate Edition (paid edition). But that's what really put me back on Ultimate Edition after many months of switching to Community Edition for my day-to-day development.

Environment: IntelliJ Community Edition 2023.2.1IntelliJ Ultimate Edition 2023.2.1, maven 3.9.3 on macOS Catalina 10.15.7

The Issue

I stopped using IntelliJ Ultimate Edition as for some weird reason it got stuck with maven dependencies broken paths issue. Specifically, SLF4J and javax libraries were shown in maven dependency broken paths in red. It happened few months ago. One day one of my maven projects that uses Lombok and had classes annotated with @Slf4J suddenly couldn't recognize the log (logger object) statements. When I checked project module dependencies there were broken maven dependency paths.

I tried all recommended and possible ways to recover from this issue like: Invalidate Caches and restart the IDE(A), blowing away specific libraries maven cache ~/.m2/libraries/.../*.* and letting it build again, blowing away entire maven local cache ~/.m2/*.* and letting it build again, downloading dependency jars and explicitly setting dependency paths in the project/module settings, reinstalling the Ultimate Edition, even reinstalling different version of the Ultimate Edition etc. Nothing worked. I spent lot of time few times since then and couldn't get it back to working. Finally gave up and moved to Community Edition. Same projects that were having broken dependency issues in Ultimate Edition, when opened in Community edition, had no issues with those dependencies, same maven local cache paths are used by both. The Ultimate Edition complains, the Community Edition doesn't. Alas!

The Fix

Here is what I did to fix it.

Started IntelliJ Ultimate Edition. At the startup there is a Customize link, click that and click Import Settings... link as shown below:

Selected the Community Edition settings directory (~/Library/Application Support/JetBrains/IdeaIC2023.2) of Community Edition in which my projects were fine with dependencies. It prompted to take a backup of current settings. I did that and imported ommunity Edition settings. It opened projects and the issue of broken dependency paths was gone.

It seemed like, there was some broken dependency path setting saved in the Ultimate Edition settings that was stuck and not getting fixed by any means.

Some Internals

IntelliJ saves all it's settings under the specific version's area. On Mac, by default, this area is under ~/Library/Application Support/JetBrains dir. The community edition directories start with IdeaIC<version> (e.g IdeaIC2023.2) and ultimate edition directories start with IntelliJIdea<version> (e.g. IntelliJIdea2023.2). The options sub-directory is where plugins settings get saved. 

Plugin Settings

Plugin settings are pat of IntelliJ settings and get stored in xml files under options sub-directory of specific IntelliJ version's settings directory. For instance, awesome editor plugin is a simple and pretty neat plugin which lets add image backgrounds. I did setup plugin awesome editor to display different kinds of images for different types of files. To get all the settings from one IntelliJ version to another, just copy the plugin settings file (in this case it is: awesome-editor-3.xml). 

Here is an example to copy plugin settings set in Ultimate edition to Community Edition.
NOTE: Once copied restart IntelliJ Community Edition.

$ cd ~/Library/"Application Support"/JetBrains $ ls -al |grep IdeaIC drwxr-xr-x 17 pottepalemg 163264107 544 Jul 26 2022 IdeaIC2022.1 drwxr-xr-x 20 pottepalemg 163264107 640 Nov 30 2022 IdeaIC2022.2 drwxr-xr-x 20 pottepalemg 163264107 640 Mar 30 15:23 IdeaIC2022.3 drwxr-xr-x 20 pottepalemg 163264107 640 Jul 7 11:31 IdeaIC2023.1 drwxr-xr-x 20 pottepalemg 163264107 640 Aug 28 10:13 IdeaIC2023.2 $ ls -al |grep IntelliJIdea drwxr-xr-x 22 pottepalemg 163264107 704 Aug 24 15:43 IntelliJIdea2020.1 drwxr-xr-x 19 pottepalemg 163264107 608 Nov 19 2020 IntelliJIdea2020.2 drwxr-xr-x 21 pottepalemg 163264107 672 Mar 2 2022 IntelliJIdea2020.3 drwxr-xr-x 20 pottepalemg 163264107 640 Jul 9 2021 IntelliJIdea2021.1 drwxr-xr-x 24 pottepalemg 163264107 768 Feb 9 2022 IntelliJIdea2021.2 drwxr-xr-x 25 pottepalemg 163264107 800 Apr 12 2022 IntelliJIdea2021.3 drwxr-xr-x 26 pottepalemg 163264107 832 Sep 23 2022 IntelliJIdea2022.1 drwxr-xr-x 25 pottepalemg 163264107 800 Nov 30 2022 IntelliJIdea2022.2 drwxr-xr-x 25 pottepalemg 163264107 800 Jun 28 13:26 IntelliJIdea2022.3 drwxr-xr-x 24 pottepalemg 163264107 768 Aug 16 15:40 IntelliJIdea2023.1 drwxr-xr-x 22 pottepalemg 163264107 704 Aug 28 10:36 IntelliJIdea2023.2 drwxr-xr-x 11 pottepalemg 163264107 352 Aug 25 15:35 IntelliJIdea2023.2-backup $ find . -name awesome* ./IntelliJIdea2023.2/options/awesome-editor-3.xml $ ls -ltr ./IdeaIC2023.2/options | grep awesome* $ cp ./IntelliJIdea2023.2/options/awesome-editor-3.xml ./IdeaIC2023.2/options

TIPS

Getting back lost database connection settings into Ultimate Edition

IntelliJ Ultimate Edition comes bundled with Database plugin that supports all features that are available in DataGrip (an SQL IDE which is a product of JetBrains). I had database connections set to connect to various PostgreSQL databases (local, int, cert, prod etc.) which I lost by importing Community Edition settings. But I had a settings backup prompted and done for the Ultimate Edition settings that my broken Ultimate Edition was setup with when I imported Community Edition Settings. The backup directory is also listed in the above list of Ultimate Editions setting directories. To get those settings back onto my new settings imported from Community Edition, I had to repeat the Customize > Import Settings... step two more times. First time pointing it to the backup directory and selecting and copying all database connections settings to the clipboard. Second time pointing it to the Community Edition Directory and pasting those connection settings from the clipboard. This was the only way I could get those database connections copied. I got all connection settings except passwords fro every connection. I had to set password for every single connection individually. This was not possible by simply copying xml files like I did for awesome editor plugin.

Summary

No software application or tool is bug-free. Applications do crash, tools do get corrupted. There is no one solution that works or fixes a similar issue for everybody. Some stupid, nasty, unknown, not very well documented internals of tools do take up lot of time to discover a fix that works for your situation and may help some others who get into similar situation.

Hope this blog post on my discovery saves someone's time sometime when that someone bumps into it.

Thursday, January 26, 2023

Spring Boot - WebTestClient Gotcha . . .

The Scenario

Recently I had a scenario to write an integration test in a Spring Boot micro-service (say MyService) which hosts it's own data in it's own PostgreSQL database in order to verify it's data against another Spring Boot micro-service which hosted master data in it's own database. The database contains a specific entity data set (say Item), a fixed set of items, used for a specific purpose. Another micro-service (say OtherService) uses one of the end-points of MyService by passing entity ids (Item ids) for some processing that MyService is capable of. The OtherService hosts master data of those Entities (Items) in its own database. But, the data hosted for each entity by both services is entirely different, and each has its own business with it's own data set. Only ids are common. OtherService data is considered master data. So, a MyService integration test requires to make sure that there are no entities missing in it's database. OtherService, offers an end-point to get it's hosted Item entities.

Environment: Java 17, Spring Boot 3.0.2, maven 3.8.5 on macOS Catalina 10.15.7

So, WebTestClient seemed like better choice to leverage to make an API request and get the master list of Items and check it's database to see if the count of Items and list of Item ids match.

In MyService, there was an integration test already in place written to check most of it's lookup like database entities; one test method for testing each entity's data set. This was a natural integration test to extend by adding another integration test method for testing Item entity set. But this one goes beyond it's database, out to OtherService, making an API request to to get the master list of Items to verify against.

The Issue

Simply Auto-wiring WebTestClient like:
@Autowired privateWebTestClient webTestClient;

broke the auto-configuration by the following exception:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.hmhco.sgm.scoring.api.persistence.LookupRepositoriesDataIT': Unsatisfied dependency expressed through field 'webTestClient'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.test.web.reactive.server.WebTestClient' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.test.web.reactive.server.WebTestClient' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

The Solution

A quick Googling suggested to try the following, which actually worked (NOTE: WebTestClient requires org.springframework.boot:spring-boot-starter-webflux dependency which we already had in MyService app pom.xml:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

But this solution brings up the embedded web server (Tomcat) starting it at a random port which is actually unnecessary for this test. All I need is an instance of WebTestClient to make a HTTP Get request to OtherService end-point.

Here is the code snippet for this solution:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("test") @RunWith(SpringRunner.class) public class MyServiceDataIT { @Value("${spring.other-service-api.get-Items-end-point}") private String apiEndPoint; @Value("${spring.other-service-api.trusted_token}") private String trustedToken; @Autowired private WebTestClient webTestClient; ... @Test public void itemLookup_has_no_missing_items() throws Exception { // given: request spec var requestSpec = webTestClient.get() .uri(apiEndPoint) .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) .header(HttpHeaders.AUTHORIZATION, trustedToken); // expect: end-point used to get items, succeeds and get JSON response body var response = requestSpec.exchange() .expectStatus().isOk() .expectBody(String.class) .returnResult() .getResponseBody(); // and: convery JSON response to List List<Map<String, String>> items = new ObjectMapper().readValue(response, List.class); // verify: items size matches with what we have in database ... // verify: item ids match with what we have in database ... }


Little more Research

But a follow-up reading of Spring Boot Documentation made me dig bit deeper. Actually, I did not need a full web environment to be up with embedded servers started and listening on random port that the above change brings in, which also satisfied auto-configuration need for WebTestClient.

Spring Boot auto configuration has a set of annotations with which you can pick and chose the ones that you really need. Apparently there is one for WebTestClient,  @AutoConfigureWebTestClient. That sounded like the way to go instead of making having the complete test web environment up and running.

So, thought the following would work, but it also failed with the above, same exception:
@SpringBootTest @AutoConfigureWebTestClient @AutoConfigureWebFlux @ActiveProfiles("test") @RunWith(SpringRunner.class) public class MyServiceDataIT { @Autowired private WebTestClient webTestClient; ... }

After little more investigation, I found that we do have dependency: spring-boot-starter-hateoas that Spring Boot Documentation clearly warns on this saying it is meant to be specifically for Spring MVC and should not be used with WebFlux. The alternative in this case is to use: org.springframework.hateoas:spring-hateoas instead. Well, tried that too with the above annotations, but ended up with the same exception.

Better and cleaner solution

The better and cleaner solution is not to have the embedded web server started, but WebTestClient instance created to make a HTTP Get request. So the obvious solution is to ditch dependency injection for WebTestClient and create an instance.

The following is the code snippet:
@SpringBootTest @ActiveProfiles("test") @RunWith(SpringRunner.class) public class MyServiceDataIT { @Value("${spring.other-service-api.get-Items-end-point}") private String apiEndPoint; @Value("${spring.other-service-api.trusted_token}") private String trustedToken; ... @Test public void itemLookup_has_no_missing_items() throws Exception { // given: web test client WebTestClient webTestClient = WebTestClient .bindToServer() .baseUrl(apiEndPoint) .build(); // and: request spec var requestSpec = webTestClient .get() .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) .header(HttpHeaders.AUTHORIZATION, trustedToken); // expect: end-point used to get items, succeeds and get JSON response body var response = requestSpec.exchange() .expectStatus().isOk() .expectBody(String.class) .returnResult() .getResponseBody(); // and: convery JSON response to List List<Map<String, String>> items = new ObjectMapper().readValue(response, List.class); // verify: items size matches with what we have in database ... // verify: item ids match with what we have in database ... }

TIP

Sometimes, WebTestClient times out if the response takes more time than the default value 5000 milliseconds (5 seconds) by throwing the following exception:
java.lang.IllegalStateException: Timeout on blocking read for 5000 MILLISECONDS at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:123) at reactor.core.publisher.Mono.block(Mono.java:1734)

This timeout can be configured in two ways.

1. If WebTestClient is @Autowired with web server starting on a random port, by using annotation @AutoConfigureWebTestClient as shown below:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureWebTestClient(timeout = "10000") // millis, 10 seconds @ActiveProfiles("test") @RunWith(SpringRunner.class) public class MyServiceDataIT { @Value("${spring.other-service-api.get-Items-end-point}") private String apiEndPoint; @Value("${spring.other-service-api.trusted_token}") private String trustedToken; @Autowired private WebTestClient webTestClient; ... }

2. If WebTestClient is NOT @AutoWired and the embedded web server is not started, then by setting response timeout while building WebTestClient instance as shown below:
@Test public void itemLookup_has_no_missing_items() throws Exception { // given: web test client WebTestClient webTestClient = WebTestClient .bindToServer() .baseUrl(apiEndPoint) .build() .mutate() .responseTimeout(Duration.ofSeconds(10)) .build(); ... }

Conclusion

These days, most of the times, solutions can be found by googling or on stackoverflow. But, sometimes a clea(ne)r solution takes time to explore and try out.

A software developer's life is never easy, it's always challenging by simple things that look simple on the surface, yet complex under the hoods. Spring Boot framework is no exception in this regard ;)

Here is the link to GitHub repo that contains example code for two different integration test-cases of the two solutions mentioned in this post to try out: spring-boot-gotchas GitHub repo

Sunday, December 11, 2016

Know test cases run order in Grails-3 app to help solve issues with "test pollution". . .

In a Grails 3 application that I upgraded recently from Grail 2.2.1 to Grails 3.2.1, it was very puzzling when an integration specification (test case upgraded from Grails 2.2.1) with few feature methods (test case methods) passed locally but failed on the Bamboo CI server. From the kind of failure, it was evident that this particular specification failed due to "data pollution"- some unwanted data hanging around in the database by the time it ran.

The failed specification was an integration test specification and I was under the impression when grails test-app command is run, the test specifications are run in the order of test categories: unit tests, followed by integration tests, followed by functional tests.  If that was the case, the integration test that failed should have never failed as all integration specifications were properly annotated with @Integration and @Rollback and there was no setup() method creating data that couldn't be rolled back by @Rollback annotation causing the pollution. Grails 3 doesn't distinguish integration tests from functional test (at least when they are run as part of integrationTest gradle task), though they are distinguished by code. Typically integration tests extend Spock's Specification and annotated with @Integration and @Rollback, where as functional tests simply extend GebSpec.

Both local and CI server ran test-app task against an empty database. The only difference was: local was on Mac OS X and Bamboo CI server was on Linux. There was a functional test (GebSpec) in the set of integration tests under integration-test/groovy/myapp dir which had a bunch of feature methods. That one obviously did not have any data cleanup methods like: cleanup() or cleanupSpec(). I didn't even want to do any cleanup at the end in that functional spec because it was perfectly fine with local run. That led me to think of test data pollution causing this issue. But the most puzzling question was: "Why this functional test was coming in between and polluting integration test cases?" The only way to find it out was to know the order in which test-cases run and compare local run with Bamboo CI run.

I read through some documentation of Spock and Junit but didn't find an easy way of knowing the order of tests. Spock supports @Stepwise annotation to specify the order, but it was only within a specification. I read through some Gradle documentation about the test task and found a way to tap into the lifecycle of test cases and print the description of each test case that gets run. This helped me finding test-cases run order, and compare local with CI run to nail down the issue. Locally on Mac OS X, test-cases ran in alphabetical order, where as on Bamboo CI server (Linux), they seemed running in random order and one GebSpec functional test that was part of integration tests that got run along with all integration tests during integrationTest task as part of grails test-app task was the culprit. It was a coincidence that the functional specification name alphabetically was the last in the set of integration tests. On Mac OS X, it ran as the very last test case but on Linux it was running in between causing the following test to fail by leaving data in the database and thus leading to "test data pollution".

Following is the code snippet I added in build.gralde that prints each test before it's run:

/** * Configure test and integrationTest tasks. * Added beforeTest closure to get notified before a test is run. The closure simply logs the test descriptor which * indicates the test method that is being executed. Added to help find the test execution order differences between two * test runs or even differences between two systems like local, ci etc. */ test { beforeTest { descriptor -> logger.lifecycle("Running test: " + descriptor) } } integrationTest { beforeTest { descriptor -> logger.lifecycle("Running test: " + descriptor) } }

This will print as shown below, for instance when unit tests are run with the command: grails test-app -unit
:compileJava UP-TO-DATE :compileGroovy :buildProperties :processResources :classes :compileTestJava UP-TO-DATE :compileTestGroovy :processTestResources UP-TO-DATE :testClasses :test Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=2048m; support was removed in 8.0 Running test: Test testIndex(myapp.FirstTestSpec) Running test: Test testList(myapp.FirstTestSpec) Running test: Test testCreate(myapp.FirstTestSpec) Running test: Test testList(myapp.SecondTestSpec) Running test: Test testCreate(myapp.SecondTestSpec) Running test: Test testIndex(myapp.ThirdTestSpec) . . .

Ideally the order in which test cases run should not be a concern at all. But when functional tests and integration tests run together as integration tests, there are high chances of functional tests and integration tests getting mixed up in the sequence of running integration tests, and thus polluting integration tests. So, knowing the order in which test cases run will help solving this problem.

After finding out the issue, I refactored test cases under integration-test/groovy/myapp directory and separated out functional from integration tests into two different folders/packages (myapp.integration and myapp.functional) and even separated out their executions by running unit, integration and functional in 3 steps instead of running all in one step (grails test-app) as follows:
grails -Dgrails.env=development test-app -unit grails -Dgrails.env=development test-app myapp.integration.* -integration grails -Dgrails.env=development test-app myapp.functional.* -integration

This will guarantee that my functional tests which are expected to leave data in the database after run (as they were written) are run as the last group of tests in a bit controlled manner.

References

Gradle Test task documentation

Friday, August 26, 2016

Grails JMS plugin 1.2 hands off messages too early during the plugin relay of application startup . . .

Rio 2016 Olympics concluded recently with the traditional last event of track and field: 4x100 meter relay. In relay, the teams that qualify are the ones that gracefully complete the race. This involves a great co-ordination among team runners. One important rule of this particular race is handing off the baton within a changeover box. Dropping the baton or failing to transfer within the box disqualifies the team. The baton has to be carried till finish line in the race. It is a synchronous race.

Messaging in software applications is like a relay except that it is asynchronous. It is a great way to integrate modern applications asynchronously achieving great performance and scalability with guaranteed delivery of messages. But it coms with certain complexities in handling messages gracefully. Java based applications using JMS API are no exceptions. Spring framework's JMS support greatly simplifies the use of JMS. Grails JMS plugin which underpins Spring JMS makes it even better.

Recently I had to investigate and put a fix for a known issue of Grails JMS plugin that we used in our Grails applications. The known issue is- Messages get queued up when the application is down (expected behavior, of course). But when the app comes up, listeners grab messages too early from queues before the application is fully started. If message processing involves accessing domain model or database, they error out forcing you to deal with this situation.

We have two Grails applications integrated asynchronously through JMS messaging with ActiveMQ as the underlying JMS implementation. Messages flow back and forth between two applications. The most recent app of the two is a RESTful API app developed using Grails 3.1.6 rest-api profile and JMS plugin ver 2.0.0.M1 whereas the other application is a bit older Grails web application using Grails 2.2.1 and Grails JMS plugin 1.2. The Grails 3.1.6 RESTful API app is a gateway for client web/mobile apps that exposes resources in RESTful way and hands off requests to the Grails 2.2.1 web app for further processing. The communication channel between these two apps is JMS through queues. So, it was important to make sure we don't lose any messages either side due to any application down times. During testing everything on the Grails 3.1.6 side looked good. After it came up, it successfully processed messages that were waiting in the queues during it's down time. The other Grails 2.2.1 app simply errored out with t's message processing during a similar situation when it came back up after being down when the messages arrived and waiting in the queues. The following is a high-lvil architectural diagram depicting this.
Applications integrated through JMS

Known Issue with Grails JMS plugin 1.2

As the application starts up, listener containers get started bit early in the plugin race, earlier than dataSource and Hibernate are fully up and ready. Hence, if message processing involves domain-model access or db access, it errors out.

Options tried

Upgrading the plugin to ver 1.3

The plugin version 1,3 seemed to have a fix for this issue when I looked into plugin's GitHub source code  repository. The fix seemed convincing and pretty simple from the source code change. There were three more plugin dependencies added to the loadAfter list of plugins: 'dataSource', 'hibernate', and 'hibernate4'.

Steps to upgrade plugin:

1. Change the dependency in BuildConfig.groovy from compile ":jms:1.2" to compile ":jms:1.3"

plugins { ... compile ":jms:1.3" ... }

2. Refresh dependencies using the following command:
grails -Dgrails.env=<your-env> refresh-dependencies

3. Grails build system prompts as below for a confirmation:
> You currently already have a version of the plugin installed [jms-1.2]. Do you want to update to [jms-1.3]? [y,n]

Press y to upgrade.

However, with this upgrade the issue became bit worse and all listeners completely stopped listening to messages. Not only the messages that were waiting in the queue when the app was down, but also any new messages that come after the application comes up were not picked up by application listeners. After looking at the source code, JmsGrailsPlugin.groovy and comparing it with that of 1.2 plugin, noticed that the method startListenerContainer which starts up all listener container beans was totally missing. 

Upgrading the plugin to 1.3-SNAPSHOT

Then I tried 1.3-SNAPSHOT version by following the above steps to update the plugin and it's  dependencies. This ran into spring ClassNotFoundException (java.lang.NoClassDefFoundError: org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor) and the application wouldn't even come up. When looked into the plugin source code it had the three additional plugin dependencies added to the list loadAfter, and the method startListenerContainer was was also in place. When I compared this source with version to 1.2, the only change I noticed was the additional 3 plugins in the list for loadAfter.

That was puzzling to me and I read Grails documentation carefully word by word to understand how this loadAfter works. The sentence with an example: Here the plugin will be loaded after the controllers plugin if it exists, otherwise it will just be loaded caught my attention. Then I went back to check and see if we had hibernate4 plugin. Apparently, we didn't have that and as hibenate4 plugin was for Grails 2.5.0 or higher. That gave me a clue.

The fix

Having looked at 1.3, 1.3-SNAPSHOT and 1.2 and since we had 1.2 plugin checked into our source repository along with the application's source code, we decided to edit plugin code by adding just 2 dependencies: 'dataSource' and 'hibernate' to the list loadAfter. That worked beautifully as expected and we decided to go with that fix.

Here is the final fix, changed code in JmsGrailsPlugin.groovy from:
def loadAfter = ['services', 'controllers']

to:
// Load jms plugin after the following plugins. // BEWARE, if any of the listed plugins don't exist, the list is ignored // and this jms plugin gets loaded and executed // Ref: http://docs.grails.org/latest/guide/plugins.html#understandingPluginStructure def loadAfter = ['services', 'controllers', 'dataSource','hibernate']

With this fix, the application once came up, gracefully handled all pending messages that were awaiting when it  was down. Completed the message relay by handing off messages gracefully without dropping or erring out of the plugin execution race.

TIP

If due to any reason if it doesn't work, simply check application log file or by other means find out which plugin is the last one in the loading process and set loadAfter with a list containing that plugin. That should delay pushing this plugin to the end in the plugin loading process ;)

There seems no easy way to know all the plugin dependencies of a Grails project.

Running grails command grails dependency-report will list dependency graph.

If using IntelliJ, either Project Settings > Modules > dependencies,  or Project View > External Libraries or even trying to open a file (Mac: CMD + shift + O) for *GrailsPlugin.groovy should give the list of plugins for the project.

Useful Links & References