Skip to content

[JENKINS-37984] org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed: General error during class generation: Method code too large! error in pipeline Script #1210

Open

Description

@jenkins-infra-bot
Note from the Maintainers

There is partial fix for this for Declarative pipelines in pipeline-model-definition-plugin v1.4.0 and later, significantly improved in v1.8.4.  Due to the extent to which it change how pipelines are executed it is turned off by default.  It can be turned on by setting a JVM property (either on the command-line or in Jenkins script console):

org.jenkinsci.plugins.pipeline.modeldefinition.parser.RuntimeASTTransformer.SCRIPT_SPLITTING_TRANSFORMATION=true 

As noted, this still works best with a Jenkinsfile with pipeline directive as the only root item in the file.
Since v1.8.2 this workaround reports an informative error for pipelines using `def` variables before the pipeline directive. Add a @​Field annotation to those declaration.
This workaround generally does NOT work if the pipeline directive inside a shared library method. If this is a scenario you want, please come join the pipeline authoring SIG and we can discuss.

Please give it a try and provide feedback. 

Hi,

We are getting below error in Pipeline which has some 495 lines of groovy code. Initially we assumed that one of our methods has an issue but once we remove any 30-40 lines of Pipeline groovy, this issue is not coming.

Can you please suggest a quick workaround. It's a blocker for us.

org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
General error during class generation: Method code too large!

java.lang.RuntimeException: Method code too large!
	at groovyjarjarasm.asm.MethodWriter.a(Unknown Source)
	at groovyjarjarasm.asm.ClassWriter.toByteArray(Unknown Source)
	at org.codehaus.groovy.control.CompilationUnit$16.call(CompilationUnit.java:815)
	at org.codehaus.groovy.control.CompilationUnit.applyToPrimaryClassNodes(CompilationUnit.java:1053)
	at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:591)
	at org.codehaus.groovy.control.CompilationUnit.processPhaseOperations(CompilationUnit.java:569)
	at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:546)
	at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:298)
	at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:268)
	at groovy.lang.GroovyShell.parseClass(GroovyShell.java:688)
	at groovy.lang.GroovyShell.parse(GroovyShell.java:700)
	at org.jenkinsci.plugins.workflow.cps.CpsGroovyShell.reparse(CpsGroovyShell.java:67)
	at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.parseScript(CpsFlowExecution.java:410)
	at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.start(CpsFlowExecution.java:373)
	at org.jenkinsci.plugins.workflow.job.WorkflowRun.run(WorkflowRun.java:213)
	at hudson.model.ResourceController.execute(ResourceController.java:98)
	at hudson.model.Executor.run(Executor.java:410)

1 error

	at org.codehaus.groovy.control.ErrorCollector.failIfErrors(ErrorCollector.java:310)
	at org.codehaus.groovy.control.CompilationUnit.applyToPrimaryClassNodes(CompilationUnit.java:1073)
	at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:591)
	at org.codehaus.groovy.control.CompilationUnit.processPhaseOperations(CompilationUnit.java:569)
	at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:546)
	at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:298)
	at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:268)
	at groovy.lang.GroovyShell.parseClass(GroovyShell.java:688)
	at groovy.lang.GroovyShell.parse(GroovyShell.java:700)
	at org.jenkinsci.plugins.workflow.cps.CpsGroovyShell.reparse(CpsGroovyShell.java:67)
	at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.parseScript(CpsFlowExecution.java:410)
	at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.start(CpsFlowExecution.java:373)
	at org.jenkinsci.plugins.workflow.job.WorkflowRun.run(WorkflowRun.java:213)
	at hudson.model.ResourceController.execute(ResourceController.java:98)
	at hudson.model.Executor.run(Executor.java:410)
Finished: FAILURE

Originally reported by anudeeplalam, imported from: org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed: General error during class generation: Method code too large! error in pipeline Script
  • status: Open
  • priority: Blocker
  • component(s): workflow-cps-plugin
  • resolution: Unresolved
  • votes: 89
  • watchers: 111
  • imported: 2025-12-07
Raw content of original issue
Note from the Maintainers

There is partial fix for this for Declarative pipelines in pipeline-model-definition-plugin v1.4.0 and later, significantly improved in v1.8.4.  Due to the extent to which it change how pipelines are executed it is turned off by default.  It can be turned on by setting a JVM property (either on the command-line or in Jenkins script console):

org.jenkinsci.plugins.pipeline.modeldefinition.parser.RuntimeASTTransformer.SCRIPT_SPLITTING_TRANSFORMATION=true 

As noted, this still works best with a Jenkinsfile with pipeline directive as the only root item in the file. Since v1.8.2 this workaround reports an informative error for pipelines using `def` variables before the pipeline directive. Add a @Field annotation to those declaration. This workaround generally does NOT work if the pipeline directive inside a shared library method. If this is a scenario you want, please come join the pipeline authoring SIG and we can discuss.

Please give it a try and provide feedback. 

Hi,

We are getting below error in Pipeline which has some 495 lines of groovy code. Initially we assumed that one of our methods has an issue but once we remove any 30-40 lines of Pipeline groovy, this issue is not coming.

Can you please suggest a quick workaround. It's a blocker for us.

org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
General error during class generation: Method code too large!

java.lang.RuntimeException: Method code too large!
	at groovyjarjarasm.asm.MethodWriter.a(Unknown Source)
	at groovyjarjarasm.asm.ClassWriter.toByteArray(Unknown Source)
	at org.codehaus.groovy.control.CompilationUnit$16.call(CompilationUnit.java:815)
	at org.codehaus.groovy.control.CompilationUnit.applyToPrimaryClassNodes(CompilationUnit.java:1053)
	at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:591)
	at org.codehaus.groovy.control.CompilationUnit.processPhaseOperations(CompilationUnit.java:569)
	at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:546)
	at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:298)
	at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:268)
	at groovy.lang.GroovyShell.parseClass(GroovyShell.java:688)
	at groovy.lang.GroovyShell.parse(GroovyShell.java:700)
	at org.jenkinsci.plugins.workflow.cps.CpsGroovyShell.reparse(CpsGroovyShell.java:67)
	at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.parseScript(CpsFlowExecution.java:410)
	at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.start(CpsFlowExecution.java:373)
	at org.jenkinsci.plugins.workflow.job.WorkflowRun.run(WorkflowRun.java:213)
	at hudson.model.ResourceController.execute(ResourceController.java:98)
	at hudson.model.Executor.run(Executor.java:410)

1 error

	at org.codehaus.groovy.control.ErrorCollector.failIfErrors(ErrorCollector.java:310)
	at org.codehaus.groovy.control.CompilationUnit.applyToPrimaryClassNodes(CompilationUnit.java:1073)
	at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:591)
	at org.codehaus.groovy.control.CompilationUnit.processPhaseOperations(CompilationUnit.java:569)
	at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:546)
	at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:298)
	at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:268)
	at groovy.lang.GroovyShell.parseClass(GroovyShell.java:688)
	at groovy.lang.GroovyShell.parse(GroovyShell.java:700)
	at org.jenkinsci.plugins.workflow.cps.CpsGroovyShell.reparse(CpsGroovyShell.java:67)
	at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.parseScript(CpsFlowExecution.java:410)
	at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.start(CpsFlowExecution.java:373)
	at org.jenkinsci.plugins.workflow.job.WorkflowRun.run(WorkflowRun.java:213)
	at hudson.model.ResourceController.execute(ResourceController.java:98)
	at hudson.model.Executor.run(Executor.java:410)
Finished: FAILURE
5 attachments

Activity

  1. jenkins-infra-bot commented on Sep 6, 2016

    @jenkins-infra-bot
    Author
    • Remote link associated with this issue:

  2. jenkins-infra-bot commented on Oct 13, 2016

    @jenkins-infra-bot
    Author
    pmcnab:
    • Original comment link
    • Raw content of original comment:

      I've run into the same problem. I have a large (200+kb) pipeline script that is generated from our legacy build specification language.

      My generated code is broken up into stages, with each stage running a potentially large parallel pipeline operation, where each platform of a given target is built.

      After some googling about the underlying cause, I attempted to break it up by having each stage be defined as a method and calling the method rather than just having all the code in one giant block, but it didn't help.

      As this will impact a lot of people trying to migrate to the Jenkins pipeline, and may make them throw up their hands due to the error being vague and unhelpful (which I realize is not the fault of Jenkins, but the underlying Java/Groovy architecture), it might be good to have some specific guides on how to deal with this.

    I've run into the same problem. I have a large (200+kb) pipeline script that is generated from our legacy build specification language.

    My generated code is broken up into stages, with each stage running a potentially large parallel pipeline operation, where each platform of a given target is built.

    After some googling about the underlying cause, I attempted to break it up by having each stage be defined as a method and calling the method rather than just having all the code in one giant block, but it didn't help.

    As this will impact a lot of people trying to migrate to the Jenkins pipeline, and may make them throw up their hands due to the error being vague and unhelpful (which I realize is not the fault of Jenkins, but the underlying Java/Groovy architecture), it might be good to have some specific guides on how to deal with this.

  3. jenkins-infra-bot commented on Oct 13, 2016

    @jenkins-infra-bot
    Author
    jglick:
    • Original comment link
    • Raw content of original comment:

      Surprised that breaking it up into separate methods did not help. Other than that, I have no suggestions offhand.

    Surprised that breaking it up into separate methods did not help. Other than that, I have no suggestions offhand.

  4. jenkins-infra-bot commented on Oct 13, 2016

    @jenkins-infra-bot
    Author
    pmcnab:
    • Original comment link
    • Raw content of original comment:

      Yeah it's kind of puzzling – as part of my testing I had reduced the number of stages significantly.

      So a version that worked had 4 stages with no methods and it was about 1720 lines.

      With the full set of stages (27), the number of lines is about 5300 lines. Breaking it up into methods had no method larger than about 450 lines, but it wouldn't run.

      I also went in and removed the 4 stages that had worked, which put the entire script down to ~3600 lines broken out into methods, and that wouldn't run either.

    Yeah it's kind of puzzling – as part of my testing I had reduced the number of stages significantly.

    So a version that worked had 4 stages with no methods and it was about 1720 lines.

    With the full set of stages (27), the number of lines is about 5300 lines. Breaking it up into methods had no method larger than about 450 lines, but it wouldn't run.

    I also went in and removed the 4 stages that had worked, which put the entire script down to ~3600 lines broken out into methods, and that wouldn't run either.

  5. jenkins-infra-bot commented on Oct 13, 2016

    @jenkins-infra-bot
    Author
    pmcnab:
    • Original comment link
    • Raw content of original comment:

      Whoops. Well cancel what I said. When I generated the code with what I intended to be methods I was actually defining closures attached to a variable. When I did it properly as methods it's ok with it.

      So more specifically, for those of you who find this, what I used to have was:

      stage foo
      parallel([
       ... giant list of maps ...
      ])
      

      What I changed it to was:

      stage foo
      def build_foo() {
        parallel([
           ...giant list of maps...
        ])}
      build_foo()
      

    Whoops. Well cancel what I said. When I generated the code with what I intended to be methods I was actually defining closures attached to a variable. When I did it properly as methods it's ok with it.

    So more specifically, for those of you who find this, what I used to have was:

    stage foo
    parallel([
     ... giant list of maps ...
    ])
    
    

    What I changed it to was:

    stage foo
    def build_foo() {
      parallel([
         ...giant list of maps...
      ])}
    build_foo()
    
    
  6. jenkins-infra-bot commented on Oct 13, 2016

    @jenkins-infra-bot
    Author
    jglick:

    Sounds like a good candidate for a self-answer.

  7. jenkins-infra-bot commented on Oct 14, 2016

    @jenkins-infra-bot
    Author
    pmcnab:
    • Original comment link
    • Raw content of original comment:

      I made one – I still think this case should be touched on in the Pipeline docs. I don't think searching Jira or StackOverflow is a substitute for documentation

    I made one – I still think this case should be touched on in the Pipeline docs. I don't think searching Jira or StackOverflow is a substitute for documentation

  8. jenkins-infra-bot commented on Oct 14, 2016

    @jenkins-infra-bot
    Author
    jglick:
    • Original comment link
    • Raw content of original comment:

      Sure, that is why this is still open.

    Sure, that is why this is still open.

  9. jenkins-infra-bot commented on Feb 7, 2017

    @jenkins-infra-bot
    Author
    phillan:
    • Original comment link
    • Raw content of original comment:

      problem seems to be linked with the size all the script but I remarked it's linked also according number and size of stages. A workaround consists to declare a function called in some big stages.

      before :

      stage ('build') { .... if (.....) { } else { } ..... }

      workaround

      def build() { .... if (.....) { } else { } ..... } stage ('build') { build() }

    problem seems to be linked with the size all the script but I remarked it's linked also according number and size of stages.
    A workaround consists to declare a function called in some big stages.

    before :

    stage ('build') {
    ....
    if (.....) {
    } else {
    }
    .....
    }

    workaround

    def build() {
    ....
    if (.....) {
    } else {
    }
    .....
    }
    stage ('build') {
    build()
    }

  10. jenkins-infra-bot commented on Sep 14, 2017

    @jenkins-infra-bot
    Author
    abayer:
    • Original comment link
    • Raw content of original comment:

      So this is happening because of how CPS transformation works - everything's getting wrapped in a single method behind the scenes, and that's ending up being too large. The best answer we've got is to move things out into shared libraries.

    So this is happening because of how CPS transformation works - everything's getting wrapped in a single method behind the scenes, and that's ending up being too large. The best answer we've got is to move things out into shared libraries.

  11. jenkins-infra-bot commented on Apr 5, 2018

    @jenkins-infra-bot
    Author
    llibicpep:
    • Original comment link
    • Raw content of original comment:

      Any workarounds? My scenario is quite specific, we threat Jenkinsfile as some sort of a JIT. No one working with it directly, it rather being generated automatically based on something else. It is essential for the debugging purposes to have a big picture perspective on how the resulting Jenkinsfile going to look like, and there is no benefits into splitting it or moving to the shared library (again this is not for humans and not made by humans).

      We need a way to increase this limit.

    Any workarounds? My scenario is quite specific, we threat Jenkinsfile as some sort of a JIT. No one working with it directly, it rather being generated automatically based on something else. It is essential for the debugging purposes to have a big picture perspective on how the resulting Jenkinsfile going to look like, and there is no benefits into splitting it or moving to the shared library (again this is not for humans and not made by humans).

    We need a way to increase this limit.

  12. jenkins-infra-bot commented on Apr 5, 2018

    @jenkins-infra-bot
    Author
    ruudp:
    • Original comment link
    • Raw content of original comment:

      i use as "work a round" a script blok, for me it was a lot of work to change it but the result was smaller code witch is always good

         stages {     stage('parallel stages') {       steps {         script {

      in my case i have 200-300 parallel  stages

      i generate the parallel stages with a single function and i execute more as 200-300 stages in parallel, this is very dynamic as this amount of stages depends on user input. So i use now script block to generate all the stages and execute the stages instead of using declarative Pipeline.

       

    i use as "work a round" a script blok, for me it was a lot of work to change it but the result was smaller code witch is always good

       stages {
        stage('parallel stages') {
          steps {
            script {

    in my case i have 200-300 parallel  stages

    i generate the parallel stages with a single function and i execute more as 200-300 stages in parallel, this is very dynamic as this amount of stages depends on user input. So i use now script block to generate all the stages and execute the stages instead of using declarative Pipeline.

     

  13. jenkins-infra-bot commented on Apr 5, 2018

    @jenkins-infra-bot
    Author
    llibicpep:
    • Original comment link
    • Raw content of original comment:

      The thing is I actually don't want to hide this code to the library. My library already does it's thing, I have a custom DSL syntax that my library goes through and generates resulting Jenkinsfile. And I follow TDD, so I got tests, and my tests basically checking the resulting Jenkinsfile vs given my own DSL. Hiding parts of Jenkinsfile into the library going to ruin the whole idea of testing for my project. Again the Jenkinsfile for me is kind of JIT, it's not a code to being read or created by human.

    The thing is I actually don't want to hide this code to the library. My library already does it's thing, I have a custom DSL syntax that my library goes through and generates resulting Jenkinsfile. And I follow TDD, so I got tests, and my tests basically checking the resulting Jenkinsfile vs given my own DSL. Hiding parts of Jenkinsfile into the library going to ruin the whole idea of testing for my project. Again the Jenkinsfile for me is kind of JIT, it's not a code to being read or created by human.

  14. jenkins-infra-bot commented on Jul 14, 2018

    @jenkins-infra-bot
    Author
    thelamer:
    • Original comment link
    • Raw content of original comment:

      I am running into this, example file: 

      https://pastebin.com/raw/fnKnYiMq

      If I rip out some env declaration blocks like: 

               script{
                 env.CI_IMAGE = env.DEV_DOCKERHUB_IMAGE
                 env.CI_TAGS = env.EXT_RELEASE + '-pkg-' + env.PACKAGE_TAG + '-dev-' + env.COMMIT_SHA
                 env.CI_META_TAG = env.EXT_RELEASE + '-pkg-' + env.PACKAGE_TAG + '-dev-' + env.COMMIT_SHA
               }
      

      It will compile up and run, I already tried to move every script section out to functions, but still get Method to Large. 

       

      This is only ~800 lines and even if you compiled this text 10x over it would not be at the 64k java limit. 

      I am trying to understand how the workflow is interpreted that could result in this limit being hit.  

    I am running into this, example file: 

    https://pastebin.com/raw/fnKnYiMq

    If I rip out some env declaration blocks like: 

     script{
       env.CI_IMAGE = env.DEV_DOCKERHUB_IMAGE
       env.CI_TAGS = env.EXT_RELEASE + '-pkg-' + env.PACKAGE_TAG + '-dev-' + env.COMMIT_SHA
       env.CI_META_TAG = env.EXT_RELEASE + '-pkg-' + env.PACKAGE_TAG + '-dev-' + env.COMMIT_SHA
     }
    

    It will compile up and run, I already tried to move every script section out to functions, but still get Method to Large. 

     

    This is only ~800 lines and even if you compiled this text 10x over it would not be at the 64k java limit. 

    I am trying to understand how the workflow is interpreted that could result in this limit being hit.  

  15. jenkins-infra-bot commented on Aug 11, 2018

    @jenkins-infra-bot
    Author
    slavik334:
    • Original comment link
    • Raw content of original comment:

      +1

      it is a real headache

      about 800-900 lines of code

      could you please enforce fixing this issue?

    +1

    it is a real headache

    about 800-900 lines of code

    could you please enforce fixing this issue?

  16. 168 remaining items

  17. jenkins-infra-bot commented on Jul 15, 2025

    @jenkins-infra-bot
    Author
    felipecassiors:
    • Original comment link
    • Raw content of original comment:

      Can we get a more transparant fix for when using @Library _? Maybe @Field can be implictly included into it.

      Because it's such a common use case and is extensively documented, I would say that would be the best.

    Can we get a more transparant fix for when using @​Library _? Maybe @​Field can be implictly included into it.

    Because it's such a common use case and is extensively documented, I would say that would be the best.

  18. jenkins-infra-bot commented on Dec 7, 2025

    @jenkins-infra-bot
    Author

    [Original is_duplicated_by from Jira: JENKINS-50033]

  19. jenkins-infra-bot commented on Dec 7, 2025

    @jenkins-infra-bot
    Author

    [Original is_duplicated_by from Jira: JENKINS-72290]

  20. jenkins-infra-bot commented on Dec 7, 2025

    @jenkins-infra-bot
    Author

    [Original relates_to from Jira: JENKINS-56500]

  21. jenkins-infra-bot commented on Dec 7, 2025

    @jenkins-infra-bot
    Author

    [Original relates_to from Jira: JENKINS-64846]

  22. jenkins-infra-bot commented on Dec 7, 2025

    @jenkins-infra-bot
    Author

    [Original relates_to from Jira: JENKINS-61389]

  23. OmryAr commented on Sep 17, 2026

    @OmryAr

    (Edited — my original version reported that the profiler under-read method size by ~1 KB and that I couldn't explain why. I found the cause; it was my bug, not a limitation. The fix and the corrected figures are below, and the gotcha turns out to be the most useful thing here.)

    A few people in this thread have asked how to see this coming rather than discover it at parse time ("From my own experience, creating new stages, adding post-stages, and options seems to have a much larger effect on getting closer to the limit than adding more steps. However, it wish I could measure it."). Here are two things that work, both read-only and both runnable from Manage Jenkins → Script Console without running the pipeline or touching an agent. Script at the bottom.

    Measured on pipeline-model-definition 2.2118 and workflow-cps 3536.

    1. An exact verdict

    Parse the script through the real CpsGroovyShell, which is the same path CpsFlowExecution.parseScript uses, so MethodTooLargeException reproduces exactly as it would in a build:

    new CpsGroovyShellFactory(null).withSandbox(true).build().parse(pipelineText)

    One gotcha in reading the result: a NullPointerException in CpsScript.$initialize means the script compiled fine. It comes from prepareScript(), which runs only after GroovyShell.parse() has generated the class, and it fires purely because we hand the factory a null CpsFlowExecution. A MultipleCompilationErrorsException is the real failure.

    2. Per-method bytecode sizes — and the trap

    Recompile and read code_length out of each method's Code attribute. Two things have to be right, and the first one cost me a wrong answer:

    Do not hand-build the CompilerConfiguration. Reflect the live one out of a throwaway CpsGroovyShell and compile with that object. My first version constructed a config and applied the GroovyShellDecorators to it by hand, which looks equivalent and is not: the real config also carries a RejectASTTransformsCustomizer and a second ImportCustomizer. Extra star-imports change name resolution, which changes generated bytecode. The effect was a consistent ~1 KB under-read — enough that the profiler reported 64,397 B against the 65,535 limit for a revision the controller flatly rejects. With the live config reflected, the profiler reproduces that rejection and the numbers agree with reality.

    The symptom to watch for: if the verdict in §1 says a script does not compile while the profile shows headroom, your configuration is not the real one.

    The declarative transform needs the right CodeSource. GroovyShellDecoratorImpl only performs model parsing when the CompileUnit's CodeSource location is exactly file:/groovy/shell and the ClassNode is a script — what Jenkins produces when compiling a Jenkinsfile from a string. Pass anything else and the pipeline{} block is left untransformed, so the numbers come out far too small to mean anything; pass null and it NPEs at GroovyShellDecoratorImpl.java:54. Reusing the reflected config gets this right for free, since the decorator is already installed on it.

    Worked example, referred to below. A 630-line declarative pipeline that compiles — but only just:

      source 42598 B / 630 lines
      5882 of 65535 constant-pool entries
    
      METHOD                         BYTECODE  OF LIMIT
      ___cps___143530                  65452 B     99.9%
      $createCallSiteArray_1           36068 B     55.0%
      ___cps___143532                   2329 B      3.6%
      ___cps___143531                    821 B      1.3%
      ___cps___143533                    293 B      0.4%
      ___cps___143529                    212 B      0.3%
      <clinit>                           166 B      0.3%
      ... remaining methods all under 0.2%
    

    One ___cps___ method at 99.9% of the limit with 83 bytes of headroom, while every other ___cps___ method is under 4%. That is the shape of this issue: it is never diffuse, it is always one method. Worth stressing what 83 bytes means in practice — a single added echo with a couple of string concatenations is enough to tip a pipeline in that state from working to not compiling, which is exactly how these failures tend to arrive looking causeless.

    What actually reduces the oversized method

    The part I found most useful, because one plausible move does nothing at all:

    Move Effect on the big method
    Code inside pipeline{} → shared library vars/*.groovy Removes it (separate class)
    Code inside pipeline{} → top-level def in the same file Removes it (own ___cps___ method, own 64 KB budget)
    Top-level def → shared library No effect — it already had its own method
    Wrapping code in a closure No effect — closures inline into the enclosing builder tree

    So a helper already sitting in a top-level def is not what is costing you; only code written inside pipeline{} is. You can see this in the table: each top-level def contributes its own small ___cps___ method (plus a stub carrying the original name), which is why they cannot be what overflows.

    On that same pipeline, moving the inline logic into the shared library took the big method from 65,452 B to 60,362 B (92.1%) — from 83 bytes of headroom to 5,173.

    This matches the resolution the CloudBees KB article suggests, and it is also why "I broke it up into methods and it still failed" reports in this thread can be consistent with the advice — if the code stayed inside the pipeline{} braces, splitting it into closures or stages does not move it out of the one method.

    Secondary ceilings worth knowing about

    • $createCallSiteArray_1 grows with the number of distinct call sites in the file. In the table above it is the second-largest method at 55% of the limit, so it is a second wall in the same class.
    • The constant pool is a separate 65535-entry cap (noted earlier in this thread). The example uses 5,882 of them, so it is rarely the binding constraint, but the script reports it.
    • The 255-argument limit. groovy-cps emits one builder argument per statement in a block, so a block with more than 255 consecutive statements fails with The max number of supported arguments is 255, but found N instead of the size error. I hit this while trying to build a synthetic reproducer and it sent me down the wrong path for a while.
    • ___cps___NNNNN in the error message is a JVM-global counter (JENKINS-39719 made CpsTransformer.iota globally unique), so the number identifies nothing and is not comparable between builds or even between runs of this script.
    Script — paste into the Script Console, set PIPELINE_SOURCE, run
    // Measure how close a Declarative Pipeline is to the JVM's 64 KB per-method bytecode limit.
    //
    // Paste into Manage Jenkins -> Script Console, set PIPELINE_SOURCE, run. Read-only: compiles in
    // memory and into a temp dir it deletes, and never runs the pipeline.
    //
    //   VERDICT - does Jenkins compile this script? Exact: uses the real CpsGroovyShell.
    //   PROFILE - bytecode size of every generated method, largest first.
    
    import org.jenkinsci.plugins.workflow.cps.CpsGroovyShellFactory
    import org.codehaus.groovy.control.CompilerConfiguration
    import org.codehaus.groovy.control.CompilationUnit
    import org.codehaus.groovy.control.Phases
    import jenkins.model.Jenkins
    import java.security.CodeSource
    import java.security.cert.Certificate
    
    final int LIMIT = 65535   // JVMS 4.7.3: code_length must be < 65536
    
    // Replace with your Jenkinsfile text, or read it from disk:
    //   String PIPELINE_SOURCE = new File("/var/jenkins_home/.../Jenkinsfile").text
    String PIPELINE_SOURCE = '''
    pipeline {
        agent any
        stages {
            stage('example') {
                steps {
                    script { echo "replace me with your real pipeline" }
                }
            }
        }
    }
    '''
    
    // Reads code_length out of each method's Code attribute.
    class CodeSizes {
        private final byte[] b
        private int pos = 0
        CodeSizes(byte[] bytes) { this.b = bytes }
    
        private int u1() { return ((int) b[pos++]) & 0xff }
        private int u2() { int hi = u1(); int lo = u1(); return (hi << 8) | lo }
        private long u4() { long hi = u2(); long lo = u2(); return (hi << 16) | lo }
    
        private Long attributes(String[] utf8) {
            Long codeLen = null
            int count = u2()
            for (int a = 0; a < count; a++) {
                String name = utf8[u2()]
                long len = u4()
                int end = pos + (int) len
                if (name == "Code") { u2(); u2(); codeLen = u4() }   // max_stack, max_locals, code_length
                pos = end
            }
            return codeLen
        }
    
        Map read() {
            long magic = u4()
            assert magic == 0xCAFEBABEL, "not a class file (magic=${Long.toHexString(magic)})"
            u2()                                   // minor
            int major = u2()
    
            int cpCount = u2()
            String[] utf8 = new String[cpCount]
            int i = 1
            while (i < cpCount) {
                int tag = u1()
                if (tag == 1) { int len = u2(); utf8[i] = new String(b, pos, len, "UTF-8"); pos += len }
                else if (tag in [5, 6]) { pos += 8; i++ }            // Long/Double take two slots
                else if (tag in [7, 8, 16, 19, 20]) { pos += 2 }
                else if (tag == 15) { pos += 3 }
                else if (tag in [3, 4, 9, 10, 11, 12, 17, 18]) { pos += 4 }
                else { throw new RuntimeException("unknown constant pool tag ${tag}") }
                i++
            }
    
            u2(); u2(); u2()                       // access_flags, this_class, super_class
            int ifaceCount = u2()
            pos += ifaceCount * 2
    
            int fieldCount = u2()
            for (int f = 0; f < fieldCount; f++) { u2(); u2(); u2(); attributes(utf8) }
    
            int methodCount = u2()
            def methods = []
            for (int m = 0; m < methodCount; m++) {
                u2()                               // access_flags
                String name = utf8[u2()]
                u2()                               // descriptor
                Long len = attributes(utf8)
                if (len != null) methods << [name: name, code: len]
            }
            attributes(utf8)                       // class-level attributes
    
            assert pos == b.length, "parse drift: ${pos} of ${b.length}"
            return [major: major, cpEntries: cpCount - 1, methods: methods.sort { -it.code }]
        }
    }
    
    def banner = { String title -> println "=" * 78; println title; println "=" * 78 }
    def flatten = { Throwable t ->
        def out = []
        for (def c = t; c != null && out.size() < 4; c = (c.cause == c ? null : c.cause)) {
            out << "${c.class.simpleName}: ${(c.message ?: '').readLines().findAll { it.trim() }.take(2).join(' | ')}"
        }
        out.join("  <-  ")
    }
    
    // One shell serves both halves: parsed for the verdict, and its CompilerConfiguration reflected
    // for the profile. That config must be the live one, not a rebuilt equivalent -- Jenkins' config
    // carries a RejectASTTransformsCustomizer and a second ImportCustomizer, and the differing
    // star-imports change name resolution and therefore the bytecode being measured.
    def shell = new CpsGroovyShellFactory(null).withSandbox(true).build()
    
    // A NullPointerException from CpsScript.$initialize means SUCCESS: it comes from prepareScript(),
    // which runs only after the class is generated, and fires only because the execution is null.
    banner("VERDICT")
    try {
        shell.parse(PIPELINE_SOURCE)
        println "  COMPILES"
    } catch (NullPointerException npe) {
        if (npe.stackTrace.any { it.className.contains("CpsScript") }) {
            println "  COMPILES  (post-compile init NPE, expected with a null execution)"
        } else {
            println "  UNKNOWN NPE: ${npe.stackTrace.take(3).join(' | ')}"
        }
    } catch (Throwable t) {
        println "  DOES NOT COMPILE"
        println "    ${flatten(t)}"
    }
    println ""
    
    banner("PROFILE")
    File work = new File(System.getProperty("java.io.tmpdir"), "cps-size-" + System.nanoTime())
    File out = new File(work, "out"); out.mkdirs()
    File srcFile = new File(work, "WorkflowScript.groovy"); srcFile.text = PIPELINE_SOURCE
    try {
        CompilerConfiguration cc = null
        for (Class k = shell.classLoader.class; k != null && cc == null; k = k.superclass) {
            k.declaredFields.each { f ->
                if (cc == null && CompilerConfiguration.isAssignableFrom(f.type)) {
                    f.setAccessible(true); cc = (CompilerConfiguration) f.get(shell.classLoader)
                }
            }
        }
        if (cc == null) throw new RuntimeException("no CompilerConfiguration on ${shell.classLoader.class.name}")
        println "  transforms: ${cc.compilationCustomizers.collect { it.class.simpleName }.join(', ')}"
        cc.targetDirectory = out
    
        // GroovyShellDecoratorImpl only performs declarative model parsing when the CodeSource
        // location is exactly this and the ClassNode is a script, which is what Jenkins produces
        // when it compiles a Jenkinsfile from a string. Anything else leaves pipeline{}
        // untransformed; null throws NPE.
        def cs = new CodeSource(new URL("file:/groovy/shell"), (Certificate[]) null)
        def cu = new CompilationUnit(cc, cs, new GroovyClassLoader(Jenkins.get().pluginManager.uberClassLoader, cc))
        cu.addSource(srcFile)
        cu.compile(Phases.OUTPUT)
    
        def r = new CodeSizes(new File(out, "WorkflowScript.class").bytes).read()
        println "  source ${PIPELINE_SOURCE.length()} B / ${PIPELINE_SOURCE.readLines().size()} lines"
        println "  ${r.cpEntries} of ${LIMIT} constant-pool entries"
        println ""
        println String.format("  %-28s %10s %9s", "METHOD", "BYTECODE", "OF LIMIT")
        r.methods.take(12).each { m ->
            println String.format("  %-28s %9d B %8.1f%%%s", m.name, m.code,
                    m.code * 100.0d / LIMIT, m.code > LIMIT ? "  <-- OVER" : "")
        }
        def big = r.methods[0]
        println ""
        println "  largest: ${big.name} = ${big.code} B, ${LIMIT - big.code} B of headroom"
        println "  (the big ___cps___NNNNN method is your pipeline{} block; the number is a"
        println "   JVM-global counter and identifies nothing)"
    } catch (Throwable t) {
        println "  no profile: ${flatten(t)}"
        println "  (expected when VERDICT says it does not compile: no class is emitted when a"
        println "   method is over the limit, so no size can be read)"
    } finally {
        work.deleteDir()
    }

    Happy to turn any of this into a docs PR if maintainers think it belongs somewhere more discoverable than a comment on a nine-year-old issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions