Forums

Articles
Create
cancel
Showing results for 
Search instead for 
Did you mean: 

Moving an Issue between projects

Jackson Farnsworth
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Champions.
September 26, 2017

I'm attempting to move an issue between jira core projects using code I've seen from others who claim success. So far I get to invoking MoveIssueUpdateFields doExecute() method, but get a null pointer exception with the following stack trace: 

java.lang.reflect.InvocationTargetException 
at ProjectIssueMove.moveIssueToAnotherProject(Script514.groovy:110)
at ProjectIssueMove$moveIssueToAnotherProject.call(Unknown Source) at Script514.run(Script514.groovy:41)

Caused by: java.lang.NullPointerException at com.atlassian.jira.web.action.issue.AbstractIssueSelectAction.readPrepopulatedIssueFromRequest(AbstractIssueSelectAction.java:124)
at com.atlassian.jira.web.action.issue.AbstractIssueSelectAction.getIssueResultFromRequestOrDatabase(AbstractIssueSelectAction.java:102)
at com.atlassian.jira.web.action.issue.AbstractIssueSelectAction.access$100(AbstractIssueSelectAction.java:30)
at com.atlassian.jira.web.action.issue.AbstractIssueSelectAction$IssueGetter.get(AbstractIssueSelectAction.java:519)
at com.atlassian.jira.web.action.issue.SelectedIssue.fetchIssueResult(SelectedIssue.java:85)
at com.atlassian.jira.web.action.issue.SelectedIssue.exists(SelectedIssue.java:73)
at com.atlassian.jira.web.action.issue.AbstractIssueSelectAction.isIssueExists(AbstractIssueSelectAction.java:60)
at com.atlassian.jira.web.action.issue.AbstractIssueSelectAction.assertIssueIsValid(AbstractIssueSelectAction.java:488)
at com.atlassian.jira.web.action.issue.AbstractIssueSelectAction.getIssueObject(AbstractIssueSelectAction.java:157)
at com.atlassian.jira.web.action.issue.MoveIssueUpdateFields.doExecute(MoveIssueUpdateFields.java:241)
... 3 more

 I can't seem to find what the null object is, any help would be greatly appreciated.

The following is the code used: 

import com.atlassian.core.ofbiz.util.CoreTransactionUtil
import com.atlassian.jira.bc.issue.comment.CommentService
import com.atlassian.jira.bc.issue.IssueService
import com.atlassian.jira.event.issue.IssueEventBundleFactory
import com.atlassian.jira.event.issue.txnaware.TxnAwareEventFactory
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.config.ConstantsManager
import com.atlassian.jira.config.StatusManager
import com.atlassian.jira.config.properties.JiraSystemProperties
import com.atlassian.jira.issue.AttachmentManager
import com.atlassian.jira.issue.IssueFieldConstants
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.issuetype.IssueType
import com.atlassian.jira.issue.security.IssueSecurityHelper
import com.atlassian.jira.project.Project
import com.atlassian.jira.project.ProjectManager
import com.atlassian.jira.web.SessionKeys
import com.atlassian.jira.web.action.issue.MoveIssueConfirm
import com.atlassian.jira.web.action.issue.MoveIssueUpdateFields
import com.atlassian.jira.web.bean.MoveIssueBean
import org.apache.log4j.Category
import org.apache.log4j.Logger
import org.apache.log4j.Level
import java.lang.reflect.Method
import webwork.action.ActionContext

//For testing one issue at a time//----------------------------------------
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
def issueService = ComponentAccessor.getComponentOfType(IssueService.class)
MutableIssue issue = issueService.getIssue(user,212961).getIssue() //enter issue id here
//-------------------------------------------------------------------------


ProjectIssueMove pim = new ProjectIssueMove()
String newProjectKey = "LN"
String issueType = issue.getIssueType().getName()
String statusName = issue.getStatus().getName()

pim.moveIssueToAnotherProject(newProjectKey,issueType,statusName,issue)


class ProjectIssueMove {
Category log = Category.getInstance(ProjectIssueMove.class)

public void moveIssueToAnotherProject(String newProjectKey, String newIssueType, String newStatusName, MutableIssue targetIssue) {

def log = Logger.getLogger("com.acme.CreateSubtask")
log.setLevel(Level.INFO)

MoveIssueBean moveIssueBean = new MoveIssueBean(ComponentAccessor.getConstantsManager(), ComponentAccessor.getProjectManager())
moveIssueBean.getFieldValuesHolder().put(IssueFieldConstants.PROJECT, getProjectFromKey(newProjectKey).id)
moveIssueBean.getFieldValuesHolder().put(IssueFieldConstants.ISSUE_TYPE, getTargetIssueType(newIssueType).id)
moveIssueBean.setIssueId(targetIssue.getId())
moveIssueBean.setTargetStatusId(getStatusToSet(newStatusName).id)
moveIssueBean.setSourceIssueKey(targetIssue.getKey())
moveIssueBean.setUpdatedIssue(targetIssue)

ActionContext.getSession().put(SessionKeys.MOVEISSUEBEAN, moveIssueBean)

MoveIssueUpdateFields moveIssueUpdateFields = new MoveIssueUpdateFields(
ComponentAccessor.getSubTaskManager(),
ComponentAccessor.getConstantsManager(),
ComponentAccessor.getWorkflowManager(),
ComponentAccessor.getFieldManager(),
ComponentAccessor.getFieldLayoutManager(),
ComponentAccessor.getIssueFactory(),
ComponentAccessor.getFieldScreenRendererFactory(),
ComponentAccessor.getComponentOfType(CommentService.class),
ComponentAccessor.getComponentOfType(IssueSecurityHelper.class),
ComponentAccessor.getUserUtil()
)

MoveIssueConfirm moveIssueConfirm = new MoveIssueConfirm(
ComponentAccessor.getSubTaskManager(),
ComponentAccessor.getComponentOfType(AttachmentManager.class),
ComponentAccessor.getConstantsManager(),
ComponentAccessor.getWorkflowManager(),
ComponentAccessor.getFieldManager(),
ComponentAccessor.getFieldLayoutManager(),
ComponentAccessor.getIssueFactory(),
ComponentAccessor.getFieldScreenRendererFactory(),
ComponentAccessor.getComponentOfType(CommentService.class),
ComponentAccessor.getComponentOfType(IssueSecurityHelper.class),
ComponentAccessor.getIssueManager(),
ComponentAccessor.getUserUtil(),
ComponentAccessor.getIssueEventManager(),
ComponentAccessor.getComponentOfType(IssueEventBundleFactory.class),
ComponentAccessor.getComponentOfType(TxnAwareEventFactory.class)
)

JiraSystemProperties.getInstance()
moveIssueUpdateFields.setId(targetIssue.getId())
Method privateUpdateFieldsDoExecute = MoveIssueUpdateFields.class.getDeclaredMethod("doExecute")
privateUpdateFieldsDoExecute.setAccessible(true)
privateUpdateFieldsDoExecute.invoke(moveIssueUpdateFields) //fails here
moveIssueConfirm.setId(targetIssue.getId())
Method privateIssueConfirmDoExecute = MoveIssueConfirm.class.getDeclaredMethod("doExecute")

privateIssueConfirmDoExecute.setAccessible(true)
privateIssueConfirmDoExecute.invoke(moveIssueConfirm)
CoreTransactionUtil.commit(true)

}
private Project getProjectFromKey(String projectKey) { //get the projectObject from a String key
ProjectManager projectManager = ComponentAccessor.getProjectManager()
return projectManager.getProjectObjByKeyIgnoreCase(projectKey)
}
def getStatusToSet(String statusName) { //find the status object from a status name
StatusManager statusManager = ComponentAccessor.getComponentOfType(StatusManager.class)
def statuses = statusManager.getStatuses()
def iterator = statuses.iterator()
while (iterator.hasNext()) {
def status = iterator.next()
if (status.getName() == statusName) {
return status
}
}
return null
}
def IssueType getTargetIssueType(String issueTypeName) { //Find the issue type object from a issue type name
ConstantsManager constantsManager = ComponentAccessor.getConstantsManager()
def issueTypes = constantsManager.getAllIssueTypeObjects()
def issuetypeiterator = issueTypes.iterator()
while (issuetypeiterator.hasNext()) {
def issueType = issuetypeiterator.next()
if (issueType.name.toLowerCase() == issueTypeName.toLowerCase()) {
return issueType
}
}
return null
}
}

Main Reference question:

 How-to-move-an-issue-programatically-to-a-different-project

3 answers

Comments for this post are closed

Community moderators have prevented the ability to post new answers.

Post a new question

0 votes
Jean-François FORGET
July 14, 2020

It took me the whole day, but I've finally made it works into a Script Runner Rest Endpoint with my Jira SW 8.3.2 and JSD 4.3.2.

Don't ask exactly how....

Note: My goal is a one shot move of all tickets from project A to B and delete project A, making its key available again.

+ This way I can bulk move with a mapping for "Customer Request Type" which is not possible with the "move" user interface.

 

If any expert see something dangerous or to be improved, please be my guest.

 

import com.mycorp.jira.MyCustomField
import com.atlassian.jira.bc.issue.comment.CommentService
import com.atlassian.jira.bc.project.component.ProjectComponent
import com.atlassian.jira.config.properties.JiraSystemProperties
import com.atlassian.jira.event.issue.IssueEventBundleFactory
import com.atlassian.jira.event.issue.txnaware.TxnAwareEventFactory
import com.atlassian.jira.help.HelpUrls
import com.atlassian.jira.issue.IssueFieldConstants
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.security.IssueSecurityHelper
import com.atlassian.jira.project.Project
import com.atlassian.jira.project.ProjectManager
import com.atlassian.jira.web.SessionKeys
import com.atlassian.jira.web.action.issue.MoveIssueConfirm
import com.atlassian.jira.web.action.issue.MoveIssueUpdateFields
import com.atlassian.jira.web.bean.MoveIssueBean
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import conf.MyCustFieldByEnv
import groovy.json.JsonBuilder
import groovy.transform.BaseScript
import webwork.action.ActionContext
import webwork.dispatcher.GenericDispatcher
import javax.servlet.http.HttpServletRequest
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.component.pico.ComponentManager
import java.lang.reflect.Method

@BaseScript CustomEndpointDelegate delegate

test(httpMethod: "GET", groups: ["jira-administrators"]) { MultivaluedMap queryParams, String body, HttpServletRequest request ->
/******************************************
* Define issue to move and target project *
* And values mapping *
*******************************************/
String targetProjectKey = "<target project key>"
String issueKey = "<issue key to move>"

Map customerRequestTypeMapping = [
'default':'sptst/ef0ee952-93ff-4886-ae98-e23b61eba08f',
'spfdl/a0ef7668-4de9-42a5-a35b-8e22b59f64e6':'sptst/ef0ee952-93ff-4886-ae98-e23b61eba08f',
'spfdl/f54a4a3f-739e-45a0-93bd-8e648a3d0524':'sptst/fc3849bd-c2f1-49e9-bb5a-7853911c47e2',
'spfdl/bf077552-6d75-4b40-b4cd-3f211fb28244':'sptst/133fbf56-719d-43d7-a78a-eece3cdd4091',
'spfdl/dfe9294a-872a-4ca6-b72b-f97278c5dec5':'sptst/1b9f32b2-c9c3-40af-a109-b757fd7d1282',
]


/*******************
* Load components *
*******************/
ComponentManager componentManager = ComponentManager.getInstance()
ProjectManager projectManager = ComponentAccessor.getProjectManager()
IssueManager issueManager = ComponentAccessor.getIssueManager()
int cfCustomerRequestTypeId = MyCustFieldByEnv.getCustomFieldId("CustomerRequestType")
CustomField cfCustomerRequestType = MyCustomField.getCustFieldObjectById(cfCustomerRequestTypeId)
def requestTypeCustomFieldType = cfCustomerRequestType.getCustomFieldType()

/********************************************
* Load issue, target project and move data *
********************************************/
Project targetProject = projectManager.getProjectObjByKey(targetProjectKey)
MutableIssue targetIssue = issueManager.getIssueObject(issueKey)
MoveIssueBean moveIssueBean = new MoveIssueBean(ComponentAccessor.getConstantsManager(), projectManager)
moveIssueBean.getFieldValuesHolder().put(IssueFieldConstants.PROJECT, targetProject.id)
moveIssueBean.getFieldValuesHolder().put(IssueFieldConstants.ISSUE_TYPE, targetIssue.issueType.id.toString())
moveIssueBean.setIssueId(targetIssue.id)
moveIssueBean.setSourceIssueKey(targetIssue.key)
moveIssueBean.setTargetStatusId(targetIssue.status.id.toString())

/*************************
* Define action context * (here is the "dirty tricks")
*************************/
GenericDispatcher gd = new GenericDispatcher("Move")
request.setAttribute("jira.webwork.generic.dispatcher",gd)
gd.prepareContext()
ActionContext.session.put(SessionKeys.MOVEISSUEBEAN, moveIssueBean)
ActionContext.setRequest(request)

/********
* MOVE *
********/
MoveIssueUpdateFields moveIssueUpdateFields = new MoveIssueUpdateFields(
ComponentAccessor.getSubTaskManager(),
ComponentAccessor.getConstantsManager(),
ComponentAccessor.getWorkflowManager(),
ComponentAccessor.getFieldManager(),
ComponentAccessor.getFieldLayoutManager(),
ComponentAccessor.getIssueFactory(),
ComponentAccessor.getFieldScreenRendererFactory(),
componentManager.getComponentInstanceOfType(CommentService.class),
componentManager.getComponentInstanceOfType(IssueSecurityHelper.class),
ComponentAccessor.getUserUtil(),
componentManager.getComponentInstanceOfType(HelpUrls.class)
)

MoveIssueConfirm moveIssueConfirm = new MoveIssueConfirm(
ComponentAccessor.getSubTaskManager(),
ComponentAccessor.getAttachmentManager(),
ComponentAccessor.getConstantsManager(),
ComponentAccessor.getWorkflowManager(),
ComponentAccessor.getFieldManager(),
ComponentAccessor.getFieldLayoutManager(),
ComponentAccessor.getIssueFactory(),
ComponentAccessor.getFieldScreenRendererFactory(),
componentManager.getComponentInstanceOfType(CommentService.class),
componentManager.getComponentInstanceOfType(IssueSecurityHelper.class),
issueManager,
ComponentAccessor.getUserUtil(),
ComponentAccessor.getIssueEventManager(),
componentManager.getComponentInstanceOfType(IssueEventBundleFactory.class),
componentManager.getComponentInstanceOfType(TxnAwareEventFactory.class),
componentManager.getComponentInstanceOfType(HelpUrls.class)
)
/****************************
* Prepare move object bean *
****************************/
JiraSystemProperties.getInstance()
moveIssueUpdateFields.setId(targetIssue.id)
Method privateUpdateFieldsDoExecute = MoveIssueUpdateFields.class.getDeclaredMethod("doExecute")
privateUpdateFieldsDoExecute.setAccessible(true)
String msg = (String) privateUpdateFieldsDoExecute.invoke(moveIssueUpdateFields)
log.error(msg)

/**************************
* Include custom updates *
**************************/
// set components
Collection<ProjectComponent> targetComponents = []
targetIssue.getComponents().each{ component ->
targetComponents.add(ComponentAccessor.getProjectComponentManager().findAllForProject(targetProject.id)?.find { it.name == component.name})
}
moveIssueBean.getUpdatedIssue().setComponent(targetComponents)
// Set Request type
String sourceCustRequestType = MyCustomField.getCustFieldValueById(cfCustomerRequestTypeId,targetIssue)
String targetCustRequestType = customerRequestTypeMapping.default
if(customerRequestTypeMapping.containsKey(sourceCustRequestType)){
targetCustRequestType = customerRequestTypeMapping[sourceCustRequestType]
}
def newRequestTypeValue = requestTypeCustomFieldType.getSingularObjectFromString(targetCustRequestType)
moveIssueBean.getUpdatedIssue().setCustomFieldValue(cfCustomerRequestType, newRequestTypeValue)

/****************
* Confirm move *
****************/
moveIssueConfirm.setId(targetIssue.id)
Method privateIssueConfirmDoExecute = MoveIssueConfirm.class.getDeclaredMethod("doExecute")
// issueManager.updateIssue(user, issueObject, eventDispatchOption, args.notify)
privateIssueConfirmDoExecute.setAccessible(true)
msg = (String) privateIssueConfirmDoExecute.invoke(moveIssueConfirm)
log.error(msg)


return Response.ok(new JsonBuilder([result: "${moveIssueBean.getSourceIssueKey()} ==> ${moveIssueBean.getUpdatedIssue()}"]).toString()).build()
}
Steffen John
December 21, 2023

Hi Jean-François,

after more than 3 years, I found your solution to be still relevant as I don't see a really good way to deal with moving via Java API.

How did your solution hold up? Do you still use it? I admire your code, but at the same time, I didn't want to use it because it looks hacky with that Action context. Am I correct to assume you are imitating the stuff that Jira does natively when someone uses the GUI?

I opted to just create an issue copy and link it (probably with the Copy & Sync plugin).

Thanks,
Steffen

0 votes
Jean-François FORGET
July 14, 2020

Hi @Jackson Farnsworth ,

I got the same issue.

I think the error happen because the HttpServletRequest "request" on line 124 in AbstractIssueSelectAction.java is not provided when running this code.

 

And I am unable to find a way to get it :(

Did you find a way to fix this since you have posted this question?

Jackson Farnsworth
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Champions.
July 14, 2020

I think I had to use a work around, sorry to say I can't recall what I did, but I'm relatively certain I abandoned this path. I checked my saved scripts folder, and found this in it that might be a solved version. Give it a look over, and see if maybe it helps.

{code}

import com.atlassian.core.ofbiz.util.CoreTransactionUtil
import com.atlassian.jira.bc.issue.comment.CommentService
import com.atlassian.jira.bc.issue.IssueService
import com.atlassian.jira.event.issue.IssueEventBundleFactory
import com.atlassian.jira.event.issue.txnaware.TxnAwareEventFactory
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.config.ConstantsManager
import com.atlassian.jira.config.StatusManager
import com.atlassian.jira.config.properties.JiraSystemProperties
import com.atlassian.jira.issue.AttachmentManager
import com.atlassian.jira.issue.IssueFieldConstants
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.issuetype.IssueType
import com.atlassian.jira.issue.security.IssueSecurityHelper
import com.atlassian.jira.project.Project
import com.atlassian.jira.project.ProjectManager
import com.atlassian.jira.web.SessionKeys
import com.atlassian.jira.web.action.issue.MoveIssueConfirm
import com.atlassian.jira.web.action.issue.MoveIssueUpdateFields
import com.atlassian.jira.web.bean.MoveIssueBean
import org.apache.log4j.Category
import org.apache.log4j.Logger
import org.apache.log4j.Level
import java.lang.reflect.Method
import webwork.action.ActionContext

//For testing one issue at a time//----------------------------------------
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
def issueService = ComponentAccessor.getComponentOfType(IssueService.class)
MutableIssue issue = issueService.getIssue(user,212961).getIssue() //enter issue id here
//-------------------------------------------------------------------------


ProjectIssueMove pim = new ProjectIssueMove()
String newProjectKey = "PROJECTKEY HERE"
String issueType = issue.getIssueType().getName()
String statusName = issue.getStatus().getName()

pim.moveIssueToAnotherProject(newProjectKey,issueType,statusName,issue)


class ProjectIssueMove {
Category log = Category.getInstance(ProjectIssueMove.class)

public void moveIssueToAnotherProject(String newProjectKey, String newIssueType, String newStatusName, MutableIssue targetIssue) {

def log = Logger.getLogger("com.acme.CreateSubtask")
log.setLevel(Level.INFO)

MoveIssueBean moveIssueBean = new MoveIssueBean(ComponentAccessor.getConstantsManager(), ComponentAccessor.getProjectManager())
moveIssueBean.getFieldValuesHolder().put(IssueFieldConstants.PROJECT, getProjectFromKey(newProjectKey).id)
moveIssueBean.getFieldValuesHolder().put(IssueFieldConstants.ISSUE_TYPE, getTargetIssueType(newIssueType).id)
moveIssueBean.setIssueId(targetIssue.getId())
moveIssueBean.setTargetStatusId(getStatusToSet(newStatusName).id)
moveIssueBean.setSourceIssueKey(targetIssue.getKey())
moveIssueBean.setUpdatedIssue(targetIssue)

ActionContext.getSession().put(SessionKeys.MOVEISSUEBEAN, moveIssueBean)

MoveIssueUpdateFields moveIssueUpdateFields = new MoveIssueUpdateFields(
ComponentAccessor.getSubTaskManager(),
ComponentAccessor.getConstantsManager(),
ComponentAccessor.getWorkflowManager(),
ComponentAccessor.getFieldManager(),
ComponentAccessor.getFieldLayoutManager(),
ComponentAccessor.getIssueFactory(),
ComponentAccessor.getFieldScreenRendererFactory(),
ComponentAccessor.getComponentOfType(CommentService.class),
ComponentAccessor.getComponentOfType(IssueSecurityHelper.class),
ComponentAccessor.getUserUtil()
)

MoveIssueConfirm moveIssueConfirm = new MoveIssueConfirm(
ComponentAccessor.getSubTaskManager(),
ComponentAccessor.getComponentOfType(AttachmentManager.class),
ComponentAccessor.getConstantsManager(),
ComponentAccessor.getWorkflowManager(),
ComponentAccessor.getFieldManager(),
ComponentAccessor.getFieldLayoutManager(),
ComponentAccessor.getIssueFactory(),
ComponentAccessor.getFieldScreenRendererFactory(),
ComponentAccessor.getComponentOfType(CommentService.class),
ComponentAccessor.getComponentOfType(IssueSecurityHelper.class),
ComponentAccessor.getIssueManager(),
ComponentAccessor.getUserUtil(),
ComponentAccessor.getIssueEventManager(),
ComponentAccessor.getComponentOfType(IssueEventBundleFactory.class),
ComponentAccessor.getComponentOfType(TxnAwareEventFactory.class)
)

log.info "Created MoveConfirmIssue"

JiraSystemProperties.getInstance()
moveIssueUpdateFields.setId(targetIssue.getId())
Method privateUpdateFieldsDoExecute = MoveIssueUpdateFields.class.getDeclaredMethod("doExecute")
privateUpdateFieldsDoExecute.setAccessible(true)
privateUpdateFieldsDoExecute.invoke(moveIssueUpdateFields) //fails here
moveIssueConfirm.setId(targetIssue.getId())
Method privateIssueConfirmDoExecute = MoveIssueConfirm.class.getDeclaredMethod("doExecute")

privateIssueConfirmDoExecute.setAccessible(true)
privateIssueConfirmDoExecute.invoke(moveIssueConfirm)
CoreTransactionUtil.commit(true)

}
private Project getProjectFromKey(String projectKey) { //get the projectObject from a String key
ProjectManager projectManager = ComponentAccessor.getProjectManager()
return projectManager.getProjectObjByKeyIgnoreCase(projectKey)
}
def getStatusToSet(String statusName) { //find the status object from a status name
StatusManager statusManager = ComponentAccessor.getComponentOfType(StatusManager.class)
def statuses = statusManager.getStatuses()
def iterator = statuses.iterator()
while (iterator.hasNext()) {
def status = iterator.next()
if (status.getName() == statusName) {
return status
}
}
return null
}
def IssueType getTargetIssueType(String issueTypeName) { //Find the issue type object from a issue type name
ConstantsManager constantsManager = ComponentAccessor.getConstantsManager()
def issueTypes = constantsManager.getAllIssueTypeObjects()
def issuetypeiterator = issueTypes.iterator()
while (issuetypeiterator.hasNext()) {
def issueType = issuetypeiterator.next()
if (issueType.name.toLowerCase() == issueTypeName.toLowerCase()) {
return issueType
}
}
return null
}
}

{code}

Jean-François FORGET
July 14, 2020

Thanks for the reply.

Unfortunately the error is the same.

 

On my side i did a tiny progress by embedding the script in a script runner Rest Endpoint.

I grab the "HttpServletRequest" object provided by the Rest Endpoint and inject it into ActionContext.setRequest(requestObject).

This is throwing the following error:

[c.a.j.w.f.steps.requestcleanup.WebworkActionCleanupStep] Thread corrupted! ActionContext still references a HttpRequest. URL: 'https://<my_jira_base_url>/rest/scriptrunner/latest/custom/<my_rest_endpoint_name>'. Attempted to clean up: false

 

So i tried this to try to force the "cleanup":

GenericDispatcher gd = new GenericDispatcher("Create")
request.setAttribute("jira.webwork.generic.dispatcher",gd)
ActionContext.setRequest(request)

 

No more error, but the issue doesn't moved....

Jean-François FORGET
July 14, 2020

Maybe I can try to summon @JamieA ?

(sorry)

Mohammad Mahyar ahmadi
Contributor
August 18, 2026

Got this fully working on Jira Data Center 11.3.7 — including proper re-key,
old-key redirect, and workflow/status migration. Wanted to close the loop here
since @Jackson Farnsworth's original NPE and @Jean-François FORGET's later
"issue doesn't moved" / thread-corruption issue never got resolved.

Two things were actually blocking this, neither of which is really about the
Move* action classes themselves:

1. Constructor signatures for MoveIssueUpdateFields/MoveIssueConfirm are NOT
stable across versions - every snippet in this thread has a different
parameter list because it was captured on a different Jira version. Don't
copy any of them verbatim - probe your own instance first:

[MoveIssue, MoveIssueUpdateFields, MoveIssueConfirm].each { Class c ->
println "=== ${c.name} ==="
c.declaredConstructors.each { ctor ->
println " (" + ctor.parameterTypes.collect { it.name }.join(", ") + ")"
}
}

On 11.3.7 mine needed extra UserManager and HelpUrls params at the end that
none of the posted versions above have.

2. The NPE on "this.request" (AbstractIssueSelectAction line ~124) happens
because there's no real HttpServletRequest in a Script Console/Listener/
scripted-automation context. @Jean-François's fix of grabbing the REST
endpoint's real request and calling ActionContext.setRequest(request) gets
further, but then Jira's own WebworkActionCleanupStep sees a "real" request
still attached and logs "Thread corrupted... Attempted to clean up: false"
- and in my testing that half-cleaned-up state is exactly why the move
silently didn't stick.

Fix: don't use a real request at all. Inject a no-op dynamic Proxy instead,
directly via reflection (both any public setter and, if none exists, the
declared field), so nothing "real" is ever attached for the framework to
worry about cleaning up:

def handler = { proxy, method, args ->
def rt = method.returnType
if (rt == boolean.class) return false
if (rt == int.class || rt == Integer.class) return 0
if (rt == long.class || rt == Long.class) return 0L
return null
} as InvocationHandler
HttpServletRequest fakeRequest = Proxy.newProxyInstance(
this.class.classLoader, [HttpServletRequest.class] as Class[], handler
) as HttpServletRequest

Full script (constants at the top need your own project/issuetype/status ids,
and you should re-probe the constructors for your version as above):

Verified: old key correctly redirects (findMovedIssue), the issue re-keys into
the target project's numbering, workflow/status is a valid step in the newly
attached workflow (not just changed and left inconsistent), and attachments
move over. Tested moving between two Jira Service Management projects.

Evie Z_
Community Manager
Community Managers are Atlassian Team members who specifically run and moderate Atlassian communities. Feel free to say hello!
August 18, 2026

 @Mohammad Mahyar ahmadi 

This topic is now closed as the discussion has become outdated. If you have more questions or want to continue the conversation, feel free to start a new topic. For more details on why we close older threads, check out our Rules of engagement - Atlassian Community . Additionally, we’ve removed the external links from your post, per our Community Rules of Engagement.

Thank you for your understanding!

0 votes
Laurent Bierge
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Champions.
December 5, 2018

Same for me !

Comments for this post are closed

Community moderators have prevented the ability to post new answers.

Post a new question

TAGS
AUG Leaders

Atlassian Community Events