magento2: "URL key for specified store already exists." cannot save category

I’ve been trying to update/save a specific category but get the message “URL key for specified store already exists.” every time.

I don’t know if you can replicated it as it’s very specific.

Recreate

  1. go to Products > Categories
  2. Select the Category
  3. Click Save

Message: URL key for specified store already exists. comes up and nothing is saved

Preconditions

Magento ver. 2.1.2 PHP 7 MySQL 5.6

Trouble Shooting:**

  1. I’ve checked the DB table url_rewrite and found no duplicates.
  2. Ran the following queries to clean out possible duplicates (see here):

delete from url_rewrite where entity_type='product' and entity_id NOT IN (Select entity_id from catalog_product_entity);

select count(*) from url_rewrite where entity_type='product' and entity_id NOT IN (Select entity_id from catalog_product_entity);

Each time I’ve re-ran the indexer via ssh and cleared all caches, nothing has worked.

Any ideas?

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 5
  • Comments: 96 (24 by maintainers)

Most upvoted comments

I have struggled with this problem for almost a day. And finnally i found out about this.

It is because of my data migrated from magento V1 to magento V2. Some categories have the same url as products. And when we save categories. It throws mysql exception named URL_REWRITE_REQUEST_PATH_STORE_ID (request_path, store_id is unique)

There are my solutions: The firt one : Clean up your database in table url_rewrite (Change the url_key of all category). You can write UpgradeData script for this solution.

The second one : Remove the duplication data when saving category. This data is throw in method doReplace($urls) in \vendor\magento\module-url-rewrite\Model\Storage\DbStorage.php file.

protected function doReplace($urls)
    {
        foreach ($this->createFilterDataBasedOnUrls($urls) as $type => $urlData) {
            $urlData[UrlRewrite::ENTITY_TYPE] = $type;
            $this->deleteByData($urlData);
        }
        $data = [];
        foreach ($urls as $url) {
            $data[] = $url->toArray();
        }
        $this->insertMultiple($data);
    } 

After debugging, i found out $data variable have duplicate record. If you want this method to work without any errors. Rewrite this method above to:

protected function doReplace($urls)
    {
        foreach ($this->createFilterDataBasedOnUrls($urls) as $type => $urlData) {
            $urlData[UrlRewrite::ENTITY_TYPE] = $type;
            $this->deleteByData($urlData);
        }
        $data = [];
        $storeId_requestPaths = [];
        foreach ($urls as $url) {
            $storeId = $url->getStoreId();
            $requestPath = $url->getRequestPath();
            // Skip if is exist in the database
            $sql = "SELECT * FROM url_rewrite where store_id = $storeId and request_path = '$requestPath'";
            $exists = $this->connection->fetchOne($sql);

            if ($exists) continue;

            $storeId_requestPaths[] = $storeId . '-' . $requestPath;
            $data[] = $url->toArray();
        }

        // Remove duplication data;
        $n = count($storeId_requestPaths);
        for ($i = 0; $i < $n - 1; $i++) {
            for ($j = $i + 1; $j < $n; $j++) {
                if ($storeId_requestPaths[$i] == $storeId_requestPaths[$j]) {
                    unset($data[$j]);
                }
            }
        }
        $this->insertMultiple($data);
    }

These solutions above both work for me. But the second one is not an ideal solution. Because, there are wrong url_keys in your database.
And sometime, it will not work as you expected in the front end.

I hope it will help. Thanks. Toan Tam

I also encountered this issue after I cleaned the magento database. At last I found the sql script I used was incomplete and some data still remained in database.

https://blog.mdnsolutions.com/magento-2-delete-all-categories/

I used the script suggested in this blog and the problem was solved.

There’s actually nothing return as a result.

@toanthree I like your second option, works great for me, I only added the last line of the old function, that makes it work for real:

    protected function doReplace($urls)
    {
        foreach ($this->createFilterDataBasedOnUrls($urls) as $type => $urlData) {
            $urlData[UrlRewrite::ENTITY_TYPE] = $type;
            $this->deleteByData($urlData);
        }
        $data = [];
        $storeId_requestPaths = [];
        foreach ($urls as $url) {
            $storeId = $url->getStoreId();
            $requestPath = $url->getRerquestPath();
            $url->setRequestPath($requestPath);

            // Skip if is exist in the database
            $sql = "SELECT * FROM url_rewrite where store_id = $storeId and request_path = '$requestPath'";
            $exists = $this->connection->fetchOne($sql);

            if ($exists) continue;

            $storeId_requestPaths[] = $storeId . '-' . $requestPath;
            $data[] = $url->toArray();
        }

        // Remove duplication data;
        $n = count($storeId_requestPaths);
        for ($i = 0; $i < $n - 1; $i++) {
            for ($j = $i + 1; $j < $n; $j++) {
                if ($storeId_requestPaths[$i] == $storeId_requestPaths[$j]) {
                    unset($data[$j]);
                }
            }
        }

        // remove root links
        foreach( $data as $key => $info ){
            if(  isset($info['target_path']) && ( stristr($info['target_path'],'/category/1') || stristr($info['target_path'],'/category/2') ) && $info['entity_type']=='product' || $info['request_path'] == "" ){
                    unset($data[$key]);
            }
        }

        // create links
        if( count($data) > 0 ){
            // file_put_contents('var/log/url_rewite.log', print_r($data, TRUE), FILE_APPEND); // enable to log
            $this->insertMultiple($data);
        }

    }

@orlangur

see http://fabien.potencier.org/pragmatism-over-theory-protected-vs-private.html

Closing the API allows design flaws to be found more easily

That’s all nice and well as long as core developers fix the design flaws in a timely matter. Otherwise the developer will need to hack into the core in order to perform the changes necessary. I mean here we discuss an issue that’s almost a year old and still the best solutions they are will require you to hack into the core yourselves. In such case these private markers turn out to be nothing more than a large PITA for the person who has to deal with it on a real project.

Unless bugfixes will be delivered in a timely matter and there are more extension points provided, those private markers are more of an issue than a solution.

still have this issue, this should not be closed

@maderlock this is one of the most right design decisions in Magento 2, see http://fabien.potencier.org/pragmatism-over-theory-protected-vs-private.html.

For people who keep running against this, first start by investigating if you don’t have invalid url-related data in your catalog, you can use this module for that if you want to. Then try to fix that invalid data (which you sometimes need to do directly in the database since Magento doesn’t have an UI for specific data like product or category url_path’s for example).

If you are importing new products or categories, then make sure the url_key’s for them won’t cause conflicts with already existing categories/products in your shop or with other categories/products in your import data. Everything needs to be unique, otherwise you’ll get into troubles!

Hope this helps somehow 🙂

This Magento 2 bug you can directy fix is changing file

File Path :vendor/magento/module-url-rewrite/Model/Storage/DbStorage.php

Orginal Code:

from

protected function doReplace($urls)
{
    foreach ($this->createFilterDataBasedOnUrls($urls) as $type => $urlData) {
        $urlData[UrlRewrite::ENTITY_TYPE] = $type;
        $this->deleteByData($urlData);
    }
    $data = [];
    foreach ($urls as $url) {
        $data[] = $url->toArray();
    }
    $this->insertMultiple($data);
}

into

protected function doReplace($urls)
{
    foreach ($this->createFilterDataBasedOnUrls($urls) as $type => $urlData) {
        $urlData[UrlRewrite::ENTITY_TYPE] = $type;
        $this->deleteByData($urlData);
    }
    $data = [];
    foreach ($urls as $url) {
        $data[] = $url->toArray();
    }

     /* Add this line : Get rid of rewrite for root Magento category to unduplicate things
    foreach($data as $key =>$info){
            if(isset($info['target_path']) && stristr($info['target_path'],'/category/1') && $info['entity_type']=='product'){
                    unset($data[$key]);
            }
    }

    $this->insertMultiple($data);
}

After insert this line clear cache.

Fix for me on a single store view, was to remove anything that had store_id as 1 set from importing data.

DELETE FROM catalog_product_entity_varchar WHERE store_id = 1

After a migration and a week digging into the problem the only thing that worked for me was https://www.safemage.com/url-optimization-after-migration-magento-2.html

I had to downgrade to 2.2.7 to use it. It says it works on 2.3 but it does not.

Hello, I got also the same issue in Magento 2.2.4 after data migration from Magento1.9.1.0. I have checked file

vendor/magento/module-url-rewrite/Model/Storage/DbStorage.php

and found that in function doReplace(array $urls) ,$this->deleteOldUrls($urls); is not working properly. So I have resolve this issue with help of forums

 //$this->deleteOldUrls($urls);

        //~ $data = [];
        //~ foreach ($urls as $url) {
            //~ $data[] = $url->toArray();
        //~ }
        
        
       /*Custom Functinality Used for delete old Url ,we need to override it*/  
			$data = [];
			$storeId_requestPaths = [];
			foreach ($urls as $url) {
				$storeId = $url->getStoreId();
				$requestPath = $url->getRequestPath();
				// Skip if is exist in the database
				$sql = "SELECT * FROM url_rewrite where store_id = $storeId and request_path = '$requestPath'";
				$exists = $this->connection->fetchOne($sql);

				if ($exists) continue;

				$storeId_requestPaths[] = $storeId . '-' . $requestPath;
				$data[] = $url->toArray();
			}

			// Remove duplication data;
			$n = count($storeId_requestPaths);
			for ($i = 0; $i < $n - 1; $i++) {
				for ($j = $i + 1; $j < $n; $j++) {
					if ($storeId_requestPaths[$i] == $storeId_requestPaths[$j]) {
						unset($data[$j]);
					}
				}
			}

Now issue is resolved and working fine.

ARGHH. Trying to put a fix in place with a preference on ProductScopeRewriteGenerator but every ****ing thing is private. Magento, PLEASE STOP DOING THIS!

@JonathanHelvey Yep or just change the product’s url_key to something different

I handled it by updating the products url_key with a script that simply adds a number depending on the occurences 😛

it can probably be done in multiple ways

@aliomattux: in fact you should clear the url_path for all products in your database. It’s a legacy field from Magento 1 which for some reason is still included and being used by Magento 2, even though Magento 2 will never change its contents. It should therefore always be empty otherwise you’ll run into problems. One of the reasons why it often isn’t empty, is because people migrate from M1 to M2 and inadvertently copy the field over.

Hi @engcom-Bravo. Thank you for working on this issue. In order to make sure that issue has enough information and ready for development, please read and check the following instruction: 👇

  • 1. Verify that issue has all the required information. (Preconditions, Steps to reproduce, Expected result, Actual result).

    DetailsIf the issue has a valid description, the label Issue: Format is valid will be added to the issue automatically. Please, edit issue description if needed, until label Issue: Format is valid appears.

  • 2. Verify that issue has a meaningful description and provides enough information to reproduce the issue. If the report is valid, add Issue: Clear Description label to the issue by yourself.

  • 3. Add Component: XXXXX label(s) to the ticket, indicating the components it may be related to.

  • 4. Verify that the issue is reproducible on 2.3-develop branch

    Details- Add the comment @magento give me 2.3-develop instance to deploy test instance on Magento infrastructure.
    - If the issue is reproducible on 2.3-develop branch, please, add the label Reproduced on 2.3.x.
    - If the issue is not reproducible, add your comment that issue is not reproducible and close the issue and stop verification process here!

  • 5. Add label Issue: Confirmed once verification is complete.

  • 6. Make sure that automatic system confirms that report has been added to the backlog.

Magento Open Source 2.3.3 PHP 7.2.19 Ubuntu 18.04 Migrated database from 1.9.2.4

Steps to reproduce Category has URL key of led-deck-lights Product has URL key of aurora-odyssey-led-strip-light There is also a URL rewrite with request path/target path lighting/deck-stair-lights/aurora-odyssey-led-strip-light.html / aurora-odyssey-led-strip-light.html

Purpose of redirect is to redirect a direct category nested link to go directly to the product. Most likely for Canonical/SEO purposes.

When I go to the category in the admin, I can save the category changing some field values like name, etc. If I set Anchor Yes/No, just toggle the switch and save, I get an error that the URL key already exists. I receive the same error if I attempt to remove the single product from the category or if I go to the product and remove the category there, the same exact error.

This product does NOT have a duplicate key. The issue has been narrowed to a URL rewrite. Deleting the URL rewrite fixes the issue, however that is not a workable solution. The company has thousands of URL redirects for varying purposes. URL rewrite management is a valid business function. The raise exception is not logical based on the use case. The raise happens in module-url-rewrite/Model/Storage/DbStorage.php, doReplace()

There are many categories that have this issue and the only solution so far is to delete all url rewrites and not create them, which again is not an acceptable solution.

I have read at least 2 bug reports on this specific issue dating back to 2016. I do not understand why it is still an issue and is not fixed.

@koopjesboom, try to avoid giving advise like running rm -rf pub/static/ This will also remove the file pub/static/.htaccess which you certainly do not want to remove when using Apache as your webserver. Please use the command rm -r pub/static/* instead, the * will not match files which start with a . in the filename. Also try to avoid using the flag -f of the rm command as much as possible, this should not be needed in any of these commands if your permissions are setup correctly.

Also removing var/di/ and generated/ is very inconsistent, since the first directory is from Magento 2.1 and no longer exists since 2.2 while the second directory only got introduced in 2.2. Also, there is no directory var/dir/ in Magento.

Giving advise like this and people just copy/pasting stuff like this might make the problem worse instead of improving it, so please try to pay a bit more attention while posting such a long string of commands 😉

Also your advise won’t help with fixing this database error @visahardik was running against.

Hello I am facing same issue in 2.3.2 after migration

main.CRITICAL: Unique constraint violation found {"exception":"[object] (Magento\\Framework\\Exception\\AlreadyExistsException(code: 0): Unique constraint violation found at /vendor/magento/framework/EntityManager/Operation/Update.php:121, Magento\\Framework\\DB\\Adapter\\DuplicateException(code: 1062): SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'White-Ultra-Board-Custom-Cut-Sizes.html-1' for key 'MGL0_URL_REWRITE_REQUEST_PATH_STORE_ID', query was: INSERT INTO mgl0_url_rewrite (redirect_type,is_autogenerated,metadata,description,entity_type,entity_id,request_path,target_path,store_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?, ?) at /vendor/magento/framework/DB/Adapter/Pdo/Mysql.php:589, Zend_Db_Statement_Exception(code: 23000): SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'White-Ultra-Board-Custom-Cut-Sizes.html-1' for key 'MGL0_URL_REWRITE_REQUEST_PATH_STORE_ID', query was: INSERT INTO mgl0_url_rewrite (redirect_type,is_autogenerated,metadata,description,entity_type,entity_id,request_path,target_path,store_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?, ?) at /vendor/magento/framework/DB/Statement/Pdo/Mysql.php:110, PDOException(code: 23000): SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'White-Ultra-Board-Custom-Cut-Sizes.html-1' for key 'MGL0_URL_REWRITE_REQUEST_PATH_STORE_ID' at /vendor/magento/framework/DB/Statement/Pdo/Mysql.php:91)"} [] [2019-08-29 11:15:46] main.CRITICAL: Exception message: Unique constraint violation found Trace: <pre>#1 Magento\Framework\EntityManager\EntityManager->save(&Magento\Catalog\Model\Product\Interceptor#0000000065b434430000000061455766#) called at [vendor/magento/module-catalog/Model/ResourceModel/Product.php:680] #2 Magento\Catalog\Model\ResourceModel\Product->save(&Magento\Catalog\Model\Product\Interceptor#0000000065b434430000000061455766#) called at [vendor/magento/framework/Interception/Interceptor.php:58] #3 Magento\Catalog\Model\ResourceModel\Product\Interceptor->___callParent('save', array(&Magento\Catalog\Model\Product\Interceptor#0000000065b434430000000061455766#)) called at [vendor/magento/framework/Interception/Interceptor.php:138] #4 Magento\Catalog\Model\ResourceModel\Product\Interceptor->Magento\Framework\Interception\{closure}(&Magento\Catalog\Model\Product\Interceptor#0000000065b434430000000061455766#) called at [vendor/magento/module-catalog-search/Model/Indexer/Fulltext/Plugin/Product.php:58] #5 Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin\Product->addCommitCallback(&Magento\Catalog\Model\ResourceModel\Product\Interceptor#0000000065b4360f0000000061455766#, &Closure#0000000065b434160000000061455766#, &Magento\Catalog\Model\Product\Interceptor#0000000065b434430000000061455766#) called at [vendor/magento/module-catalog-search/Model/Indexer/Fulltext/Plugin/Product.php:28] #6 Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin\Product->aroundSave(&Magento\Catalog\Model\ResourceModel\Product\Interceptor#0000000065b4360f0000000061455766#, &Closure#0000000065b434160000000061455766#, &Magento\Catalog\Model\Product\Interceptor#0000000065b434430000000061455766#) called at [vendor/magento/framework/Interception/Interceptor.php:135] #7 Magento\Catalog\Model\ResourceModel\Product\Interceptor->Magento\Framework\Interception\{closure}(&Magento\Catalog\Model\Product\Interceptor#0000000065b434430000000061455766#) called at [vendor/magento/framework/App/Cache/FlushCacheByTags.php:68] #8 Magento\Framework\App\Cache\FlushCacheByTags->aroundSave(&Magento\Catalog\Model\ResourceModel\Product\Interceptor#0000000065b4360f0000000061455766#, &Closure#0000000065b434160000000061455766#, &Magento\Catalog\Model\Product\Interceptor#0000000065b434430000000061455766#) called at [vendor/magento/framework/Interception/Interceptor.php:135] #9 Magento\Catalog\Model\ResourceModel\Product\Interceptor->Magento\Framework\Interception\{closure}(&Magento\Catalog\Model\Product\Interceptor#0000000065b434430000000061455766#) called at [vendor/magento/framework/Interception/Interceptor.php:153] #10 Magento\Catalog\Model\ResourceModel\Product\Interceptor->___callPlugins('save', array(&Magento\Catalog\Model\Product\Interceptor#0000000065b434430000000061455766#), array(array('apply_catalog_ru...'), array('reload_attribute...'))) called at [generated/code/Magento/Catalog/Model/ResourceModel/Product/Interceptor.php:273] #11 Magento\Catalog\Model\ResourceModel\Product\Interceptor->save(&Magento\Catalog\Model\Product\Interceptor#0000000065b434430000000061455766#) called at [vendor/magento/framework/Model/AbstractModel.php:655] #12 Magento\Framework\Model\AbstractModel->save() called at [generated/code/Magento/Catalog/Model/Product/Interceptor.php:2442] #13 Magento\Catalog\Model\Product\Interceptor->save() called at [vendor/magento/module-catalog/Controller/Adminhtml/Product/Save.php:131] #14 Magento\Catalog\Controller\Adminhtml\Product\Save->execute() called at [generated/code/Magento/Catalog/Controller/Adminhtml/Product/Save/Interceptor.php:24] #15 Magento\Catalog\Controller\Adminhtml\Product\Save\Interceptor->execute() called at [vendor/magento/framework/App/Action/Action.php:108] #16 Magento\Framework\App\Action\Action->dispatch(&Magento\Framework\App\Request\Http#0000000065b432a00000000061455766#) called at [vendor/magento/module-backend/App/AbstractAction.php:231] #17 Magento\Backend\App\AbstractAction->dispatch(&Magento\Framework\App\Request\Http#0000000065b432a00000000061455766#) called at [vendor/magento/framework/Interception/Interceptor.php:58] #18 Magento\Catalog\Controller\Adminhtml\Product\Save\Interceptor->___callParent('dispatch', array(&Magento\Framework\App\Request\Http#0000000065b432a00000000061455766#)) called at [vendor/magento/framework/Interception/Interceptor.php:138] #19 Magento\Catalog\Controller\Adminhtml\Product\Save\Interceptor->Magento\Framework\Interception\{closure}(&Magento\Framework\App\Request\Http#0000000065b432a00000000061455766#) called at [vendor/magento/module-backend/App/Action/Plugin/Authentication.php:143] #20 Magento\Backend\App\Action\Plugin\Authentication->aroundDispatch(&Magento\Catalog\Controller\Adminhtml\Product\Save\Interceptor#0000000065b4364b0000000061455766#, &Closure#0000000065b436770000000061455766#, &Magento\Framework\App\Request\Http#0000000065b432a00000000061455766#) called at [vendor/magento/framework/Interception/Interceptor.php:135] #21 Magento\Catalog\Controller\Adminhtml\Product\Save\Interceptor->Magento\Framework\Interception\{closure}(&Magento\Framework\App\Request\Http#0000000065b432a00000000061455766#) called at [vendor/magento/framework/Interception/Interceptor.php:153] #22 Magento\Catalog\Controller\Adminhtml\Product\Save\Interceptor->___callPlugins('dispatch', array(&Magento\Framework\App\Request\Http#0000000065b432a00000000061455766#), NULL) called at [generated/code/Magento/Catalog/Controller/Adminhtml/Product/Save/Interceptor.php:39] #23 Magento\Catalog\Controller\Adminhtml\Product\Save\Interceptor->dispatch(&Magento\Framework\App\Request\Http#0000000065b432a00000000061455766#) called at [vendor/magento/framework/App/FrontController.php:159] #24 Magento\Framework\App\FrontController->processRequest(&Magento\Framework\App\Request\Http#0000000065b432a00000000061455766#, &Magento\Catalog\Controller\Adminhtml\Product\Save\Interceptor#0000000065b4364b0000000061455766#) called at [vendor/magento/framework/App/FrontController.php:99] #25 Magento\Framework\App\FrontController->dispatch(&Magento\Framework\App\Request\Http#0000000065b432a00000000061455766#) called at [vendor/magento/framework/Interception/Interceptor.php:58] #26 Magento\Framework\App\FrontController\Interceptor->___callParent('dispatch', array(&Magento\Framework\App\Request\Http#0000000065b432a00000000061455766#)) called at [vendor/magento/framework/Interception/Interceptor.php:138] #27 Magento\Framework\App\FrontController\Interceptor->Magento\Framework\Interception\{closure}(&Magento\Framework\App\Request\Http#0000000065b432a00000000061455766#) called at [vendor/magento/framework/Interception/Interceptor.php:153] #28 Magento\Framework\App\FrontController\Interceptor->___callPlugins('dispatch', array(&Magento\Framework\App\Request\Http#0000000065b432a00000000061455766#), array(array('default_store_se...', 'page_cache_from_...', 'storeCookieValid...', 'install', 'configHash'))) called at [generated/code/Magento/Framework/App/FrontController/Interceptor.php:26] #29 Magento\Framework\App\FrontController\Interceptor->dispatch(&Magento\Framework\App\Request\Http#0000000065b432a00000000061455766#) called at [vendor/magento/framework/App/Http.php:137] #30 Magento\Framework\App\Http->launch() called at [generated/code/Magento/Framework/App/Http/Interceptor.php:24] #31 Magento\Framework\App\Http\Interceptor->launch() called at [vendor/magento/framework/App/Bootstrap.php:261] #32 Magento\Framework\App\Bootstrap->run(&Magento\Framework\App\Http\Interceptor#0000000065b432dd0000000061455766#) called at [index.php:39]

Any idea to how to resolve.

I tried to delete all product url in url_rewrite table. after save product I am facing Unique constraint violation found.

Try this command in your SSH with putty ow whatever tool you prefer. Do not forget to check the language codes in the deploy of static contend. And check the commands for your needs if there are things you do not want to happen get them out.

this worked for me, after my error like above was gone If not check all file and folder permissions and re-run the below

rm -rf var/di/; rm -rf pub/static/; rm -rf var/cache/; rm -rf var/pagecache/; rm -rf generated/; rm -rf var/dir/; rm -rf var/view_preprocessed/*; php bin/magento cache:flush && php bin/magento setup:upgrade && php bin/magento setup:di:compile && php bin/magento setup:static-content:deploy en_US nl_NL && php bin/magento indexer:reindex && php bin/magento cache:enable

Best regards

@sadeeshmca

in latest magento 2.3 code totally change in this function

    protected function doReplace(array $urls)
    {
        $this->deleteOldUrls($urls);

        $data = [];
        foreach ($urls as $url) {
            $data[] = $url->toArray();
        }
        try {
            $this->insertMultiple($data);
        } catch (\Magento\Framework\Exception\AlreadyExistsException $e) {
            /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] $urlConflicted */
            $urlConflicted = [];
            foreach ($urls as $url) {
                $urlFound = $this->doFindOneByData(
                    [
                        UrlRewrite::REQUEST_PATH => $url->getRequestPath(),
                        UrlRewrite::STORE_ID => $url->getStoreId(),
                    ]
                );
                if (isset($urlFound[UrlRewrite::URL_REWRITE_ID])) {
                    $urlConflicted[$urlFound[UrlRewrite::URL_REWRITE_ID]] = $url->toArray();
                }
            }
            if ($urlConflicted) {
                throw new \Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException(
                    __('URL key for specified store already exists.'),
                    $e,
                    $e->getCode(),
                    $urlConflicted
                );
            } else {
                throw $e->getPrevious() ?: $e;
            }
        }

        return $urls;
    }

Then how can replace this function ?

root Magento category @NiteshKuyate

I just want to add one more reason why I received the url key error. In addition to fixing the url key product attributes after a data migration I ALSO had to delete any orphan records in url_rewrite :

delete from url_rewrite where entity_type = ‘product’ and entity_id not in (select entity_id from catalog_product_entity);

Hope this helps someone. Thank you all for sharing your solutions!

@sadeeshmca I tried your fix but it didn’t work for me as the error persists as I tried to save my category. I couldn’t find your suggested file, in my case I edited: app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php I have fixed some of these problematic categories by exclude certain products, it looks like those products were created wrong, maybe they been duplicated without changing the url key? I never had such issue until I handed over to the client when they started messing around with duplicate products.