{"id":21718208,"url":"https://github.com/protonull/bonkersjavacoptimisationstest","last_synced_at":"2026-05-20T10:37:06.436Z","repository":{"id":235672691,"uuid":"761631834","full_name":"Protonull/BonkersJavacOptimisationsTest","owner":"Protonull","description":"When javac optimisations make working with soft-dependencies more difficult.","archived":false,"fork":false,"pushed_at":"2024-02-22T07:38:37.000Z","size":77,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-25T18:13:23.895Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Protonull.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2024-02-22T07:36:04.000Z","updated_at":"2024-02-22T07:45:19.000Z","dependencies_parsed_at":"2024-04-24T07:54:13.653Z","dependency_job_id":null,"html_url":"https://github.com/Protonull/BonkersJavacOptimisationsTest","commit_stats":null,"previous_names":["protonull/bonkersjavacoptimisationstest"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Protonull%2FBonkersJavacOptimisationsTest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Protonull%2FBonkersJavacOptimisationsTest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Protonull%2FBonkersJavacOptimisationsTest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Protonull%2FBonkersJavacOptimisationsTest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Protonull","download_url":"https://codeload.github.com/Protonull/BonkersJavacOptimisationsTest/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244685466,"owners_count":20493271,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-26T01:24:32.065Z","updated_at":"2026-05-20T10:37:01.404Z","avatar_url":"https://github.com/Protonull.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# BonkersJavacOptimisationsTest\n\nCame across these issues while working on a Minecraft plugin that softly depends on other plugins. It hadn't been an\nissue since those plugins were always present, but as soon as one was missing, the plugin exploded with\n`NoClassDefFoundError` exceptions.\n\nThe problem arises from the assumption that you can directly reference potentially-missing classes if the process of\nexecution never 'reaches' that code, eg:\n```java\nclass ChestLockerListener implements Listener {\n    \n    @EventHandler(ignoreCancelled = true)\n    public void onChestOpen(FictionalChestOpenEvent event) {\n        if (Bukkit.getPluginManager().isPluginEnabled(\"SomePlugin\")) {\n            if (SomePlugin.isChestLocked(event.getChest())) {\n                event.setCancelled(true);\n                return;\n            }\n        }\n    }\n    \n}\n```\nAnd yes, this *does* work. However, there are cases where javac optimises your code ways that break this assumption.\nThis repo demonstrates three instances of this (see [SoftDependencyTests](testing/src/test/java/uk/protonull/tests/SoftDependencyTests.java)):\n\n**KEEP IN MIND THAT THE `init` METHOD IN ALL THE TESTS IS NEVER INVOKED!**\n\n## Tests\n\n### Lambda Hoisting\n\nIf you've played around with any Java decompilers, you may have noticed that lambdas are often compiled as sibling\nclasses where, for example, lambdas in `ExampleClass` will produce `ExampleClass$1` as a standalone class within the\nsame package. This is not always true, something that `lambdaHoistTest()` demonstrates. Instead, the lambda gets hoisted\nas a sibling *method*, eg:\n```java\npublic final class Demonstration {\n    public void init() {\n        ActionRegistry.registerActionProvider(\n            ExampleAction.IDENTIFIER,\n            this::lambda$init$0\n        );\n    }\n\n    public Action lambda$init$0(UUID uuid) {\n        return new ExampleAction(uuid.toString(), 0, 0, 0);\n    }\n}\n```\nAnd because `Action` is a class from a missing soft-dependency, the `Demonstration` class fails upon initialisation.\n\n### Const `new`\n\nHowever, manually doing what you wish the compiler had done and creating a sibling class isn't helpful either.\n```java\npublic final class Demonstration {\n    public void init() { // Remember that this is never invoked\n        ActionRegistry.registerActionProvider(\n            ExampleAction.IDENTIFIER,\n            new ExampleActionProvider() // Why does this cause a NoClassDefFoundError?! ಠ_ಠ\n        );\n    }\n}\n```\nYou'd think that, based on the `ChestLockerListener` example above, that this would be fine. But *something* is\nhappening here that's causing the `Demonstration` class to fail upon initialisation that I can't put my finger on... but\nwhatever it is, the issue is 'fixed' by replacing it with a static-method reference, even if that static method is on an\nmissing class.\n```java\npublic final class Demonstration {\n    public void init() { // Remember that this is never invoked\n        ActionRegistry.registerActionProvider(\n            ExampleAction.IDENTIFIER,\n            ExampleAction::provider // This fixes it... for some reason\n        );\n    }\n}\n```\n\n### Const fields\n\nNow, let's say you have an event listener like so:\n```java\npublic final class Demonstration {\n    private final Object events = new Object() { // This is never registered nor is any of its methods invoked\n        @Subscribe\n        public void handleSomeEvent(\n            final Object event\n        ) {\n            for (final ActionUser user : ActionUser.getUsers()) {\n                user.acceptAction(new ExampleAction(UUID.randomUUID().toString(), 0, 0, 0));\n            }\n        }\n    };\n\n    public void init() { // Remember that this is never invoked\n        Something.eventBus.register(this.events);\n    }\n}\n```\nThat anonymous class is compiled as a sibling class, as expected. And yet, despite none of the missing classes being\n'reached'. Fixing this means making the field no longer final (or effectively final) and re-assigning it within the\n`init` method, like so:\n```java\npublic final class Demonstration {\n    private final Object events = null;\n\n    public void init() { // Remember that this is never invoked\n        Something.eventBus.register(this.events = new Object() {\n            @Subscribe\n            public void handleSomeEvent(\n                final Object event\n            ) {\n                for (final ActionUser user : ActionUser.getUsers()) {\n                    user.acceptAction(new ExampleAction(UUID.randomUUID().toString(), 0, 0, 0));\n                }\n            }\n        });\n    }\n}\n```\nWhich is bizarre because this changes seemingly nothing about how the anonymous class gets compiled as a sibling class.\n\n## How to run\n\nClone this repo and execute `sh run.sh`\n\n## Results\n\n```\nopenjdk 17.0.9 2023-10-17 LTS\nOpenJDK Runtime Environment Corretto-17.0.9.8.1 (build 17.0.9+8-LTS)\nOpenJDK 64-Bit Server VM Corretto-17.0.9.8.1 (build 17.0.9+8-LTS, mixed mode, sharing)\n\n\u003e Task :testing:test\n\nSoftDependencyTests \u003e constParameterTest() STARTED\n\nSoftDependencyTests \u003e constParameterTest() PASSED\n\nSoftDependencyTests \u003e lambdaHoistTest() STARTED\n\nSoftDependencyTests \u003e lambdaHoistTest() PASSED\n\nSoftDependencyTests \u003e constFieldTest() STARTED\n\nSoftDependencyTests \u003e constFieldTest() PASSED\n\nDeprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.\n\nYou can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.\n\nFor more on this, please refer to https://docs.gradle.org/8.5/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.\n\nBUILD SUCCESSFUL in 1s\n7 actionable tasks: 7 executed\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprotonull%2Fbonkersjavacoptimisationstest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprotonull%2Fbonkersjavacoptimisationstest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprotonull%2Fbonkersjavacoptimisationstest/lists"}